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.

Top comments (0)