Iniciante 3 horas

Aula 4: Rotas e Middleware

Aprenda a organizar rotas em módulos, implementar middleware personalizado e tratar parâmetros de rota e query

🎯 Objetivos da Aula

  • Organizar rotas em módulos
  • Implementar middleware personalizado
  • Tratar parâmetros de rota e query

1. Introdução

Imagine que você está organizando uma empresa que cresceu muito rapidamente. No início, você fazia tudo sozinho em uma sala pequena. Mas agora, com mais funcionários e mais responsabilidades, você precisa criar departamentos específicos e regras claras.

É exatamente isso que acontece com aplicações web! Quando começamos, tudo fica no mesmo arquivo, mas conforme cresce, precisamos organizar melhor. Hoje você vai aprender a criar "departamentos" (rotas organizadas) e "porteiros" (middleware) para sua aplicação.

🎯 O que você vai praticar hoje:

  • Organizar sua API como departamentos de uma empresa
  • Criar "porteiros" que verificam quem pode entrar onde
  • Trabalhar com "endereços" dinâmicos na sua aplicação
  • Implementar controle de qualidade automático
📖 EXEMPLO

2. Express Router - Organizando Departamentos

🏢 Analogia: Departamentos de uma Empresa

Imagine uma empresa que começou pequena, com o dono fazendo tudo: vendas, contabilidade, recursos humanos, tudo na mesma mesa. Conforme a empresa cresce, ela precisa criar departamentos específicos:

  • Departamento de Vendas: Cuida apenas de vendas e clientes
  • Departamento de RH: Gerencia funcionários e contratações
  • Departamento Financeiro: Controla dinheiro e pagamentos

Cada departamento tem suas próprias regras, mas todos fazem parte da mesma empresa!

💡 Como isso funciona no Express

O Express Router é como criar departamentos na sua aplicação. Cada "departamento" (router) cuida de um tipo específico de funcionalidade, mas todos fazem parte da mesma aplicação.

Isso torna seu código mais organizado, fácil de manter e permite que diferentes pessoas trabalhem em departamentos diferentes sem conflitos.

📝 Exemplo Simples:

// Criando um "departamento" de produtos
const express = require('express');
const router = express.Router();

// Este departamento cuida apenas de produtos
router.get('/', (req, res) => {
    res.json({ message: 'Lista de produtos' });
});

router.post('/', (req, res) => {
    res.json({ message: 'Produto criado' });
});

module.exports = router;
💻 EXERCÍCIO PRÁTICO

🚀 Exercício Prático: Sistema de Lanchonete

Cenário:

Você vai criar uma API para uma lanchonete. A lanchonete tem 3 departamentos: Cardápio, Pedidos e Clientes.

Passo a Passo:

  1. Crie uma pasta chamada routes
  2. Crie 3 arquivos: cardapio.js, pedidos.js, clientes.js
  3. Em cada arquivo, crie um router com pelo menos 2 rotas
  4. No app.js, importe e use todos os routers

Dica de Estrutura:

lanchonete/
├── app.js
└── routes/
    ├── cardapio.js    # GET /cardapio, POST /cardapio
    ├── pedidos.js     # GET /pedidos, POST /pedidos
    └── clientes.js    # GET /clientes, POST /clientes

3. Organização de Rotas

Uma boa organização de rotas é fundamental para manter o código limpo e escalável. Vamos ver diferentes padrões de organização.

Estrutura Recomendada:

projeto/
├── app.js
├── routes/
│   ├── index.js          # Rotas principais
│   ├── usuarios.js       # Rotas de usuários
│   ├── produtos.js       # Rotas de produtos
│   └── auth.js          # Rotas de autenticação
├── controllers/
│   ├── usuarioController.js
│   └── produtoController.js
├── middleware/
│   ├── auth.js
│   └── validation.js
└── utils/
    └── helpers.js

routes/index.js - Router Principal

const express = require('express');
const router = express.Router();

// Importar outros routers
const usuariosRouter = require('./usuarios');
const produtosRouter = require('./produtos');
const authRouter = require('./auth');

// Rota principal
router.get('/', (req, res) => {
    res.json({
        message: 'API da Padaria Doce Sabor',
        version: '1.0.0',
        endpoints: {
            usuarios: '/api/usuarios',
            produtos: '/api/produtos',
            auth: '/api/auth'
        }
    });
});

