Node.js: Projeto API (Parte 2)

Última atualização: 2026-08-26

1. Foco nos negócios: O segundo dia de Alice

No dia seguinte, Alice ficou encarregada de implementar as operações CRUD, enquanto Bob se concentrou na filtragem, na paginação e no controle de acesso. “CRUD é a espinha dorsal, a filtragem e a paginação são a experiência do usuário, e o controle de acesso é a segurança”, disse Alice. “Todos os três são indispensáveis.”



2. API CRUD de tarefas

(1) Projeto de endpoints CRUD

Método Caminho Descrição Permissões
POST /api/tasks Criar tarefa Usuário conectado
OBTER /api/tasks Obter lista de tarefas Usuário conectado
OBTER /api/tasks/:id Obter uma única tarefa Eu ou o administrador
PUT /api/tasks/:id Atualizar tarefa Eu mesmo ou administrador
EXCLUIR /api/tasks/:id Excluir tarefa Eu ou o administrador

▶ Exemplo: Como criar uma tarefa

JAVASCRIPT
router.post('/', auth, async (req, res, next) => {
  try {
    const task = await Task.create({ ...req.body, assignedTo: req.user._id });
    res.status(201).json(task);
  } catch (err) {
    next(err);
  }
});
▶ Experimente

▶ Exemplo: Recuperação de uma única tarefa

JAVASCRIPT
router.get('/:id', auth, async (req, res, next) => {
  try {
    const task = await Task.findById(req.params.id).populate('assignedTo', 'username email');
    if (!task) return res.status(404).json({ message: 'Task not found' });
    if (task.assignedTo._id.toString() !== req.user._id.toString() && req.user.role !== 'admin') {
      return res.status(403).json({ message: 'Forbidden' });
    }
    res.json(task);
  } catch (err) {
    next(err);
  }
});
▶ Experimente

▶ Exemplo: Atualizar tarefa

JAVASCRIPT
router.put('/:id', auth, async (req, res, next) => {
  try {
    const task = await Task.findById(req.params.id);
    if (!task) return res.status(404).json({ message: 'Task not found' });
    if (task.assignedTo.toString() !== req.user._id.toString() && req.user.role !== 'admin') {
      return res.status(403).json({ message: 'Forbidden' });
    }
    Object.assign(task, req.body);
    await task.save();
    res.json(task);
  } catch (err) {
    next(err);
  }
});
▶ Experimente

▶ Exemplo: Excluindo uma tarefa

JAVASCRIPT
router.delete('/:id', auth, async (req, res, next) => {
  try {
    const task = await Task.findById(req.params.id);
    if (!task) return res.status(404).json({ message: 'Task not found' });
    if (task.assignedTo.toString() !== req.user._id.toString() && req.user.role !== 'admin') {
      return res.status(403).json({ message: 'Forbidden' });
    }
    await task.deleteOne();
    res.json({ message: 'Task deleted' });
  } catch (err) {
    next(err);
  }
});
▶ Experimente

3. Filtragem, paginação e ordenação de listas

(1) Descrição dos parâmetros do filtro

Parâmetro Tipo Descrição Exemplo
status String Filtrar por status ?status=completed
priority String Filtrar por prioridade ?priority=high
assignedTo ObjectId Filtrar por responsável (admin) ?assignedTo=userId
dueBefore Data ISO Data de vencimento anterior a ?dueBefore=2025-12-31
dueAfter Data ISO Data de vencimento posterior a ?dueAfter=2025-01-01
search String Pesquisa aproximada por título ?search=deploy

(2) Parâmetros de paginação

Parâmetro Valor padrão Descrição
page 1 Página atual
limit 10 Itens por página (máx. 100)
sort -createdAt Campo de classificação; o prefixo - indica ordem decrescente

(3) Regras de permissão

Função Alcance de visibilidade Alcance de ação
user Apenas minhas tarefas Apenas minhas tarefas
admin Todas as tarefas Todas as tarefas
user + consulta assignedTo Ignorar este parâmetro

