DEV Community 👩‍💻👨‍💻

Gabriel Pinheiro
Gabriel Pinheiro

Posted on

Criando PDF com Node JS e React

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

Enter fullscreen mode Exit fullscreen mode

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,
};

Enter fullscreen mode Exit fullscreen mode

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}`));

Enter fullscreen mode Exit fullscreen mode

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>

Enter fullscreen mode Exit fullscreen mode

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"
  },
Enter fullscreen mode Exit fullscreen mode

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>
  );
}

Enter fullscreen mode Exit fullscreen mode

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>
);

Enter fullscreen mode Exit fullscreen mode

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;
  }
}

Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)

About Real-time

Join DEV and MongoDB to build a front-end application using MongoDB Atlas. Change streams to display live updates as your database changes for your entry in the DEV x MongoDB Atlas Hackathon 2022.

Join the Hackathon