// Usar sub-routers
router.use('/usuarios', usuariosRouter);
router.use('/produtos', produtosRouter);
router.use('/auth', authRouter);

module.exports = router;

routes/produtos.js - Router de Produtos

const express = require('express');
const router = express.Router();
const produtoController = require('../controllers/produtoController');
const { validarProduto } = require('../middleware/validation');

// GET /api/produtos - Listar todos os produtos
router.get('/', produtoController.listarTodos);

// GET /api/produtos/:id - Buscar produto por ID
router.get('/:id', produtoController.buscarPorId);

// POST /api/produtos - Criar novo produto
router.post('/', validarProduto, produtoController.criar);

// PUT /api/produtos/:id - Atualizar produto
router.put('/:id', validarProduto, produtoController.atualizar);

// DELETE /api/produtos/:id - Deletar produto
router.delete('/:id', produtoController.deletar);

// GET /api/produtos/categoria/:categoria - Filtrar por categoria
router.get('/categoria/:categoria', produtoController.buscarPorCategoria);

module.exports = router;

controllers/produtoController.js - Controller

// Simulando um banco de dados em memória
let produtos = [
    { id: 1, nome: 'Pão Francês', preco: 0.50, categoria: 'paes' },
    { id: 2, nome: 'Croissant', preco: 3.50, categoria: 'doces' },
    { id: 3, nome: 'Coxinha', preco: 4.00, categoria: 'salgados' }
];

const produtoController = {
    listarTodos: (req, res) => {
        const { categoria, preco_min, preco_max } = req.query;
        let resultado = produtos;
        
        // Filtrar por categoria
        if (categoria) {
            resultado = resultado.filter(p => p.categoria === categoria);
        }
        
        // Filtrar por preço
        if (preco_min) {
            resultado = resultado.filter(p => p.preco >= parseFloat(preco_min));
        }
        if (preco_max) {
            resultado = resultado.filter(p => p.preco <= parseFloat(preco_max));
        }
        
        res.json({
            produtos: resultado,
            total: resultado.length
        });
    },
    
    buscarPorId: (req, res) => {
        const id = parseInt(req.params.id);
        const produto = produtos.find(p => p.id === id);
        
        if (!produto) {
            return res.status(404).json({ error: 'Produto não encontrado' });
        }
        
        res.json(produto);
    },
    
    criar: (req, res) => {
        const { nome, preco, categoria } = req.body;
        const novoProduto = {
            id: produtos.length + 1,
            nome,
            preco: parseFloat(preco),
            categoria
        };
        
        produtos.push(novoProduto);
        res.status(201).json(novoProduto);
    },
    
    atualizar: (req, res) => {
        const id = parseInt(req.params.id);
        const index = produtos.findIndex(p => p.id === id);
        
        if (index === -1) {
            return res.status(404).json({ error: 'Produto não encontrado' });
        }
        
        produtos[index] = { ...produtos[index], ...req.body };
        res.json(produtos[index]);
    },
    
    deletar: (req, res) => {
        const id = parseInt(req.params.id);
        const index = produtos.findIndex(p => p.id === id);
        
        if (index === -1) {
            return res.status(404).json({ error: 'Produto não encontrado' });
        }
        
        produtos.splice(index, 1);
        res.status(204).send();
    },
    
    buscarPorCategoria: (req, res) => {
        const categoria = req.params.categoria;
        const resultado = produtos.filter(p => p.categoria === categoria);
        
        res.json({
            categoria,
            produtos: resultado,
            total: resultado.length
        });
    }
};

module.exports = produtoController;

4. Parâmetros de Rota - Endereços Dinâmicos

🏠 Analogia: Sistema de Endereços

Imagine que você é um entregador e precisa encontrar casas em uma cidade. Existem diferentes tipos de "endereços" que você pode receber:

  • Endereço Fixo: "Rua das Flores, 123" - sempre o mesmo lugar
  • Endereço Dinâmico: "Rua das Flores, casa do João" - muda dependendo da pessoa
  • Instruções Extras: "Portão azul, toque 2 vezes" - informações adicionais

Na web, temos a mesma coisa! Algumas URLs são fixas, outras mudam dependendo do que queremos buscar, e podemos passar "instruções extras" também.

💡 Como isso funciona no Express

Parâmetros de Rota são como "variáveis" no endereço da URL. Eles permitem que uma única rota atenda diferentes requisições:

  • /usuario/123 - busca o usuário com ID 123
  • /usuario/456 - busca o usuário com ID 456
  • /produtos?categoria=doces - filtra produtos doces
  • /produtos?categoria=salgados&preco=5 - filtra por categoria e preço

