Criando PDF com Node JS e React
Tags: react, node, express, pdf
Published: 2022-09-02 01:37:06

## Introdução
Talvez você já tenha escutado o termo "mala direta" envolvendo o Microsoft Word. Para quem não sabe, basicamente consiste em criar um documento padrão e deixar alguns campos que irão ocorrer algum tipo de alteração de forma automatizada. A ideia desse post é bem parecida. Com o auxílio do Node JS, mais precisamente com o framework **express** e as bibliotecas **html-pdf-node**, **ejs** e **React**, iremos construir, de forma prática, um gerador de PDF.

## Passo 1
O projeto seguirá a seguinte estrutura de arquivos. Primeiramente, crie somente a pasta "app". As demais serão criadas ao longo desse tutorial.

```
app/
├─ backend/
│  ├─ node_modules/
│  ├─ public/
│  ├─ package.json
│  ├─ package-lock.json
│  ├─ src/
│  │  ├─ example.ejs
│  │  ├─ index.js
│  │  ├─ services/
│  │  │  ├─ createPDF.js
│  ├─ .gitignore/
├─ frontend/
│  ├─ node_modules/
│  ├─ public/
│  ├─ package.json
│  ├─ package-lock.json
│  ├─ src/
│  │  ├─ App.jsx
│  │  ├─ index.css
│  │  ├─ main.jsx
│  ├─ .gitignore
│  ├─ index.html
│  ├─ vite.config.js

```

Iremos começar pelo Back-end, instalando alguns pacotes necessários para o projeto. Diante disso, crie a pasta "backend". Iniciaremos pelo arquivo "package.json", que conterá diversas informações importantes da nossa aplicação. Abra o terminal na pasta citada e execute o comando abaixo:

`npm init -y`

Em seguida, iremos instalar o restante dos pacotes que utilizaremos nesse projeto:

`npm i nodemon cors express ejs html-pdf-node`

De forma breve, irei explicar cada uma dessas instalações. Caso você já conheça, pule essa parte.

- nodemon: ajudará a iniciar o nosso servidor Node JS no processo de desenvolvimento. O seu grande diferencial é que ele reinicia automaticamente o nosso servidor a medida que alterações ou erros sejam realizados.
- cors: utilizado para adicionar cabeçalhos HTTP que permite que aplicações de origens diferentes comuniquem entre si.
- express: possibilitará criar um servidor onde será construído as nossas APIs.
- ejs: ajudará a construír páginas HTML com javascript de forma direta.
- html-pdf-node: ajudará a converter o arquivo html em pdf.
 
Agora, para o Front-end, utilizaremos o React JS com o auxílio do ViteJS. Para isso, é necessário rodar o seguinte comando dentro da pasta "app". Após rodá-lo, algumas opções de instalações irão aparecer, selecione "React" e depois "React" novamente.

`npm create vite@latest frontend --template react`

Rode os dois comandos abaixo para instalar as dependências do Front-end.

`cd frontend`
`npm install`

## Passo 2
Realizado o passo 1, iremos dar início ao desenvolvimento da lógica na pasta services com a criação do arquivo createPDF.js, seguindo a estrutura abordada no início.
O código abaixo irá resumir, através de comentários, os motivos e o que está sendo realizado em cada parte:

```
const html_to_pdf = require("html-pdf-node");
const ejs = require("ejs");

async function createPDF(file, ejsVariables) {
  let html;
  // A constante url irá ajudar caso a gente queira utilizar imagens que estão disponíveis na pasta public do backend no nosso arquivo ejs
  const url = process.env.URL || "http://localhost:3001";

  ejs.renderFile(
    // Nome do arquivo ejs que será transformado
    file,
    // Objeto contendo as váriaveis para substituir no arquivo ".ejs"
    { ...ejsVariables, url },
    (err, content) => {
      if (err) {
        throw new Error(
          "Can't render the file, probably some variables are missing"
        );
      }
      // Vamos armazenar o html após a conversão na variável html
      html = { content };
    }
  );

  // Algumas opções para o pdf que irá ser gerado. Caso precise de algo mais específico, favor consultar a documentação: https://github.com/mrafiqk/html-pdf-node
  const options = { format: "A4" };

  // Aqui será gerado o buffer do nosso pdf
  const pdfBuffer = await html_to_pdf.generatePdf(html, options);

  return pdfBuffer;
}

module.exports = {
  createPDF,
};

```

Após isso, precisaremos criar uma instância do nosso servidor e uma API para enviar esse documento para o usuário. Para isso, bastar copiar o conteúdo abaixo e colar no arquivo "index.js", dentro da pasta "src":

```
const express = require("express");
const cors = require("cors");
const { createPDF } = require("./services/createPDF");
const path = require("path");

const PORT = process.env.PORT || 3001;

const app = express();

app.use(cors());
app.use(express.json());
// Pasta onde ficarão os arquivos estáticos, como fotos
app.use(express.static("public"));

app.post("/generate-pdf", async (req, res) => {
  const FILE_PATH = path.join(__dirname, "/example.ejs");
  const pdfBuffer = await createPDF(FILE_PATH, { ...req.body });

  // Iremos setar o header com o tipo de arquivo enviado
  res.setHeader("Content-Type", "application/pdf");
  // Por fim, enviar o arquivo
  res.end(pdfBuffer);
});

app.listen(PORT, () => console.log(`Server is running on port ${PORT}`));

```

