📜 ⬆️ ⬇️

Create dynamic PDFs using React and Node.js

The material, the translation of which we are publishing today, is devoted to creating dynamic PDF files using HTML code as a template. Namely, it will be discussed how to form a simple invoice for payment of certain goods or services, the dynamic data included in which is taken from the state of the React-application. The React application base was created using create-react-app, the server side of the project is based on Node.js, and the Express framework is used in its development.



The author of this material notes that he has prepared a video demonstrating 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 considered there. And then just read the article.

Creating a project


Create a project directory and go to it:
')
mkdir pdfGenerator && cd pdfGenerator 

Create a new React application:

 create-react-app client 

After completing the creation of the application, go to the newly created directory and install the dependencies:

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

Create an Express server. To do this, create a server folder in the project directory and navigate to it. In it we will create the index.js file and run the project initialization:

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

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

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

Now, in the client/package.json file, above the dependencies 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 terminal windows.

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

 npm start 

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

 nodemon index.js 

Initial client setup


The client side of our project will look very simple.

To begin with, in the src/App.js client part of the application, import into the dependency code:

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

After that, in the upper part of the component description, we initialize the state:

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

Remove the standard JSX markup created in the application template using the create-react-app and returned from the render() method. Insert there the following:

 <div className="App">   <input type="text" placeholder="Name" name="name" onChange {this.handleChange}/>   <input type="number" placeholder="Receipt ID" name="receiptId"  onChange={this.handleChange}/>   <input type="number" placeholder="Price 1" name="price1" onChange={this.handleChange}/>   <input type="number" placeholder="Price 2" name="price2" onChange={this.handleChange}/>   <button onClick={this.createAndDownloadPdf}>Download PDF</button></div> 

Create a handleChange method that 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 proceed to solving the problem of creating a PDF file. That part of it, which is decided by the client, is to create a POST request to the server. The request sends the status of the application:

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

Before we continue working on the client side of the project, we need to set up routes on the server. This will allow the server to retrieve 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 PDF files. The second is for sending files to the client.

First we import the dependencies into the index.js file:

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

Initialize the Express application and configure the port:

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

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

 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 deal with the code responsible for creating PDF files.

Creating an HTML Template for PDF Files


We need an HTML template that will be used when creating PDF files. In the creation of such a pattern, endless possibilities open up before us. Everything that can be created using pure HTML and CSS can be represented as a PDF file. Create the documents directory in the server folder, navigate to it and create the index.js file in it:

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

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

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

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

Here is the code index.js from the server/documents folder to the following form:

 module.exports = ({ name, price1, price2, receiptId }) => {    const today = new Date(); return `    <!doctype html>    <html>       <head>          <meta charset="utf-8">          <title>PDF Result Template</title>          <style>             .invoice-box {             max-width: 800px;             margin: auto;             padding: 30px;             border: 1px solid #eee;             box-shadow: 0 0 10px rgba(0, 0, 0, .15);             font-size: 16px;             line-height: 24px;             font-family: 'Helvetica Neue', 'Helvetica',             color: #555;             }             .margin-top {             margin-top: 50px;             }             .justify-center {             text-align: center;             }             .invoice-box table {             width: 100%;             line-height: inherit;             text-align: left;             }             .invoice-box table td {             padding: 5px;             vertical-align: top;             }             .invoice-box table tr td:nth-child(2) {             text-align: right;             }             .invoice-box table tr.top table td {             padding-bottom: 20px;             }             .invoice-box table tr.top table td.title {             font-size: 45px;             line-height: 45px;             color: #333;             }             .invoice-box table tr.information table td {             padding-bottom: 40px;             }             .invoice-box table tr.heading td {             background: #eee;             border-bottom: 1px solid #ddd;             font-weight: bold;             }             .invoice-box table tr.details td {             padding-bottom: 20px;             }             .invoice-box table tr.item td {             border-bottom: 1px solid #eee;             }             .invoice-box table tr.item.last td {             border-bottom: none;             }             .invoice-box table tr.total td:nth-child(2) {             border-top: 2px solid #eee;             font-weight: bold;             }             @media only screen and (max-width: 600px) {             .invoice-box table tr.top table td {             width: 100%;             display: block;             text-align: center;             }             .invoice-box table tr.information table td {             width: 100%;             display: block;             text-align: center;             }             }          </style>       </head>       <body>          <div class="invoice-box">             <table cellpadding="0" cellspacing="0">                <tr class="top">                   <td colspan="2">                      <table>                         <tr>                            <td class="title"><img src="https://i2.wp.com/cleverlogos.co/wp-content/uploads/2018/05/reciepthound_1.jpg?fit=800%2C600&ssl=1"                               style="width:100%; max-width:156px;"></td>                            <td>                               Datum: ${`${today.getDate()}. ${today.getMonth() + 1}. ${today.getFullYear()}.`}                            </td>                         </tr>                      </table>                   </td>                </tr>                <tr class="information">                   <td colspan="2">                      <table>                         <tr>                            <td>                               Customer name: ${name}                            </td>                            <td>                               Receipt number: ${receiptId}                            </td>                         </tr>                      </table>                   </td>                </tr>                <tr class="heading">                   <td>Bought items:</td>                   <td>Price</td>                </tr>                <tr class="item">                   <td>First item:</td>                   <td>${price1}$</td>                </tr>                <tr class="item">                   <td>Second item:</td>                   <td>${price2}$</td>                </tr>             </table>             <br />             <h1 class="justify-center">Total price: ${parseInt(price1) + parseInt(price2)}$</h1>          </div>       </body>    </html>    `; }; 

server/index.js include this file in the 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.

▍ Route create-pdf


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

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

In order to return pdf.create() , we will call the toFile() method, passing it the name we want to assign to the PDF document, as well as the arrow callback function. This function, if an error occurs, will execute the res.send(Promise.reject()) command. In case everything went well, it will execute the res.send(Promise.resolve()) 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());    }); }); 

▍ Route fetch-pdf


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 simply 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 createAndDownloadPdf function. Here we perform a POST request to the server using the axios module. Now this function looks like this:

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

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

To implement such a pattern of behavior, we call after axios.post() . then() . We are allowed to do this by responding to a client's POST request, we return a promise from the server, which can either be successfully resolved or rejected.

Let's add the function with the following code:

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

Here you can draw attention to the fact that a blob used as responseType . Before we move on - let's talk about what it is.

Blob Objects


A blob is an immutable object that represents some raw data. Such objects are often used to work with data that may not be represented in the native JavaScript format. Such objects are sequences of bytes that store, for example, file data. It may seem that the Blob object stores 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.

Completion of the project


Now that we know what Blob objects are, we can use another .then() call to res.data new Blob object based on res.data. When creating this object, we will pass to its constructor a parameter indicating that the data that will store the object is of type application/pdf . After that, we can use the saveAs() method that we imported from the file-saver module, and save the data to a file. As a result, the code of the createAndDowndloadPdf method will look as 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 interface of the application looks like in the browser.


Application in browser

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


Download PDF

And here is the PDF document itself.


Software PDF

Results


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

Dear readers! How would you approach the task of creating PDF files using Node.js?

Source: https://habr.com/ru/post/460018/


All Articles