Formação BackEnd

Node.js + Express

Módulo 7: Desenvolvendo APIs com Node.js e Express

Prof. Tiago Segato

Sumário

Parte 1: A Base Teórica (Aula Introdutória)

  • Introdução ao Node.js
  • Configuração do Ambiente (IDE)
  • Frontend, Backend e APIs: A Grande Divisão
  • Framework Express.js
  • Roteamento e Endpoints
  • Ferramentas de Teste de API

Parte 2: Construindo a Primeira API (Tutorial Prático)

  • Criando um Gerenciador de Tarefas
  • Preparação do Ambiente
  • Criando o Servidor Básico
  • Implementando o CRUD (Create, Read, Update, Delete)
  • Testando a API com um Cliente HTTP

Parte 3: Módulos de Aprofundamento

  • Persistência de Dados com Banco de Dados e Sequelize
  • Middlewares, Validação e Tratamento de Erros
  • Autenticação de Usuário com Token JWT
  • Estudos de Caso e Desafios
  • Preparando para o Ambiente de Produção

Introdução ao Node.js

Até pouco tempo atrás, o JavaScript vivia exclusivamente dentro dos navegadores de internet (como Chrome, Firefox) para criar interatividade em páginas web (o frontend).

O que é Node.js?

Node.js é um ambiente de execução que permite rodar código JavaScript no lado do servidor (no backend).

Características do Node.js:

  • Não é uma nova linguagem de programação. É o mesmo JavaScript que você já conhece.
  • Não é um framework. É a plataforma sobre a qual construiremos nossas aplicações.
  • É rápido e eficiente. Foi construído sobre o motor V8 do Google Chrome.

Isso permitiu que desenvolvedores usassem uma única linguagem (JavaScript) tanto para o frontend quanto para o backend, simplificando o desenvolvimento.

Configuração do Ambiente (IDE)

Para escrever nosso código, usaremos um Editor de Código ou IDE (Ambiente de Desenvolvimento Integrado).

