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.