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.”
- A API CRUD constitui a lógica de negócios central do gerenciamento de tarefas
- O filtro de consulta permite que os usuários filtrem tarefas por status, prioridade ou data
- A paginação e a ordenação ajudam a evitar a queda no desempenho ao lidar com grandes conjuntos de dados
- As permissões de função garantem que os usuários comuns possam trabalhar apenas em suas próprias tarefas
- express-validator: Padroniza a validação dos formatos dos parâmetros de solicitação
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
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);
}
});
▶ Exemplo: Recuperação de uma única tarefa
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);
}
});
▶ Exemplo: Atualizar tarefa
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);
}
});
▶ Exemplo: Excluindo uma tarefa
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);
}
});
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
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);
}
});
▶ Exemplo: Classificação por vários campos
// ?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;
};
4. Middleware de validação e autorização de dados
▶ Exemplo:(1) Fluxo de trabalho para processamento de solicitações
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
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();
}
];
▶ Exemplo: Middleware de verificação de permissão
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();
};
▶ Exemplo: Exclusão em massa de tarefas
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);
}
});
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:
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
skipelimit: useTask.countDocuments()para obter o número total de documentos eTask.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
deletedAtao 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.userIdetask.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
bulkWriteouupdateManydo 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.
- P: Como faço para implementar a paginação? R: Use o
.skip((page-1)*limit).limit(limit)do Mongoose para implementar a paginação baseada em deslocamento e use ocountDocumentspara retornar a contagem total e calcular o número total de páginas. - P: Como posso restringir os usuários para que trabalhem apenas em suas próprias tarefas? R: Adicione
assignedTo: req.user._idao filtro de consulta e, antes de atualizar ou excluir, verifique setask.assignedToé igual ao ID do usuário atual. - P: Como faço para excluir itens em massa? R: Use
Task.deleteMany({ _id: { $in: ids } }), mas recomendamos restringir as operações em massa aos usuários com a função “admin”. - P: Como faço para ordenar por vários campos? R: Separe-os por vírgulas, como
?sort=-priority,createdAt, que é analisado como{ priority: -1, createdAt: 1 }e passado para o Mongoose como.sort(). - P: Como posso padronizar o formato dos erros de validação? R: Use
validationResultdo express-validator para garantir que todas as respostas sigam a estrutura{ errors: [{ msg, param, value }] }. - P: Há espaço para otimização no desempenho da paginação? R: Ao lidar com grandes conjuntos de dados, a operação
skipé lenta. Você pode mudar para a paginação baseada em cursor (usando a filtragem$gtcom base em_idoucreatedAt) para evitar pular um grande número de documentos.
📖 Resumo
- Foco nos negócios: conceitos-chave e uso do Alice no segundo dia
- Conceitos básicos e uso da API CRUD
- Conceitos básicos e uso da filtragem, paginação e ordenação de listas
- Conceitos básicos e uso de middleware de validação e autorização de dados
- Exemplo abrangente: conceitos básicos e uso da rota “tasks” completa
📝 Exercícios
- 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.
- Adicione recursos de filtragem e paginação e teste consultas combinadas, como
?status=completed&page=2&limit=5&sort=-priority. - Escreva o middleware
requireAdminpara garantir que seja retornado um erro 403 quando um usuário comum acessar a interface de administração. - 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.