Recomendação: Visual Studio Code (VS Code)

  • É gratuito
  • Poderoso e leve
  • Possui um ecossistema gigante de extensões
  • Terminal integrado (Ctrl + ')

Terminal Integrado

O VS Code possui um terminal embutido para rodar comandos como:

npm install node index.js

Extensões Úteis

  • Node.js Extension Pack: Conjunto de extensões para desenvolvimento Node.js
  • REST Client: Para testar APIs diretamente no VS Code
  • Thunder Client: Alternativa ao Postman integrada ao VS Code

Frontend, Backend e APIs: A Grande Divisão

🍽️ Analogia: Restaurante

Imagine que uma aplicação web é um restaurante.

Frontend (O Salão)

  • É a parte com a qual o usuário interage diretamente
  • Telas, botões, formulários
  • É o que você vê
  • Tecnologias: HTML, CSS, JavaScript (React, Vue, Angular)

Backend (A Cozinha)

  • É a parte que funciona nos "bastidores"
  • Processa regras de negócio
  • Acessa o banco de dados
  • Cuida da segurança
  • É o cérebro da operação
  • Tecnologias: Node.js, Python, Java

API (O Garçom)

  • É o intermediário que permite a comunicação entre o Frontend e o Backend
  • API (Application Programming Interface) é um conjunto de regras e protocolos
  • Define como as duas partes devem se comunicar

Fluxo de Comunicação:

  1. Usuário clica em um botão (Frontend)
  2. Frontend faz uma requisição para a API
  3. API processa a requisição (Backend)
  4. Backend retorna os dados
  5. Frontend exibe os dados para o usuário

Framework Express.js: Não Reinvente a Roda

Node.js puro é muito "cru". Seria como construir um carro do zero, fabricando cada parafuso.

O que é o Express.js?

É um framework web minimalista e flexível para Node.js. Ele nos fornece um conjunto de ferramentas para construir APIs de forma muito mais rápida e organizada.

Funcionalidades do Express:

  • Gerenciamento de rotas
  • Middleware para processamento de requisições
  • Tratamento de requisições e respostas
  • Integração com template engines
  • Servir arquivos estáticos
  • Tratamento de erros

Vantagens do Express:

  • Simplicidade: Fácil de aprender e usar
  • Flexibilidade: Não impõe estruturas rígidas
  • Performance: Rápido e eficiente
  • Comunidade: Grande ecossistema de plugins
  • Documentação: Bem documentado
npm install express

Roteamento e Endpoints: Os Endereços da Nossa API

Uma rota é um "caminho" em nosso servidor que executa uma função específica. A combinação do método HTTP e do caminho forma o que chamamos de endpoint.

Métodos HTTP (Verbos)

A ação que queremos realizar:

  • GET: Para buscar dados
  • POST: Para criar um novo dado
  • PUT/PATCH: Para atualizar um dado existente
  • DELETE: Para remover um dado

Caminho (Path)

O endereço do recurso:

  • /tarefas
  • /usuarios
  • /produtos

Exemplos de Endpoints

GET /tarefas // Buscar todas as tarefas POST /tarefas // Criar uma nova tarefa GET /tarefas/1 // Buscar tarefa com ID 1 PUT /tarefas/1 // Atualizar tarefa com ID 1 DELETE /tarefas/1 // Deletar tarefa com ID 1

Padrão RESTful

Seguimos convenções para tornar nossa API previsível e fácil de usar.

Ferramentas de Teste de API

Enquanto o frontend não existe, como testamos nossa API? Usamos Clientes de API (API Clients).

Ferramentas Populares:

  • Postman: O mais conhecido e robusto
  • Insomnia: Uma alternativa mais leve e moderna
  • Thunder Client: Extensão do VS Code
  • REST Client: Extensão do VS Code
  • curl: Ferramenta de linha de comando

Funcionalidades:

  • Enviar requisições HTTP
  • Definir headers e parâmetros
  • Visualizar respostas
  • Salvar coleções de requisições
  • Automatizar testes

Exemplo com curl:

# GET - Buscar todas as tarefas curl http://localhost:3000/tarefas # POST - Criar nova tarefa curl -X POST \ http://localhost:3000/tarefas \ -H "Content-Type: application/json" \ -d '{"titulo": "Nova tarefa"}' # PUT - Atualizar tarefa curl -X PUT \ http://localhost:3000/tarefas/1 \ -H "Content-Type: application/json" \ -d '{"titulo": "Tarefa atualizada"}' # DELETE - Deletar tarefa curl -X DELETE \ http://localhost:3000/tarefas/1

Criando um Gerenciador de Tarefas

Vamos construir uma API para gerenciar uma lista de tarefas. Para simplificar, usaremos um array em memória como nosso "banco de dados" inicial.

Funcionalidades da API:

  • Listar todas as tarefas
  • Buscar uma tarefa específica por ID
  • Criar uma nova tarefa
  • Atualizar uma tarefa existente
  • Deletar uma tarefa

Estrutura de uma Tarefa:

{ "id": 1, "titulo": "Aprender Node.js", "concluida": false }

CRUD Operations

  • Create - Criar
  • Read - Ler
  • Update - Atualizar
  • Delete - Deletar

Preparação do Ambiente

Passo 1: Criar o Projeto

mkdir gerenciador-tarefas-api cd gerenciador-tarefas-api

Passo 2: Inicializar o Projeto Node.js

npm init -y

Este comando cria o arquivo package.json com as configurações padrão.

Passo 3: Instalar o Express

npm install express

Estrutura do Projeto:

gerenciador-tarefas-api/ ├── package.json ├── package-lock.json ├── node_modules/ └── index.js (vamos criar)

Dica:

O arquivo package.json contém as informações do projeto e suas dependências. O node_modules contém os pacotes instalados.

Criando o Servidor Básico

Crie um arquivo index.js com o seguinte código:

const express = require('express'); const app = express(); const PORT = 3000; // Middleware para o Express entender JSON app.use(express.json()); // Nosso "banco de dados" em memória let tarefas = [ { id: 1, titulo: "Aprender Node.js", concluida: false }, { id: 2, titulo: "Criar projeto com Express", concluida: true }, ]; app.get('/', (req, res) => { res.send('<h1>API de Tarefas está funcionando!</h1>'); }); // Implementação do CRUD virá aqui... app.listen(PORT, () => { console.log(`Servidor rodando na porta http://localhost:${PORT}`); });

Para executar:

node index.js

Acesse http://localhost:3000 no navegador para verificar se está funcionando.

Implementando o CRUD - READ

Listar todas as tarefas (GET /tarefas)

app.get('/tarefas', (req, res) => { res.json(tarefas); });

Obter uma tarefa por ID (GET /tarefas/:id)

app.get('/tarefas/:id', (req, res) => { const tarefa = tarefas.find(t => t.id === parseInt(req.params.id)); if (!tarefa) { return res.status(404).json({ mensagem: "Tarefa não encontrada!" }); } res.json(tarefa); });

Explicação:

  • req.params.id - Captura o parâmetro da URL
  • parseInt() - Converte string para número
  • find() - Busca o primeiro elemento que atende à condição
  • status(404) - Define o código de status HTTP

Implementando o CRUD - CREATE

Criar uma nova tarefa (POST /tarefas)

app.post('/tarefas', (req, res) => { const novaTarefa = { id: tarefas.length > 0 ? tarefas[tarefas.length - 1].id + 1 : 1, titulo: req.body.titulo, concluida: false }; tarefas.push(novaTarefa); res.status(201).json(novaTarefa); });

Explicação:

  • req.body - Dados enviados no corpo da requisição
  • Geramos um ID automaticamente (último ID + 1)
  • push() - Adiciona o elemento ao final do array
  • status(201) - Código para "Created" (Criado)

Exemplo de requisição:

POST /tarefas Content-Type: application/json { "titulo": "Estudar Express.js" }

Implementando o CRUD - UPDATE

Atualizar uma tarefa (PUT /tarefas/:id)

app.put('/tarefas/:id', (req, res) => { const indice = tarefas.findIndex(t => t.id === parseInt(req.params.id)); if (indice === -1) { return res.status(404).json({ mensagem: "Tarefa não encontrada!" }); } tarefas[indice].titulo = req.body.titulo || tarefas[indice].titulo; tarefas[indice].concluida = typeof req.body.concluida === 'boolean' ? req.body.concluida : tarefas[indice].concluida; res.json(tarefas[indice]); });

Explicação:

  • findIndex() - Retorna o índice do elemento encontrado
  • || - Operador OR: usa o valor existente se o novo for undefined
  • typeof - Verifica o tipo da variável
  • Permite atualização parcial dos campos

Implementando o CRUD - DELETE

Deletar uma tarefa (DELETE /tarefas/:id)

app.delete('/tarefas/:id', (req, res) => { const indice = tarefas.findIndex(t => t.id === parseInt(req.params.id)); if (indice === -1) { return res.status(404).json({ mensagem: "Tarefa não encontrada!" }); } tarefas.splice(indice, 1); res.status(204).send(); });

Explicação:

  • splice(indice, 1) - Remove 1 elemento na posição especificada
  • status(204) - Código para "No Content" (Sem Conteúdo)
  • send() - Envia resposta vazia

Resumo dos Status Codes:

  • 200: OK (sucesso geral)
  • 201: Created (recurso criado)
  • 204: No Content (sucesso sem conteúdo)
  • 404: Not Found (recurso não encontrado)
  • 500: Internal Server Error (erro do servidor)

Testando a API

Use o Postman, Insomnia ou Thunder Client para testar os endpoints que você criou.

Sequência de Testes:

  1. GET /tarefas - Listar todas as tarefas
  2. POST /tarefas - Criar uma nova tarefa
  3. GET /tarefas/1 - Buscar tarefa específica
  4. PUT /tarefas/1 - Atualizar tarefa
  5. DELETE /tarefas/1 - Deletar tarefa

Exemplo de Teste com Postman:

  1. Abra o Postman
  2. Crie uma nova requisição
  3. Selecione o método HTTP
  4. Digite a URL: http://localhost:3000/tarefas
  5. Para POST/PUT, adicione o JSON no Body
  6. Clique em "Send"

Exemplo de Body para POST:

{ "titulo": "Aprender MongoDB" }

Exemplo de Body para PUT:

{ "titulo": "Aprender MongoDB Avançado", "concluida": true }

Programação Assíncrona

Operações de banco de dados são assíncronas. O Node.js não espera por elas.

Callbacks (Jeito Antigo)

fs.readFile('arquivo.txt', (err, data) => { if (err) { console.error(err); } else { console.log(data); } });

Problema: "Callback Hell" - código aninhado e difícil de ler.

Promises (Melhor)

readFile('arquivo.txt') .then(data => console.log(data)) .catch(err => console.error(err));

Async/Await (Moderno)

async function lerArquivo() { try { const data = await readFile('arquivo.txt'); console.log(data); } catch (err) { console.error(err); } }

Vantagens do Async/Await:

  • Código mais limpo e legível
  • Parece código síncrono
  • Melhor tratamento de erros
  • Facilita debugging

Integrando Sequelize com SQLite

Passo 1: Instalar as dependências

npm install sequelize sqlite3 npm install --save-dev sequelize-cli

Passo 2: Inicializar o Sequelize

npx sequelize-cli init

Passo 3: Configurar a conexão (config/config.json)

{ "development": { "dialect": "sqlite", "storage": "database.sqlite" } }

Passo 4: Criar o Modelo e a Migration

npx sequelize-cli model:generate --name Tarefa --attributes titulo:string,concluida:boolean

Passo 5: Executar a Migration

npx sequelize-cli db:migrate

Refatorando o CRUD para usar Banco de Dados

Substitua as funções das rotas pelas versões async que usam os métodos do Sequelize.

// index.js const { Tarefa } = require('./models'); // GET /tarefas app.get('/tarefas', async (req, res) => { try { const tarefas = await Tarefa.findAll(); res.json(tarefas); } catch (error) { res.status(500).json({ mensagem: 'Erro interno do servidor' }); } }); // POST /tarefas app.post('/tarefas', async (req, res) => { try { const novaTarefa = await Tarefa.create(req.body); res.status(201).json(novaTarefa); } catch (error) { res.status(500).json({ mensagem: 'Erro interno do servidor' }); } }); // PUT /tarefas/:id app.put('/tarefas/:id', async (req, res) => { try { const [updated] = await Tarefa.update(req.body, { where: { id: req.params.id } }); if (updated) { const tarefaAtualizada = await Tarefa.findByPk(req.params.id); res.json(tarefaAtualizada); } else { res.status(404).json({ mensagem: 'Tarefa não encontrada' }); } } catch (error) { res.status(500).json({ mensagem: 'Erro interno do servidor' }); } }); // DELETE /tarefas/:id app.delete('/tarefas/:id', async (req, res) => { try { const deleted = await Tarefa.destroy({ where: { id: req.params.id } }); if (deleted) { res.status(204).send(); } else { res.status(404).json({ mensagem: 'Tarefa não encontrada' }); } } catch (error) { res.status(500).json({ mensagem: 'Erro interno do servidor' }); } });

Middlewares

Um Middleware é uma função que intercepta o ciclo de requisição-resposta.

Criando um Middleware de Log

// Em index.js const logMiddleware = (req, res, next) => { console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`); next(); // Passa para o próximo middleware }; app.use(logMiddleware);

Middleware de Tratamento de Erros

// Instalar express-async-errors npm install express-async-errors // No topo do index.js require('express-async-errors'); // No final do arquivo, antes de app.listen app.use((error, req, res, next) => { console.error(error.stack); res.status(500).json({ mensagem: "Ocorreu um erro interno no servidor." }); });

Fluxo do Middleware

🔄 Analogia: Linha de Produção

Cada middleware é como uma estação em uma linha de produção. A requisição passa por cada estação (middleware) até chegar ao destino final (rota).

Tipos de Middleware:

  • Application-level: app.use()
  • Router-level: router.use()
  • Error-handling: 4 parâmetros
  • Built-in: express.json()
  • Third-party: cors, helmet

Autenticação com JWT

Vamos proteger nossas rotas para que apenas usuários logados possam acessá-las.

Passo 1: Instalar as dependências

npm install jsonwebtoken bcryptjs

Passo 2: Criar o modelo Usuario

npx sequelize-cli model:generate --name Usuario --attributes email:string,senha:string npx sequelize-cli db:migrate

Rota de Registro

const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); app.post('/auth/registrar', async (req, res) => { try { const { email, senha } = req.body; const senhaHash = await bcrypt.hash(senha, 10); const usuario = await Usuario.create({ email, senha: senhaHash }); res.status(201).json({ mensagem: 'Usuário criado com sucesso', usuario: { id: usuario.id, email: usuario.email } }); } catch (error) { res.status(500).json({ mensagem: 'Erro interno' }); } });

