1 / 12
Modulo 7 / APIs REST

API RESTful completa

Express + MongoDB + Auth + Validação + Error Handling.

school Aula 32 schedule 2 horas

Projeto

API de Produtos

folder Estrutura do projeto

api-produtos/ ├── controllers/ │ ├── authController.js │ └── produtoController.js ├── middleware/ │ ├── auth.js │ └── errorHandler.js ├── models/ │ ├── Usuario.js │ └── Produto.js ├── routes/ │ ├── authRoutes.js │ └── produtoRoutes.js ├── .env ├── .gitignore ├── package.json └── index.js

download Setup inicial

$ mkdir api-produtos && cd api-produtos $ npm init -y $ npm install express mongoose dotenv \ bcryptjs jsonwebtoken cors $ npm install -D nodemon // package.json scripts { "scripts": { "dev": "nodemon index.js", "start": "node index.js" } }

Servidor

index.js principal

require('dotenv').config(); const express = require('express'); const mongoose = require('mongoose'); const cors = require('cors'); const errorHandler = require('./middleware/errorHandler'); const app = express(); // Middlewares globais app.use(cors()); app.use(express.json()); // Rotas app.use('/api/auth', require('./routes/authRoutes')); app.use('/api/produtos', require('./routes/produtoRoutes')); // Middleware de erro (sempre por último) app.use(errorHandler); // Conexão + Start mongoose.connect(process.env.MONGODB_URI) .then(() => { console.log('✅ MongoDB conectado'); app.listen(process.env.PORT || 3000, () => console.log('🚀 Servidor rodando') ); });

Model

Model Produto

code models/Produto.js

const mongoose = require('mongoose'); const produtoSchema = new mongoose.Schema({ nome: { type: String, required: [true, 'Nome obrigatório'], trim: true }, descricao: { type: String, default: '' }, preco: { type: Number, required: true, min: [0, 'Preço não pode ser negativo'] }, categoria: { type: String, required: true, enum: ['Eletrônicos', 'Roupas', 'Alimentos', 'Livros', 'Outros'] }, estoque: { type: Number, default: 0, min: 0 }, ativo: { type: Boolean, default: true }, criadoPor: { type: mongoose.Schema.Types.ObjectId, ref: 'Usuario' } }, { timestamps: true }); module.exports = mongoose.model('Produto', produtoSchema);
enum limita os valores aceitos. ref cria um relacionamento com o model Usuario.

Controller

CRUD completo

code controllers/produtoController.js

const Produto = require('../models/Produto'); exports.listar = async (req, res, next) => { try { const { categoria, ordem, pagina = 1, limite = 10 } = req.query; const filtro = { ativo: true }; if (categoria) filtro.categoria = categoria; const produtos = await Produto.find(filtro) .sort(ordem === 'preco' ? { preco: 1 } : { createdAt: -1 }) .skip((pagina - 1) * limite) .limit(Number(limite)); const total = await Produto.countDocuments(filtro); res.json({ produtos, total, pagina: Number(pagina), paginas: Math.ceil(total / limite) }); } catch (err) { next(err); } }; exports.buscar = async (req, res, next) => { try { const produto = await Produto.findById(req.params.id); if (!produto) return res.status(404).json({ erro: 'Produto não encontrado' }); res.json(produto); } catch (err) { next(err); } }; exports.criar = async (req, res, next) => { try { req.body.criadoPor = req.userId; const produto = await Produto.create(req.body); res.status(201).json(produto); } catch (err) { next(err); } };

Controller

Update e Delete

exports.atualizar = async (req, res, next) => { try { const produto = await Produto.findByIdAndUpdate( req.params.id, req.body, { new: true, runValidators: true } ); if (!produto) return res.status(404).json({ erro: 'Não encontrado' }); res.json(produto); } catch (err) { next(err); } }; exports.remover = async (req, res, next) => { try { const produto = await Produto.findByIdAndDelete(req.params.id); if (!produto) return res.status(404).json({ erro: 'Não encontrado' }); res.status(204).send(); } catch (err) { next(err); } };
runValidators: true garante que o update respeite as validações do schema. new: true retorna o doc atualizado.