📝 Exemplo Simples:

// Rota com parâmetro dinâmico
app.get('/cliente/:nome', (req, res) => {
    const nomeCliente = req.params.nome;
    res.json({ mensagem: `Olá, ${nomeCliente}!` });
});

// Rota com filtros opcionais
app.get('/produtos', (req, res) => {
    const categoria = req.query.categoria || 'todos';
    res.json({ categoria, produtos: [] });
});

🚀 Exercício Prático: Sistema de Delivery

Cenário:

Você vai criar um sistema de delivery que precisa de diferentes tipos de "endereços":

  • Cliente específico: /cliente/:id
  • Pedido específico: /pedido/:numero
  • Busca com filtros: /restaurantes?bairro=centro&tipo=pizza

Desafio Prático:

  1. Crie uma rota que busca cliente por ID: /cliente/:id
  2. Crie uma rota que busca pedidos de um cliente: /cliente/:id/pedidos
  3. Crie uma rota com filtros: /restaurantes?bairro=X&tipo=Y&preco_max=Z
  4. Adicione validação: ID deve ser número, preço deve ser positivo

Dica de URLs para testar:

GET /cliente/123
GET /cliente/123/pedidos
GET /restaurantes?bairro=centro
GET /restaurantes?bairro=centro&tipo=pizza&preco_max=30
📖 EXEMPLO

3. Middleware - Os Porteiros da Aplicação

🛡️ Analogia: Porteiros e Seguranças

Imagine um prédio comercial com vários andares e empresas. Antes de qualquer pessoa chegar ao seu destino final, ela precisa passar por diferentes "checkpoints":

  • Porteiro da Entrada: Verifica se a pessoa pode entrar no prédio
  • Segurança do Elevador: Anota quem subiu, quando e para onde
  • Recepcionista do Andar: Verifica se a pessoa tem agendamento
  • Secretária da Empresa: Confirma se a reunião existe

Cada "porteiro" tem uma função específica, mas todos trabalham em sequência para garantir que apenas pessoas autorizadas cheguem ao destino certo!

💡 Como isso funciona no Express

Middleware são como "porteiros digitais" que interceptam todas as requisições antes delas chegarem ao destino final. Cada middleware pode:

  • • Verificar se o usuário está autenticado
  • • Registrar logs de quem acessou o que
  • • Bloquear requisições suspeitas
  • • Modificar dados antes de processar
  • • Decidir se a requisição pode continuar ou deve parar

📝 Exemplo Simples:

// Um "porteiro" que registra todas as visitas
const porteiro = (req, res, next) => {
    console.log(`${new Date().toLocaleTimeString()} - Alguém acessou: ${req.url}`);
    next(); // Libera a pessoa para continuar
};

// Usar o porteiro em todas as rotas
app.use(porteiro);
💻 EXERCÍCIO PRÁTICO

🚀 Exercício Prático: Sistema de Segurança da Biblioteca

Cenário:

Você vai criar um sistema de segurança para uma biblioteca digital. Precisa de 3 tipos de "porteiros":

  • Logger: Registra quem acessou o que e quando
  • Autenticador: Verifica se a pessoa tem carteirinha
  • Limitador: Impede que alguém faça muitas requisições

Desafio Prático:

  1. Crie um middleware que registra: hora, método, URL e IP
  2. Crie um middleware que verifica se existe um header "carteirinha"
  3. Crie um middleware que bloqueia mais de 5 requisições por minuto
  4. Aplique todos os middlewares na rota /livros

Dica de Implementação:

// Estrutura sugerida
middleware/
├── logger.js      # Registra acessos
├── auth.js        # Verifica carteirinha
└── rateLimit.js   # Limita requisições

// No app.js
app.use('/livros', logger, auth, rateLimit, livrosRouter);

5. Validação de Dados - Controle de Qualidade

🏭 Analogia: Controle de Qualidade em uma Fábrica

Imagine uma fábrica de brinquedos. Antes de qualquer produto sair da linha de produção e chegar às lojas, ele precisa passar por rigorosos testes de qualidade:

  • Inspetor de Materiais: Verifica se as peças estão completas
  • Teste de Segurança: Confirma se não há partes perigosas
  • Controle de Medidas: Verifica se o tamanho está correto
  • Teste Final: Confirma se tudo funciona como deveria

