DEV Community

Luiz Américo
Luiz Américo

Posted on

Save a pdf created with pdfkit to Google Cloud Storage

While cleaning the cloud functions of one Firebase project, i found a set of routines to save a pdf file to Google Cloud Storage. I'm removing them because i moved the pdf generation to client side.

Posting here in hope it can be useful for someone or my future self.

Use case

The pdf is generated dynamically with pdfkit in a firebase cloud function and cached in Google Cloud Storage bucket.

Generate the Data URL

The first function is to generate a Data URL from a pdfkit document

async function generatePdfDataUrl(doc) {
  return new Promise((resolve, reject) => {
    const chunks = []

    doc.on('data', (chunk) => {
      chunks.push(chunk)
    })

    doc.on('end', () => {
      const result = Buffer.concat(chunks)
      resolve('data:application/pdf;base64,' + result.toString('base64'))
    })

    doc.on('error', (error) => {
      reject(error)
    })

    doc.end()
  })
}
Enter fullscreen mode Exit fullscreen mode

It receives a pdf document and wraps the node stream events in a promise. Stores the file chunks and, at the end, concatenate them after converting to base64.

Save to Cloud Storage

const isEmulator = process.env.FUNCTIONS_EMULATOR

const baseFileURL = 'https://storage.googleapis.com/XXX.appspot.com/'

async function storePdfDoc(storage, path, doc) { 
  if (isEmulator) {
    const fileUrl = await generatePdfDataUrl(doc)
    return { fileUrl }
  }

  // toLocalDate converts cloud date (UTC) to local GMT-3 in my case
  const reportDate = toLocalDate(new Date())
  const formattedDate = format(reportDate, 'dd-MM-yy-kk-mm')

  const bucket = storage.bucket()
  const fileName = slugify(`${path}-${formattedDate}.pdf`)
  const file = bucket.file(fileName)
  const bucketFileStream = file.createWriteStream()

  return new Promise((resolve, reject) => {
    bucketFileStream.on('finish', () => {
      const link = `${baseFileURL}${fileName}`
      resolve({ fileUrl: link })
    })

    bucketFileStream.on('error', function bucketError(err) {
      reject(err)
    })

    doc.pipe(bucketFileStream)
    doc.end()
  })
}
Enter fullscreen mode Exit fullscreen mode

The first step is to check if is running in firebase emulator and return the Data URL instead of storing the file

At the time i wrote this function the firebase Storage emulator was not available

This function also wrap node stream functions in a promise. This time, we create a file in the bucket using bucket.file and the corresponding writable stream, than pipe the pdfkit document to it.

The catch here is to resolve the promise in the destination stream 'finish' event instead of document 'end' event. This way we ensure the file is fully created / uploaded on the cloud.

Warp.dev image

The best coding agent. Backed by benchmarks.

Warp outperforms every other coding agent on the market, and gives you full control over which model you use. Get started now for free, or upgrade and unlock 2.5x AI credits on Warp's paid plans.

Download Warp

Top comments (0)

Gen AI apps are built with MongoDB Atlas

Gen AI apps are built with MongoDB Atlas

MongoDB Atlas is the developer-friendly database for building, scaling, and running gen AI & LLM apps—no separate vector DB needed. Enjoy native vector search, 115+ regions, and flexible document modeling. Build AI faster, all in one place.

Start Free

👋 Kindness is contagious

Explore this practical breakdown on DEV’s open platform, where developers from every background come together to push boundaries. No matter your experience, your viewpoint enriches the conversation.

Dropping a simple “thank you” or question in the comments goes a long way in supporting authors—your feedback helps ideas evolve.

At DEV, shared discovery drives progress and builds lasting bonds. If this post resonated, a quick nod of appreciation can make all the difference.

Okay