O material, cuja tradução publicamos hoje, é dedicado à criação de arquivos PDF dinâmicos usando o código HTML como modelo. Nomeadamente, falaremos sobre como criar uma fatura simples para pagamento de certos bens ou serviços, cujos dados dinâmicos incluídos são retirados do estado do aplicativo React. A base do aplicativo React foi criada usando create-react-app, a parte do servidor do projeto é baseada no Node.js e a estrutura Express foi usada em seu desenvolvimento.

O autor deste material observa que ele preparou um
vídeo que demonstra o desenvolvimento do projeto. Se você decidir assistir ao vídeo e ler o artigo, é recomendável fazê-lo. Primeiro, percorra o artigo, depois ligue o vídeo e recrie o sistema que você está pensando lá. Depois disso, basta ler o artigo.
Criação de projeto
Crie um diretório de projeto e acesse:
mkdir pdfGenerator && cd pdfGenerator
Crie um novo aplicativo React:
create-react-app client
Após concluir a criação do aplicativo, vá para o diretório que você acabou de criar e instale as dependências:
cd client && npm i -S axios file-saver
Crie um servidor Express. Para fazer isso, crie a pasta do
server
no diretório do projeto e vá para ele. Nele, crie o arquivo
index.js
e inicie a inicialização do projeto:
mkdir server && cd server && touch index.js && npm init
Aqui, para formar
package.json
, basta pressionar
Enter
várias vezes. Depois disso, execute o seguinte comando para adicionar as dependências necessárias à parte do servidor do projeto:
npm i -S express body-parser cors html-pdf
Agora, no arquivo
client/package.json
, acima da seção de descrição da dependência, adicione o seguinte:
"proxy": "http://localhost:5000/"
Isso ajudará no trabalho com o servidor local a partir do código do cliente.
Agora você precisa abrir duas janelas do terminal.
Na primeira janela, vá para o diretório do
client
e execute o seguinte comando:
npm start
Na segunda janela, vá para a pasta do
server
e execute o seguinte comando:
nodemon index.js
Configuração inicial do cliente
A parte do cliente do nosso projeto parecerá muito simples.
Para começar, no
src/App.js
parte do cliente do aplicativo, importamos as dependências para o código:
import axios from 'axios'; import { saveAs } from 'file-saver';
Depois disso, na parte superior da descrição do componente, inicialize o estado:
state = { name: 'Adrian', receiptId: 0, price1: 0, price2: 0, }
Vamos remover a marcação JSX padrão criada no modelo de aplicativo usando o
create-react-app
e retornada do método
render()
. Insira o seguinte:
<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>
Vamos criar o método
handleChange
, que será responsável por atualizar os dados do estado do aplicativo relacionados aos campos de entrada:
handleChange = ({ target: { value, name }}) => this.setState({ [name]: value })
Agora podemos passar à tarefa de criar um arquivo PDF. Essa parte, resolvida por meio do cliente, é criar uma solicitação POST para o servidor. A solicitação envia o estado do aplicativo:
createAndDownloadPdf = () => { axios.post('/create-pdf', this.state) }
Antes de continuarmos trabalhando no lado do cliente do projeto, precisamos configurar as rotas no servidor. Isso permitirá que o servidor receba dados do cliente, gere um arquivo PDF e transfira esse arquivo de volta para o cliente.
Configuração inicial do servidor
A parte do servidor do projeto incluirá apenas duas rotas. É necessário um para criar PDFs. O segundo é para enviar arquivos para o cliente.
Primeiro, importe as dependências para o arquivo
index.js
:
const express = require('express'); const bodyParser = require('body-parser'); const pdf = require('html-pdf'); const cors = require('cors');
Inicializamos o aplicativo Express e configuramos a porta:
const app = express(); const port = process.env.PORT || 5000;
Configure o analisador de consultas. O que precisamos estará disponível como
req.body
. Também configuraremos o CORS para que nosso erro de
Cross-Origin Request Blocked
não interfira em nosso trabalho:
app.use(cors()); app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json());
Depois disso, inicie o servidor:
app.listen(port, () => console.log(`Listening on port ${port}`));
Agora podemos resolver o código responsável pela criação dos PDFs.
Crie um modelo HTML para PDFs
Precisamos de um modelo HTML para usar ao criar arquivos PDF. Ao criar esse modelo, infinitas possibilidades se abrem diante de nós. Tudo o que pode ser criado usando HTML e CSS puro pode ser representado como um arquivo PDF. Crie o diretório de
documents
na pasta do
server
, acesse-o e crie o arquivo
index.js
:
mkdir documents && cd documents && touch index.js
A partir desse arquivo, exportamos uma função de seta que retornará todo o código HTML necessário. Ao chamar esta função, você pode usar os parâmetros, que também descreveremos aqui.
module.exports = ({ name, price1, price2, receiptId }) => { ... }
Aqui vou dar
um exemplo de um modelo HTML, e você apenas o copia no seu projeto. Mas você, é claro, pode criar seu próprio modelo.
Vamos
index.js
código
index.js
da pasta
server/documents
para o seguinte formato:
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
esse arquivo no arquivo
server/index.js
:
const pdfTemplate = require('./documents');
Crie PDFs
Lembre-se de que no servidor vamos criar duas rotas. A rota POST será responsável por receber dados do cliente e criar um arquivo PDF. A rota GET será usada para enviar o arquivo finalizado para o cliente.
▍ rota create-pdf
Na rota
create-pdf
POST, usaremos o comando
pdf.create()
, referindo-se ao objeto importado do módulo
html-pdf
.
Como o primeiro parâmetro do método
pdf.create()
, um modelo HTML é usado, além de dados recebidos do cliente.
Para retornar
pdf.create()
, chamamos o método
toFile()
, passando o nome que queremos atribuir ao documento PDF, bem como a função de retorno de seta. Esta função, em caso de erro, executará o
res.send(Promise.reject())
. Caso tudo
res.send(Promise.resolve())
bem, ela executará o 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()); }); });
Rota etchFetch-pdf
Ao mesmo tempo, criaremos uma rota que será usada depois que, a pedido do cliente, um arquivo PDF for criado com sucesso. Aqui, apenas pegamos o documento final e o enviamos ao cliente usando
res.sendFile()
:
app.get('/fetch-pdf', (req, res) => { res.sendFile(`${__dirname}/result.pdf`) })
Função cliente createAndDownloadPdf
Agora podemos retornar ao código do cliente e continuar trabalhando na função
createAndDownloadPdf
. Aqui, fazemos uma solicitação POST ao servidor usando o módulo
axios
. Agora, esta função se parece com isso:
createAndDownloadPdf = () => { axios.post('/create-pdf', this.state) }
Se, após executar uma solicitação POST para o servidor, um documento PDF foi criado, precisamos executar uma solicitação GET, em resposta à qual o servidor enviará o documento final ao cliente.
Para implementar esse esquema de comportamento, chamamos
axios.post()
.
then()
. Isso nos permite fazer isso em resposta à solicitação POST de um cliente, retornamos uma promessa do servidor que pode ser resolvida ou rejeitada com êxito.
Nós suplementamos a função com o seguinte código:
.then(() => axios.get('/fetch-pdf', { responseType: 'blob' })) .then(( res) => {})
Aqui você pode prestar atenção ao fato de que o
blob
usado como
responseType
. Antes de prosseguirmos, vamos falar sobre o que é.
Objetos de blob
Blob é um objeto imutável que representa alguns dados brutos. Esses objetos geralmente são usados para trabalhar com dados que podem não estar no formato JavaScript nativo. Esses objetos são sequências de bytes que armazenam, por exemplo, dados do arquivo. Pode parecer que o objeto
Blob
esteja armazenando um link para um arquivo, mas na verdade não está. Esses objetos armazenam dados com os quais você pode trabalhar. Por exemplo - eles podem ser salvos em arquivos.
Conclusão do Projeto
Agora que sabemos o que são os objetos
Blob
, podemos usar outra chamada
.then()
para
res.data
novo objeto
Blob
, com base em res.data. Ao criar este objeto, passaremos um parâmetro para seu construtor, indicando que os dados que o objeto armazenará são do tipo
application/pdf
. Depois disso, podemos usar o método
saveAs()
, que foi importado do módulo de
file-saver
, e salvar os dados em um arquivo. Como resultado, o código do método
createAndDowndloadPdf
será semelhante ao mostrado abaixo:
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'); }) }
É assim que a interface do navegador se parece.
Aplicativo no navegadorApós preencher os campos e clicar no botão
Download PDF
, os dados são transferidos para o servidor e um documento PDF é baixado dele.
Baixar PDFE aqui está o próprio documento PDF.
PDF gerado por softwareSumário
Analisamos um mecanismo que permite criar programaticamente arquivos PDF.
Aqui está um repositório GitHub com código do projeto. Esperamos que as idéias que você conheceu neste material sejam úteis em seu desenvolvimento.
Caros leitores! Como você resolveria o problema de criar programaticamente arquivos PDF usando o Node.js.