Se algum teste falhar, o produto volta para correção. Só produtos aprovados em TODOS os testes podem seguir adiante!

💡 Como isso funciona no Express

Validação de Dados é como ter inspetores de qualidade na sua API. Antes de processar qualquer informação, você verifica se ela está correta:

  • • Campos obrigatórios estão presentes?
  • • Os tipos de dados estão corretos?
  • • Os valores estão dentro dos limites esperados?
  • • O formato está adequado (email, telefone, etc.)?

Se algo estiver errado, você rejeita os dados e explica o que precisa ser corrigido!

📝 Exemplo Simples:

// Inspetor de qualidade para cadastro de usuário
const validarUsuario = (req, res, next) => {
    const { nome, email, idade } = req.body;
    const erros = [];
    
    if (!nome || nome.length < 2) {
        erros.push('Nome deve ter pelo menos 2 caracteres');
    }
    
    if (!email || !email.includes('@')) {
        erros.push('Email deve ser válido');
    }
    
    if (!idade || idade < 18) {
        erros.push('Idade deve ser maior que 18 anos');
    }
    
    if (erros.length > 0) {
        return res.status(400).json({ erros });
    }
    
    next(); // Aprovado no controle de qualidade!
};
💻 EXERCÍCIO PRÁTICO

🚀 Exercício Prático: Sistema de Cadastro de Produtos

Cenário:

Você vai criar um sistema de controle de qualidade para cadastro de produtos em um e-commerce. Cada produto precisa passar por 4 "inspetores":

  • Inspetor de Nome: Nome deve ter 3-50 caracteres
  • Inspetor de Preço: Preço deve ser positivo e menor que R$ 10.000
  • Inspetor de Categoria: Deve ser uma das categorias válidas
  • Inspetor de Estoque: Quantidade deve ser número inteiro positivo

Desafio Prático:

  1. Crie um middleware validarProduto que verifica todos os campos
  2. Se houver erros, retorne status 400 com lista de problemas
  3. Se estiver tudo certo, permita que a requisição continue
  4. Teste com dados válidos e inválidos

Dados para testar:

// Produto válido
{
  "nome": "Smartphone XYZ",
  "preco": 899.99,
  "categoria": "eletronicos",
  "estoque": 50
}

// Produto inválido
{
  "nome": "AB",           // Muito curto
  "preco": -100,          // Negativo
  "categoria": "carros",  // Categoria inválida
  "estoque": "muitos"     // Não é número
}

6. 🎯 Exercícios Práticos - Hora de Colocar a Mão na Massa!

🎮 Missão Final: Sistema Completo de Gerenciamento

Agora que você aprendeu todos os conceitos com analogias, é hora de aplicar tudo em projetos reais! Você vai criar 3 sistemas diferentes, cada um focando em aspectos específicos que aprendemos.

💡 Dica: Comece pelo exercício que mais te interessar. Não precisa fazer todos de uma vez!

💻 EXERCÍCIO PRÁTICO

🍔 Exercício 1: API da Lanchonete "Sabor & Cia"

Cenário:

Você foi contratado para criar a API de uma lanchonete. O sistema precisa gerenciar cardápio, pedidos e clientes de forma organizada.

Estrutura de Departamentos (Routers):

  • /api/cardapio - Gerenciar produtos do cardápio
  • /api/pedidos - Controlar pedidos dos clientes
  • /api/clientes - Cadastro e dados dos clientes

Seguranças (Middlewares) necessários:

  • Logger: Registrar todas as atividades
  • Validador de Produto: Nome (3-30 chars), preço (R$ 1-100), categoria válida
  • Validador de Cliente: Nome, telefone, email válido
  • Rate Limiter: Máximo 20 pedidos por minuto por cliente

Rotas para implementar:

GET    /api/cardapio              - Listar todos os produtos
GET    /api/cardapio/:id          - Buscar produto específico
GET    /api/cardapio/categoria/:cat - Produtos por categoria
POST   /api/cardapio              - Adicionar novo produto
PUT    /api/cardapio/:id          - Atualizar produto
DELETE /api/cardapio/:id          - Remover produto

POST   /api/pedidos               - Criar novo pedido
GET    /api/pedidos/:clienteId    - Pedidos de um cliente
GET    /api/pedidos?status=...    - Filtrar por status

POST   /api/clientes              - Cadastrar cliente
GET    /api/clientes/:id          - Dados do cliente
💻 EXERCÍCIO PRÁTICO