Rotas

Rotas protegidas

route routes/produtoRoutes.js

const router = require('express').Router(); const ctrl = require('../controllers/produtoController'); const auth = require('../middleware/auth'); // Públicas (leitura) router.get('/', ctrl.listar); router.get('/:id', ctrl.buscar); // Protegidas (escrita) router.post('/', auth, ctrl.criar); router.put('/:id', auth, ctrl.atualizar); router.delete('/:id', auth, ctrl.remover); module.exports = router;

api Endpoints da API

GET /api/produtos 🔓
GET /api/produtos/:id 🔓
POST /api/produtos 🔒
PUT /api/produtos/:id 🔒
DELETE /api/produtos/:id 🔒
POST /api/auth/registrar 🔓
POST /api/auth/login 🔓

🔓 pública   🔒 requer token

Erros

Error Handler global

code middleware/errorHandler.js

module.exports = (err, req, res, next) => { console.error('❌', err.message); // Erro de validação do Mongoose if (err.name === 'ValidationError') { const erros = Object.values(err.errors) .map(e => e.message); return res.status(400).json({ erro: 'Validação', detalhes: erros }); } // ID inválido do MongoDB if (err.name === 'CastError') return res.status(400).json({ erro: 'ID inválido' }); // Duplicata (unique) if (err.code === 11000) return res.status(409).json({ erro: 'Registro duplicado' }); res.status(500).json({ erro: 'Erro interno do servidor' }); };
Um middleware com 4 parâmetros (err, req, res, next) é reconhecido pelo Express como error handler.

Recursos

Paginação e filtros

filter_alt Exemplos de query params

GET /api/produtos?categoria=Eletrônicos Filtra por categoria
GET /api/produtos?ordem=preco Ordena por preço
GET /api/produtos?pagina=2&limite=5 Página 2 com 5 itens

download Resposta paginada

{ "produtos": [...], "total": 47, "pagina": 2, "paginas": 10 }
Informar total e paginas permite ao front-end montar a navegação de páginas.

Testando

Testando com curl

# 1. Registrar curl -X POST http://localhost:3000/api/auth/registrar \ -H "Content-Type: application/json" \ -d '{"nome":"Ana","email":"ana@email.com","senha":"123456"}' # 2. Login → copiar o token curl -X POST http://localhost:3000/api/auth/login \ -H "Content-Type: application/json" \ -d '{"email":"ana@email.com","senha":"123456"}' # 3. Criar produto (com token) curl -X POST http://localhost:3000/api/produtos \ -H "Content-Type: application/json" \ -H "Authorization: Bearer SEU_TOKEN" \ -d '{"nome":"Notebook","preco":2999.90,"categoria":"Eletrônicos"}' # 4. Listar (sem token) curl http://localhost:3000/api/produtos

Hora de praticar

Exercício prático

api API RESTful completa

  1. Clone a estrutura do projeto e instale as dependências
  2. Configure o .env com sua connection string do Atlas
  3. Crie o model Produto com enum e ref
  4. Implemente o controller com paginação e filtros
  5. Adicione o error handler global
  6. Teste todos os endpoints com Thunder Client ou curl
lightbulb Desafio: adicione busca por texto no nome ($regex) e ordenação por múltiplos campos.
Próxima aula

Aula 33

Front-end consumindo API própria.

task_alt O que aprendemos hoje

  • check_circleEstrutura completa de um projeto REST
  • check_circleModel com enum, ref e validações
  • check_circleCRUD com paginação, filtros e ordenação
  • check_circleError handler global e tratamento de erros
  • check_circleRotas públicas vs protegidas com JWT
Próxima aula
auto_stories Referência: restfulapi.net
Leandro Medeiros