▶ Exemplo: Uma lista paginada com filtragem

JAVASCRIPT
router.get('/', auth, async (req, res, next) => {
  try {
    const { status, priority, dueBefore, dueAfter, search, page = 1, limit = 10, sort = '-createdAt' } = req.query;
    const filter = {};
    if (req.user.role !== 'admin') filter.assignedTo = req.user._id;
    else if (req.query.assignedTo) filter.assignedTo = req.query.assignedTo;
    if (status) filter.status = status;
    if (priority) filter.priority = priority;
    if (dueBefore || dueAfter) filter.dueDate = {};
    if (dueBefore) filter.dueDate.$lte = new Date(dueBefore);
    if (dueAfter) filter.dueDate.$gte = new Date(dueAfter);
    if (search) filter.タイトル = { $regex: search, $options: 'i' };

    const total = await Task.countDocuments(filter);
    const tasks = await Task.find(filter)
      .populate('assignedTo', 'username email')
      .sort(sort)
      .skip((page - 1) * limit)
      .limit(Number(limit));

    res.json({ tasks, total, page: Number(page), pages: Math.ceil(total / limit) });
  } catch (err) {
    next(err);
  }
});
▶ Experimente

▶ Exemplo: Classificação por vários campos

JAVASCRIPT
// ?sort=-priority,createdAt  →  { priority: -1, createdAt: 1 }
const parseSort = (sortStr) => {
  const sortObj = {};
  sortStr.split(',').forEach(field => {
    if (field.startsWith('-')) sortObj[field.slice(1)] = -1;
    else sortObj[field] = 1;
  });
  return sortObj;
};
▶ Experimente

4. Middleware de validação e autorização de dados

▶ Exemplo:(1) Fluxo de trabalho para processamento de solicitações

100%
graph LR
    A[Client Request] --> B[express-validator]
    B --> C[auth Middleware]
    C --> D[Permission Check]
    D --> E[Business Logic]
    E --> F[Standard Response]
    B -->|Verification Failed| G[400 Error]
    C -->|Not verified| H[401 Error]
    D -->|No permission| I[403 Error]

▶ Exemplo: regras de validação do express-validator

JAVASCRIPT
const { body, query, validationResult } = require('express-validator');

const validateTask = [
  body('title').notEmpty().withMessage('Title is required').isLength({ max: 100 }).withMessage('Title too long'),
  body('status').optional().isIn(['pending', 'in-progress', 'completed']),
  body('priority').optional().isIn(['low', 'medium', 'high']),
  body('dueDate').optional().isISO8601().withMessage('Invalid date format'),
  (req, res, next) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) return res.status(400).json({ errors: errors.array() });
    next();
  }
];
▶ Experimente

▶ Exemplo: Middleware de verificação de permissão

JAVASCRIPT
const requireAdmin = (req, res, next) => {
  if (req.user.role !== 'admin') return res.status(403).json({ message: 'Admin access required' });
  next();
};

const requireOwnerOrAdmin = (model) => async (req, res, next) => {
  const doc = await model.findById(req.params.id);
  if (!doc) return res.status(404).json({ message: 'Not found' });
  if (doc.assignedTo.toString() !== req.user._id.toString() && req.user.role !== 'admin') {
    return res.status(403).json({ message: 'Forbidden' });
  }
  req.doc = doc;
  next();
};
▶ Experimente

▶ Exemplo: Exclusão em massa de tarefas

JAVASCRIPT
router.delete('/batch', auth, requireAdmin, async (req, res, next) => {
  try {
    const { ids } = req.body;
    const result = await Task.deleteMany({ _id: { $in: ids } });
    res.json({ deleted: result.deletedCount });
  } catch (err) {
    next(err);
  }
});
▶ Experimente

5. Exemplo abrangente: roteamento completo de “tarefas”

Integre CRUD, filtragem, paginação, validação e permissões em um único módulo de roteamento abrangente:

JAVASCRIPT
const router = require('express').Router();
const Task = require('../models/Task');
const auth = require('../middleware/auth');
const { body, query, validationResult } = require('express-validator');