📚 Exercício 2: Sistema de Biblioteca Digital

Cenário:

Uma biblioteca precisa de um sistema para gerenciar livros, usuários e empréstimos. O sistema deve ter controles rigorosos de segurança e validação.

Departamentos (Routers):

  • /api/livros - Catálogo de livros
  • /api/usuarios - Usuários da biblioteca
  • /api/emprestimos - Controle de empréstimos

Sistema de Segurança (Middlewares):

  • Autenticação: Verificar se usuário está logado
  • Autorização: Bibliotecários vs Usuários comuns
  • Validação de Livro: ISBN, título, autor, ano
  • Controle de Empréstimos: Máximo 3 livros por usuário

Desafio Especial:

Implemente um sistema de "endereços" (parâmetros) inteligente:

GET /api/livros/autor/:nome        - Livros de um autor
GET /api/livros/ano/:ano           - Livros de um ano
GET /api/livros/genero/:genero     - Livros por gênero
GET /api/livros/buscar?q=...&ano=...&autor=... - Busca avançada

GET /api/emprestimos/usuario/:id   - Empréstimos de um usuário
GET /api/emprestimos/vencidos      - Empréstimos em atraso
GET /api/emprestimos/hoje          - Empréstimos que vencem hoje
🚀 DESAFIO

🛒 Exercício 3: E-commerce "TechStore"

Cenário:

Crie uma API completa para um e-commerce de produtos eletrônicos. Este exercício combina TODOS os conceitos aprendidos.

Departamentos Completos:

  • /api/produtos - Catálogo de produtos
  • /api/categorias - Gestão de categorias
  • /api/usuarios - Clientes e administradores
  • /api/carrinho - Carrinho de compras
  • /api/pedidos - Processamento de pedidos

Sistema de Controle de Qualidade Avançado:

  • Produto: Nome, preço (R$ 10-50.000), categoria, estoque, descrição
  • Usuário: Email único, senha forte, CPF válido, endereço completo
  • Pedido: Produtos válidos, estoque disponível, valor total correto
  • Carrinho: Limite de 20 itens, produtos ativos apenas
🚀 DESAFIO

Mega Desafio - Sistema de Endereços Completo:

// Produtos com filtros avançados
GET /api/produtos?categoria=smartphones&preco_min=500&preco_max=2000
GET /api/produtos?marca=apple&ordenar=preco&direcao=asc
GET /api/produtos/categoria/:cat/marca/:marca
GET /api/produtos/promocao/:desconto  // Ex: /promocao/20 (20% off)

// Carrinho personalizado por usuário
GET /api/carrinho/:usuarioId
POST /api/carrinho/:usuarioId/adicionar/:produtoId
DELETE /api/carrinho/:usuarioId/remover/:produtoId

// Pedidos com status e filtros
GET /api/pedidos/usuario/:id/status/:status
GET /api/pedidos/periodo/:dataInicio/:dataFim
GET /api/pedidos/valor/:min/:max

💡 Dicas para o Sucesso

🏗️ Estrutura Recomendada:

projeto/
├── app.js
├── routes/
│   ├── index.js
│   ├── produtos.js
│   └── usuarios.js
├── middleware/
│   ├── auth.js
│   ├── validation.js
│   └── logger.js
└── data/
    └── produtos.json

🧪 Como Testar com Thunder Client:

  • Instale Thunder Client: Extensão gratuita no VS Code
  • Abra Thunder Client: Ícone do raio na barra lateral
  • Nova Request: Clique em "New Request"
  • Configure: Método (GET/POST), URL (http://localhost:3000/api/...)
  • Body (POST/PUT): Aba "Body" → "JSON" → Cole os dados
  • Teste dados válidos e inválidos para validar middlewares
  • Verifique rate limiting fazendo múltiplas requests
  • Confirme validações enviando dados incorretos

🎯 Meta: Escolha UM exercício e complete-o totalmente antes de partir para o próximo. É melhor fazer um projeto completo do que três pela metade!

📚 Resumo da Aula

O que você aprendeu:

  • Express Router para organização modular
  • Separação de responsabilidades (MVC)
  • Parâmetros de rota e query parameters
  • Middleware personalizado avançado
  • Validação e tratamento de erros

Próxima aula:

Aula 5: Banco de Dados com Supabase

Aprenda a integrar sua aplicação com Supabase para persistência de dados

Ir para próxima aula