Rota de Login

app.post('/auth/login', async (req, res) => { try { const { email, senha } = req.body; const usuario = await Usuario.findOne({ where: { email } }); if (!usuario) { return res.status(401).json({ mensagem: 'Credenciais inválidas' }); } const senhaValida = await bcrypt.compare(senha, usuario.senha); if (!senhaValida) { return res.status(401).json({ mensagem: 'Credenciais inválidas' }); } const token = jwt.sign( { id: usuario.id }, 'seu_segredo_jwt', { expiresIn: '1h' } ); res.json({ token }); } catch (error) { res.status(500).json({ mensagem: 'Erro interno' }); } });

Middleware de Autenticação

Criar um Middleware de Autenticação (authMiddleware.js)

const jwt = require('jsonwebtoken'); const authMiddleware = (req, res, next) => { const token = req.header('Authorization')?.replace('Bearer ', ''); if (!token) { return res.status(401).json({ mensagem: 'Token não fornecido' }); } try { const decoded = jwt.verify(token, 'seu_segredo_jwt'); req.usuarioId = decoded.id; next(); } catch (error) { res.status(401).json({ mensagem: 'Token inválido' }); } }; module.exports = authMiddleware;

Proteger as rotas

// index.js const authMiddleware = require('./authMiddleware'); // Protege todas as rotas de tarefas app.use('/tarefas', authMiddleware);