const validate = (req, res, next) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) return res.status(400).json({ errors: errors.array() });
  next();
};

const checkOwner = async (req, res, next) => {
  const task = await Task.findById(req.params.id);
  if (!task) return res.status(404).json({ message: 'Task not found' });
  if (task.assignedTo.toString() !== req.user._id.toString() && req.user.role !== 'admin') {
    return res.status(403).json({ message: 'Forbidden' });
  }
  req.task = task;
  next();
};

router.post('/', auth, [
  body('title').notEmpty().isLength({ max: 100 }),
  body('priority').optional().isIn(['low', 'medium', 'high']),
  body('dueDate').optional().isISO8601()
], validate, async (req, res, next) => {
  try {
    const task = await Task.create({ ...req.body, assignedTo: req.user._id });
    res.status(201).json(task);
  } catch (err) { next(err); }
});

router.get('/', auth, async (req, res, next) => {
  try {
    const { status, priority, search, page = 1, limit = 10, sort = '-createdAt' } = req.query;
    const filter = {};
    if (req.user.role !== 'admin') filter.assignedTo = req.user._id;
    if (status) filter.status = status;
    if (priority) filter.priority = priority;
    if (search) filter.title = { $regex: search, $options: 'i' };
    const total = await Task.countDocuments(filter);
    const tasks = await Task.find(filter).populate('assignedTo', 'username').sort(sort).skip((page - 1) * limit).limit(Number(limit));
    res.json({ tasks, total, page: Number(page), pages: Math.ceil(total / limit) });
  } catch (err) { next(err); }
});

router.get('/:id', auth, checkOwner, (req, res) => res.json(req.task));

router.put('/:id', auth, checkOwner, [
  body('title').optional().notEmpty().isLength({ max: 100 }),
  body('status').optional().isIn(['pending', 'in-progress', 'completed'])
], validate, async (req, res, next) => {
  try {
    Object.assign(req.task, req.body);
    await req.task.save();
    res.json(req.task);
  } catch (err) { next(err); }
});

router.delete('/:id', auth, checkOwner, async (req, res, next) => {
  try {
    await req.task.deleteOne();
    res.json({ message: 'Task deleted' });
  } catch (err) { next(err); }
});

module.exports = router;

❓ Perguntas Frequentes

P: Como se implementam consultas paginadas? R: Use skip e limit: use Task.countDocuments() para obter o número total de documentos e Task.find().skip((page-1)*limit).limit(limit) para recuperar os dados da página atual.

P: Como faço para escolher entre o express-validator e o Joi? R: O express-validator é baseado no validator.js e se integra perfeitamente ao middleware do Express; o Joi é mais poderoso, mas requer uma chamada separada. Recomendamos o express-validator para projetos do Express.

P: Como faço para implementar a exclusão temporária? R: Adicione um campo deletedAt ao esquema e filtre as consultas com { deletedAt: null }; ou use o plug-in mongoose-delete para lidar com isso automaticamente.

P: Como as permissões das tarefas são controladas? R: No middleware de roteamento, compare req.userId e task.author. Somente o autor pode modificar ou excluir suas próprias tarefas; todos os demais recebem um erro 403.

P: Como faço para lidar com operações em massa? R: Use os métodos bulkWrite ou updateMany do Mongoose para executar várias operações de gravação de uma só vez; isso oferece um desempenho muito melhor do que executar operações individuais em um loop.


📖 Resumo

📝 Exercícios

  1. Implemente rotas CRUD completas para as tarefas e use o Postman para testar, uma a uma, as operações de criação, consulta, atualização e exclusão.
  2. Adicione recursos de filtragem e paginação e teste consultas combinadas, como ?status=completed&page=2&limit=5&sort=-priority.
  3. Escreva o middleware requireAdmin para garantir que seja retornado um erro 403 quando um usuário comum acessar a interface de administração.
  4. Adicione regras de validação do express-validator para o cadastro e o login e teste as respostas de erro para campos em branco e formatos inválidos.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%