Create dynamic PDFs using React and Node.js

Original author: Adrian Hajdin
  • Transfer
The material, the translation of which we publish today, is devoted to the creation of dynamic PDF-files using HTML-code as a template. Namely, we will talk about how to create a simple invoice for payment of certain goods or services, the dynamic data included in which are taken from the state of the React application. The base of the React application was created using create-react-app, the server part of the project is based on Node.js, and the Express framework was used in its development. The author of this material notes that he prepared a video that demonstrates the development of the project. If you decide to watch the video and read the article, it is recommended to do so. First, skim through the article, then turn on the video and recreate the system you are considering there. After that, just read the article.





Project creation


Create a project directory and go to it:

mkdir pdfGenerator && cd pdfGenerator

Create a new React application:

create-react-app client

After completing the application creation, go to the directory you just created and install the dependencies:

cd client && npm i -S axios file-saver

Create an Express server. To do this, create a folder in the project directory serverand go to it. In it, create a file index.jsand run the initialization of the project:

mkdir server && cd server && touch index.js && npm init

Here, to form package.json, just click on several times Enter. After that, execute the following command to add the necessary dependencies to the server part of the project:

npm i -S express body-parser cors html-pdf

Now, in the file client/package.json, above the dependency description section, add the following:

"proxy": "http://localhost:5000/"

This will help in working with the local server from the client code.

Now you need to open two windows of the terminal.

In the first window, go to the directory clientand execute the following command:

npm start

In the second window, go to the folder serverand execute the following command:

nodemon index.js

Initial client setup


The client part of our project will look very simple.

To begin with, in the file of the src/App.jsclient part of the application, we import into the dependency code:

import axios from 'axios';
import { saveAs } from 'file-saver';

After that, at the top of the component description, initialize the state:

state = {
   name: 'Adrian',
   receiptId: 0,
   price1: 0,
   price2: 0,
}

Let's remove the standard JSX markup created in the application template by means create-react-appand returned from the method render(). Insert the following into it:

                   Download PDF

Create a method handleChangethat will be responsible for updating the application state data related to the input fields:

handleChange = ({ target: { value, name }}) => this.setState({ [name]: value })

Now we can move on to the task of creating a PDF file. That part of it, which is solved by means of the client, is to create a POST request to the server. The request sends the application state:

createAndDownloadPdf = () => {
   axios.post('/create-pdf', 
this.state)
}

Before we continue working on the client side of the project, we need to configure the routes on the server. This will allow the server to receive data from the client, generate a PDF file and transfer this file back to the client.

Initial server setup


The server part of the project will include only two routes. One is needed to create PDFs. The second is for sending files to the client.

First, import the index.jsdependencies into the file :

const express = require('express');
const bodyParser = require('body-parser');
const pdf = require('html-pdf');
const cors = require('cors');

We initialize the Express application and configure the port:

const app = express();
const port = process.env.PORT || 5000;

Set up the query parser. What we need will be available as req.body. We will also configure CORS so that our work would not be prevented by an error Cross-Origin Request Blocked:

app.use(cors());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

After that, start the server:

app.listen(port, () => console.log(`Listening on port ${port}`));

Now we can tackle the code responsible for creating the PDFs.

Create an HTML template for PDFs


We need an HTML template to use when creating PDF files. In creating such a template, endless possibilities open up before us. Everything that can be created using pure HTML and CSS can be represented as a PDF file. Create a folder in the serverdirectory documents, let's move in it and create a file in it index.js:

mkdir documents && cd documents && touch index.js

From this file, we export an arrow function that will return all the necessary HTML code. When calling this function, you can use the parameters, which we will also describe here.

module.exports = ({ name, price1, price2, receiptId }) => { ... }

Here I will give you an example of an HTML template, and you just copy it into your project. But you, of course, can create your own template.

Let's bring the code index.jsfrom the folder server/documentsto the following form:

module.exports = ({ name, price1, price2, receiptId }) => {
    const today = new Date();
return `
    
    
       
          
          PDF Result Template
          
       
       
          
                                                                                                                                                                                                                                                                                                                                                                     
                                                                                                                                                           
                               Datum: ${`${today.getDate()}. ${today.getMonth() + 1}. ${today.getFullYear()}.`}                             
                   
                                                                                                                                                           
                               Customer name: ${name}                                                             Receipt number: ${receiptId}                             
                   
Bought items:Price
First item:${price1}$
Second item:${price2}$
             
             

Total price: ${parseInt(price1) + parseInt(price2)}$

          
                 `; };

Include this file in the file server/index.js:

const pdfTemplate = require('./documents');

Create PDFs


Recall that on the server we are going to create two routes. The POST route will be responsible for receiving data from the client and creating a PDF file. The GET route will be used to send the finished file to the client.

▍ create-pdf route


In the POST route, create-pdfwe will use the command pdf.create(), referring to the object imported from the module html-pdf.

The pdf.create()HTML template is used as the first parameter of the method , as well as data received from the client.

In order to return pdf.create(), we will call the method toFile(), passing it the name that we want to assign to the PDF document, as well as the arrow callback function. This function, in case of an error, will execute the command res.send(Promise.reject()). In the event that everything went well, she will execute the command res.send(Promise.resolve()).

app.post('/create-pdf', (req, res) => {
    pdf.create(pdfTemplate(req.body), {}).toFile('result.pdf', (err) => {
        if(err) {
            res.send(Promise.reject());
        }
        res.send(Promise.resolve());
    });
});

▍Fetch-pdf Route


At the same time, we will create a route that will be used after, at the request of the client, a PDF file is successfully created. Here we just take the finished document and send it to the client using res.sendFile():

app.get('/fetch-pdf', (req, res) => {
    res.sendFile(`${__dirname}/result.pdf`)
})

Client function createAndDownloadPdf


Now we can return to the client code and continue working on the function createAndDownloadPdf. Here we execute a POST request to the server using the module axios. Now this function looks like this:

createAndDownloadPdf = () => {
   axios.post('/create-pdf', 
this.state)
}

If after executing a POST request to the server a PDF document was created, we need to execute a GET request, in response to which the server will send the finished document to the client.

To implement this pattern of behavior, we, after a call axios.post(), call. then(). This allows us to do that in response to the client’s POST request, we return a promise from the server that can be either successfully resolved or rejected.

We supplement the function with the following code:

.then(() => axios.get('/fetch-pdf', { responseType: 'blob' }))
.then((
res) => {})

Here you can pay attention to what is responseTypeused as quality blob. Before we go any further, let's talk about what it is.

Blob objects


Blob is an immutable object representing some raw data. Such objects are often used to work with data that may not be in native JavaScript format. Such objects are sequences of bytes that store, for example, file data. It may seem that the object Blobstores a link to a file, but in fact it is not. These objects store data that you can work with. For example - they can be saved to files.

Project Completion


Now that we know what the objects are Blob, we can take advantage of another challenge .then()by creating, on the basis of it res.data, a new Blob-object. When creating this object, we will pass a parameter to its constructor, indicating that the data that the object will store is of type application/pdf. After that, we can use the method saveAs()that was imported from the module file-saverand save the data to a file. As a result, the method code createAndDowndloadPdfwill look like the one shown below:

  createAndDownloadPdf = () => {
    axios.post('/create-pdf', this.state)
      .then(() => axios.get('fetch-pdf', { responseType: 'blob' }))
      .then((res) => {
        const pdfBlob = new Blob([res.data], { type: 'application/pdf' });
        saveAs(pdfBlob, 'newPdf.pdf');
      })
  }

This is what the browser interface looks like.


Application in the browser

After filling in the fields and clicking on the button Download PDF, data is transferred to the server and a PDF document is downloaded from it.


Download a PDF document

And here is the PDF document itself.


Software Generated PDF

Summary


We looked at a mechanism that allows you to programmatically create PDF files. Here is a GitHub repository with project code. We hope that the ideas that you met in this material will find application in your development.

Dear readers! How would you go about solving the problem of programmatically creating PDF files using Node.js?

Also popular now: