This is the third part of the Document Generator series, where we‘re plugging together the two distinct parts of our pipeline.

In the first part we took a look at automatically generating Word documents with pre-made templates. The second part investigated building PDF files from any Word document that can be automated.


So, you might ask, why we‘re taking such a diversion from just plain out generating PDFs within our backend. I want to give some examples from my experience:

  • PDF generation libraries are flexible but with their flexibility comes a lot of complexity that needs to be maintained, which can be very expensive
  • while debugging might be easier, the visual appearance often comes second with PDF libraries
  • no non-technical people can work on the templates, so the work on them has to be done by highly-skilled expensive workers

Thus the velocity of your team can be highly increased by using Word templates instead of manually building PDFs.

Plugging together

If we take a look at our result from the first post, we can see that the generate returns a Readable. But we could instead wrap the part until we turn the Buffer into a Readable and call this function instead. As our convertWordToPdf function also takes a Buffer, we can then pipe the Word result.

Let‘s revisit the API endpoint and change it a bit to return a PDF instead:

async function pdfFromTemplate(document: string, payload: any) {
    const word = await generate(document, payload);
    const pdf = await convertWordToPdf(word.buffer);

    const readable = Readable.from(pdf);

    return { pdf: readable, filename: word.filename };

router.get('/generate/:documentId', async (req, res) => {
    try {
      const generated = await pdfFromTemplate(req.params.documentId, req.query);
        'attachment; filename="' + encodeURI(generated.filename) + '"'
      return generated.pdf.pipe(res);
    } catch (error) {
      if ( === 'ValidationError') {
          .send({ message: 'validation failed', error: error.message });
      } else {
        res.status(500).send({ message: 'Could not generate documents.' });

Now, your backend returns sweet PDF files. You could of course also run both endpoints in parallel and offer your users the choice.