Como usar no cliente

// Header da requisição Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Desafios

Desafio 1 (Fácil): Associar Tarefas a Usuários

  • Adicione usuarioId ao modelo Tarefa
  • Crie a associação no Sequelize (Usuario.hasMany(Tarefa))
  • Refatore as rotas de tarefas para que um usuário só possa ver e manipular as suas próprias tarefas

Desafio 2 (Médio): API de um Blog

  • Crie modelos para Post e Comentario com as devidas associações
  • Implemente o CRUD completo para Posts (apenas o autor pode editar/deletar)
  • Permita que qualquer usuário logado possa criar comentários

Desafio 3 (Avançado): API de uma Biblioteca

  • Modelos: Livro, Usuario, Emprestimo
  • Implemente a lógica de negócio para não permitir o empréstimo de um livro já emprestado
  • Adicione funcionalidades como reserva de livros e histórico de empréstimos

CORS (Cross-Origin Resource Sharing)

Permite que seu frontend (em outro domínio) acesse sua API.

Instalação e Configuração

npm install cors
// Em index.js const cors = require('cors'); // Desenvolvimento - permite qualquer origem app.use(cors()); // Produção - restringe a origem app.use(cors({ origin: 'http://meu-frontend.com' }));

O que é CORS?

É uma política de segurança dos navegadores que bloqueia requisições entre domínios diferentes por padrão.

