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
Top comments (0)