El material, cuya traducción publicamos hoy, está dedicado a la creación de archivos PDF dinámicos utilizando el código HTML como plantilla. Es decir, hablaremos sobre cómo crear una factura simple para el pago de ciertos bienes o servicios, los datos dinámicos incluidos en los que se toman del estado de la aplicación React. La base de la aplicación React se creó utilizando create-react-app, la parte del servidor del proyecto se basa en Node.js y el marco Express se utilizó en su desarrollo.

El autor de este material señala que preparó un
video que demuestra el desarrollo del proyecto. Si decide ver el video y leer el artículo, se recomienda hacerlo. Primero, hojee el artículo, luego encienda el video y vuelva a crear el sistema que está considerando allí. Después de eso, solo lea el artículo.
Creación de proyectos
Crea un directorio de proyecto y ve a él:
mkdir pdfGenerator && cd pdfGenerator
Cree una nueva aplicación React:
create-react-app client
Después de completar la creación de la aplicación, vaya al directorio que acaba de crear e instale las dependencias:
cd client && npm i -S axios file-saver
Crea un servidor Express. Para hacer esto, cree la carpeta del
server
en el directorio del proyecto y vaya a ella. En él, cree el archivo
index.js
e inicie la inicialización del proyecto:
mkdir server && cd server && touch index.js && npm init
Aquí, para formar
package.json
, simplemente presione
Enter
varias veces. Después de eso, ejecute el siguiente comando para agregar las dependencias necesarias a la parte del servidor del proyecto:
npm i -S express body-parser cors html-pdf
Ahora, en el archivo
client/package.json
, arriba de la sección de descripción de dependencia, agregue lo siguiente:
"proxy": "http://localhost:5000/"
Esto ayudará a trabajar con el servidor local desde el código del cliente.
Ahora necesita abrir dos ventanas de la terminal.
En la primera ventana, vaya al directorio del
client
y ejecute el siguiente comando:
npm start
En la segunda ventana, vaya a la carpeta del
server
y ejecute el siguiente comando:
nodemon index.js
Configuración inicial del cliente
La parte del cliente de nuestro proyecto se verá muy simple.
Para comenzar, en el
src/App.js
parte del cliente de la aplicación, importamos las dependencias en el código:
import axios from 'axios'; import { saveAs } from 'file-saver';
Después de eso, en la parte superior de la descripción del componente, inicialice el estado:
state = { name: 'Adrian', receiptId: 0, price1: 0, price2: 0, }
Eliminemos el marcado JSX estándar creado en la plantilla de la aplicación utilizando
create-react-app
y devuelto desde el método
render()
. Inserte lo siguiente en él:
<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>
handleChange
método
handleChange
, que será responsable de actualizar los datos del estado de la aplicación relacionados con los campos de entrada:
handleChange = ({ target: { value, name }}) => this.setState({ [name]: value })
Ahora podemos pasar a la tarea de crear un archivo PDF. Esa parte, que se resuelve por medio del cliente, es crear una solicitud POST al servidor. La solicitud envía el estado de la aplicación:
createAndDownloadPdf = () => { axios.post('/create-pdf', this.state) }
Antes de continuar trabajando en el lado del cliente del proyecto, necesitamos configurar las rutas en el servidor. Esto permitirá que el servidor reciba datos del cliente, genere un archivo PDF y transfiera este archivo nuevamente al cliente.
Configuración inicial del servidor
La parte del servidor del proyecto incluirá solo dos rutas. Se necesita uno para crear archivos PDF. El segundo es para enviar archivos al cliente.
Primero, importe las dependencias en el archivo
index.js
:
const express = require('express'); const bodyParser = require('body-parser'); const pdf = require('html-pdf'); const cors = require('cors');
Inicializamos la aplicación Express y configuramos el puerto:
const app = express(); const port = process.env.PORT || 5000;
Configure el analizador de consultas. Lo que necesitamos estará disponible como
req.body
. También configuraremos CORS para que nuestro error de
Cross-Origin Request Blocked
no interfiera con nuestro trabajo:
app.use(cors()); app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json());
Después de eso, inicie el servidor:
app.listen(port, () => console.log(`Listening on port ${port}`));
Ahora podemos abordar el código responsable de crear los PDF.
Crear una plantilla HTML para PDF
Necesitamos una plantilla HTML para usar al crear archivos PDF. Al crear dicha plantilla, se abren infinitas posibilidades ante nosotros. Todo lo que se puede crear utilizando HTML y CSS puro se puede representar como un archivo PDF. Cree el directorio de
documents
en la carpeta del
server
, vaya a él y cree el archivo
index.js
en él:
mkdir documents && cd documents && touch index.js
Desde este archivo, exportamos una función de flecha que devolverá todo el código HTML necesario. Al llamar a esta función, puede usar los parámetros, que también describiremos aquí.
module.exports = ({ name, price1, price2, receiptId }) => { ... }
Aquí le daré
un ejemplo de una plantilla HTML, y usted simplemente la copiará en su proyecto. Pero usted, por supuesto, puede crear su propia plantilla.
Llevemos
index.js
código
index.js
de la carpeta
server/documents
al siguiente formulario:
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
este archivo en el archivo
server/index.js
:
const pdfTemplate = require('./documents');
Crear archivos PDF
Recordemos que en el servidor vamos a crear dos rutas. La ruta POST será responsable de recibir datos del cliente y crear un archivo PDF. La ruta GET se usará para enviar el archivo terminado al cliente.
▍ ruta create-pdf
En la ruta POST
create-pdf
, utilizaremos el
pdf.create()
, que se refiere al objeto importado del módulo
html-pdf
.
Como primer parámetro del método
pdf.create()
, se utiliza una plantilla HTML, así como los datos recibidos del cliente.
Para devolver
pdf.create()
, llamamos al método
toFile()
, pasándole el nombre que queremos asignar al documento PDF, así como la función de devolución de llamada de flecha. Esta función, en caso de error, ejecutará el
res.send(Promise.reject())
. En caso de que todo saliera bien, ella ejecutará el comando
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 Ruta
Al mismo tiempo, crearemos una ruta que se utilizará después de que, a pedido del cliente, se cree con éxito un archivo PDF. Aquí solo tomamos el documento terminado y lo enviamos al cliente usando
res.sendFile()
:
app.get('/fetch-pdf', (req, res) => { res.sendFile(`${__dirname}/result.pdf`) })
Función del cliente createAndDownloadPdf
Ahora podemos volver al código del cliente y continuar trabajando en la función
createAndDownloadPdf
. Aquí hacemos una solicitud POST al servidor utilizando el módulo
axios
. Ahora esta función se ve así:
createAndDownloadPdf = () => { axios.post('/create-pdf', this.state) }
Si después de ejecutar una solicitud POST al servidor se creó un documento PDF, necesitamos ejecutar una solicitud GET, en respuesta a lo cual el servidor enviará el documento terminado al cliente.
Para implementar este esquema de comportamiento, nosotros, después de llamar a
axios.post()
, llamamos.
then()
. Esto nos permite hacer eso en respuesta a la solicitud POST de un cliente, devolvemos una promesa del servidor que se puede resolver o rechazar con éxito.
Complementamos la función con el siguiente código:
.then(() => axios.get('/fetch-pdf', { responseType: 'blob' })) .then(( res) => {})
Aquí puede prestar atención al hecho de que
blob
usa como
responseType
. Antes de continuar, hablemos de lo que es.
Objetos blob
Blob es un objeto inmutable que representa algunos datos sin procesar. Dichos objetos a menudo se usan para trabajar con datos que pueden no estar en formato JavaScript nativo. Dichos objetos son secuencias de bytes que almacenan, por ejemplo, datos de archivos. Puede parecer que el objeto
Blob
está almacenando un enlace a un archivo, pero de hecho no lo está. Estos objetos almacenan datos con los que puede trabajar. Por ejemplo, se pueden guardar en archivos.
Proyecto terminado
Ahora que sabemos qué son los objetos
Blob
, podemos usar otra llamada
.then()
para
res.data
nuevo objeto
Blob
en él, basado en res.data. Al crear este objeto, pasaremos un parámetro a su constructor, indicando que los datos que almacenará el objeto son del tipo
application/pdf
. Después de eso, podemos usar el método
saveAs()
, que se importó desde el módulo
file-saver
, y guardar los datos en un archivo. Como resultado, el código del método
createAndDowndloadPdf
se verá como el que se muestra a continuación:
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'); }) }
Así es como se ve la interfaz del navegador.
Aplicación en navegadorDespués de completar los campos y hacer clic en el botón
Download PDF
, los datos se transfieren al servidor y se descarga un documento PDF.
Descargar PDFY aquí está el documento PDF en sí.
PDF generado por softwareResumen
Analizamos un mecanismo que le permite crear mediante programación archivos PDF.
Aquí hay un repositorio de GitHub con código de proyecto. Esperamos que las ideas que conoció en este material encuentren aplicación en su desarrollo.
Estimados lectores! ¿Cómo resolvería el problema de crear mediante programación archivos PDF utilizando Node.js?