## Passo 3
A partir desse momento, podemos desenvolver o nosso arquivo "example.ejs" que conterá a estrutura HTML e as variáveis que serão preenchidas automaticamente. Vale ressaltar que essa etapa demandará que você faça testes à medida que for desenvolvendo a página, pois nem sempre o PDF será originado igual ao HTML. 
Outro ponto importante é que as variáveis utilizadas dentro do arquivo devem sempre ser enviadas para o Back-end. Caso tenha algum campo facultativo, enviar essa propriedade com valor "null" ou algo similar.

```
<!-- example.ejs -->
<!DOCTYPE html>
<html lang="pt">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>PDF Generator</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }
      h1,
      h3,
      p {
        margin: 10px 0 10px 10px;
      }
    </style>
  </head>
  <body>
    <img style="width: 100%" src="<%= url %>/images.png" />
    <h1>Dono do documento: <%= name %></h1>
    <h3>Objetivo do documento:</h3>
    <p style="font-size: 24px">Esse documento tem como objetivo gerar um PDF</p>
    <% if (references) { %>
    <h3>Referências:</h3>
    <p><%= references.join(", ") + '.' %></p>
    <% } %>
  </body>
</html>

```
Agora o nosso Back-end está quase finalizado. Ainda precisamos configurar o nodemon para iniciar o nosso servidor. Para isso, adicione a linha abaixo na parte de scripts do package.json.

```
  "scripts": {
    "dev": "nodemon src/index.js"
  },
```

Caso queira testar, você já pode iniciar o servidor com o comando abaixo dentro da pasta "backend":

`npm run dev`

Uma mensagem como essa deve aparecer: "Server is running on port 3001". Caso algum erro aconteça, favor retornar aos passos anteriores e também conferir a estrutura dos arquivos.

## Passo 4

Nesse momento iremos montar uma estrutura simples na pasta "frontend" para realizarmos a requisição. Basta copiar e colar os códigos abaixo nos arquivos corretos (pode substituir os arquivos que já estavam presentes).

No arquivo "App.jsx":

```
export default function App() {
  // Informações utilizadas nas variáveis dos arquivos .ejs
  const data = {
    name: "SEU NOME",
    references: ["https://ejs.co/", "https://github.com/mrafiqk/html-pdf-node"],
  };

  const generatePdf = (e) => {
    e.preventDefault();

    fetch("http://localhost:3001/generate-pdf", {
      headers: {
        "Content-type": "application/json; charset=UTF-8",
      },
      method: "POST",
      body: JSON.stringify(data),
    })
      .then((res) => res.blob())
      .then((blob) => {
        // Criando uma url de acesso as informações
        const url = window.URL.createObjectURL(blob);
        const a = document.createElement("a");
        a.href = url;
        a.download = "example.pdf";
        // Necessário para funcionar em todos os browsers
        document.body.appendChild(a);
        a.click();
        a.remove();
      });
  };

  return (
    <div className="App">
      <form onSubmit={generatePdf}>
        <button type="submit">Download Here!</button>
      </form>
    </div>
  );
}

```
No arquivo "main.tsx"

```
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

```
No arquivo index.css

```

:root {
  font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
  font-size: 16px;
  line-height: 24px;
  font-weight: 400;
}

body {
  margin: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  min-width: 100vw;
  min-height: 100vh;
  background-color: #8187f5;
}

button {
  border-radius: 8px;
  border: 1px solid transparent;
  padding: 0.6em 1.2em;
  font-size: 1em;
  font-weight: 500;
  font-family: inherit;
  background-color: #1a1a1a;
  cursor: pointer;
  transition: border-color 0.25s;
}
button:hover {
  border-color: #646cff;
}
button:focus,
button:focus-visible {
  outline: 4px auto -webkit-focus-ring-color;
}

@media (prefers-color-scheme: light) {
  :root {
    color: #213547;
    background-color: #ffffff;
  }
  a:hover {
    color: #747bff;
  }
  button {
    background-color: #f9f9f9;
  }
}

```
Pronto! A partir desse momento, você pode iniciar o seu Front-end com o comando abaixo e também o seu Back-end, caso não tenha rodado nos passos anteriores. Acesse o browser com a URL gerada no seu terminal após executar o comando. Você irá observar um botão de download e, ao apertá-lo, um arquivo PDF será baixado. 
Uma observação é que a imagem não irá aparecer, pois você não a possui em sua máquina. Irei deixar o código da aplicação no fim desse post para que você possa pegá-lo na pasta backend/public e salvar no mesmo local no seu código, caso queira testar.

`npm run dev`

Por fim, espero ter contribuído para que você consiga gerar seus arquivos PDFs utilizando dados presentes nas suas aplicações. Vale ressaltar que existem várias soluções para esse problema e cabe a você avaliar qual irá atender melhor as necessidades do projeto que esteja fazendo. Caso perceba algum ponto de melhoria, tenha alguma dúvida ou queira contribuir com mais informações a respeito, basta deixar nos comentários!

Código Git Hub: [Link](https://github.com/GabrielFMPinheiro/PDFGenerator)