Exemplo de Erro CORS:

Access to fetch at 'http://localhost:3000/tarefas' from origin 'http://localhost:3001' has been blocked by CORS policy

Configurações Avançadas:

app.use(cors({ origin: ['http://localhost:3001', 'https://meuapp.com'], methods: ['GET', 'POST', 'PUT', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization'], credentials: true }));

Dica de Segurança:

Em produção, sempre especifique as origens permitidas. Nunca use * em produção!

Variáveis de Ambiente com .env

Nunca coloque dados sensíveis (senhas, segredos JWT) no código.

Passo 1: Instalar dotenv

npm install dotenv

Passo 2: Criar arquivo .env na raiz do projeto

PORT=3001 JWT_SECRET=meu_segredo_super_secreto DB_HOST=localhost DB_USER=usuario DB_PASS=senha123

Passo 3: Carregar as variáveis no index.js

// No topo do index.js require('dotenv').config(); // Usar as variáveis const PORT = process.env.PORT || 3000; const JWT_SECRET = process.env.JWT_SECRET;

Importante:

  • Adicione .env ao seu arquivo .gitignore
  • Crie um arquivo .env.example com as variáveis (sem valores)
  • Documente as variáveis necessárias no README

Deploy em Produção

SSL (HTTPS)

Criptografa a comunicação. Essencial em produção. Geralmente é gerenciado por um Reverse Proxy como o Nginx.

Deploy em VPS (Visão Geral):

  1. Configurar Servidor: Instale Node.js, Git, Nginx
  2. Transferir Código: Use Git para clonar seu projeto
  3. Gerenciador de Processos: Use o PM2
  4. Configurar Reverse Proxy (Nginx): Direcione o tráfego

Instalar PM2

npm install -g pm2 # Iniciar aplicação pm2 start index.js # Ver status pm2 status # Logs pm2 logs # Reiniciar pm2 restart index

Configuração Nginx Básica

server { listen 80; server_name meudominio.com; location / { proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } }

Conclusão

O que aprendemos:

  • Fundamentos do Node.js e Express
  • Criação de APIs RESTful
  • Operações CRUD com banco de dados
  • Programação assíncrona com async/await
  • Middlewares e tratamento de erros
  • Autenticação com JWT
  • Preparação para produção

Próximos Passos:

  • Pratique criando diferentes tipos de APIs
  • Explore outros bancos de dados (MongoDB, PostgreSQL)
  • Aprenda sobre testes automatizados
  • Estude arquiteturas mais avançadas (microserviços)
  • Implemente cache com Redis
  • Explore GraphQL como alternativa ao REST

Lembre-se:

A prática leva à perfeição. Continue construindo projetos e experimentando novas tecnologias!