name: express-best-practices
description: Especialista em Express.js Best Practices - Middleware, Seguranca, CORS, Rate Limiting, Controller/Service Separation, API Response Standardization, Error Handling e SPA Serving. Adaptado para o stack Node.js/Express/MongoDB do Super Cartola Manager. Keywords: express, middleware, rota, route, controller, service, cors, rate limit, seguranca, headers, error handling, api response, SPA, catch-all, static files
allowed-tools: Read, Grep, Glob, Bash, TodoWrite
Express Best Practices Skill (Super Cartola Manager)
Missao
Garantir que o servidor Express do Super Cartola Manager siga padroes de seguranca, performance e manutenibilidade. Toda rota, middleware e controller deve seguir convencoes consistentes e documentadas.
1. Ordem de Middleware (Pipeline de Request)
1.1 Ordem Correta no server.js
A ordem dos middlewares importa. Um middleware fora de posicao pode causar falhas silenciosas ou brechas de seguranca.
app.use(securityMiddleware);
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
app.use(corsMiddleware);
app.use(sessionMiddleware);
app.use(express.static('public'));
app.use(activityTrackerMiddleware);
app.use('/api', tenantMiddleware);
app.use('/api/admin', verificarAdmin, adminRoutes);
app.use('/api/participante', verificarParticipante, participanteRoutes);
app.use('/api/public', publicRoutes);
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
app.use(errorHandlerMiddleware);
1.2 Por que esta Ordem
| Posicao | Middleware | Motivo |
|---|
| 1 | security.js | Bloquear requests maliciosos antes de qualquer processamento |
| 2 | body parsing | Necessario para ler req.body nas rotas |
| 3 | CORS | Definir headers antes de processar resposta |
| 4 | sessao | Necessario para auth nos proximos middlewares |
| 5 | static | Servir arquivos sem processar rotas desnecessariamente |
| 6 | activityTracker | Registrar atividade apos sessao estar disponivel |
| 7 | tenant | Identificar liga antes de chegar aos controllers |
| 8 | rotas | Logica de negocio |
| 9 | catch-all | SPA fallback — so se nenhuma rota de API matchou |
| 10 | error handler | Capturar erros de qualquer etapa anterior |
2. Seguranca
2.1 Referencia: middleware/security.js
O projeto ja implementa headers de seguranca e rate limiting neste middleware. Ao criar novas rotas, garantir que passem por ele.
2.2 Headers de Seguranca Obrigatorios
{
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Content-Security-Policy': "default-src 'self'; ...",
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains'
}
2.3 Rate Limiting
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 20,
message: { error: 'Muitas tentativas. Tente novamente em 15 minutos.' }
});
app.use('/api/auth/login', authLimiter);
2.4 CORS
const corsOptions = {
origin: [
'https://supercartolamanager.com.br',
'https://www.supercartolamanager.com.br',
process.env.REPLIT_DEV_DOMAIN
].filter(Boolean),
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization']
};
3. Controller / Service Separation
3.1 Principio
Controllers lidam com HTTP (req/res). Services lidam com logica de negocio e dados. Controllers NAO devem conter queries MongoDB diretamente.
3.2 Estrutura do Projeto
controllers/
├── ajusteFinanceiroController.js # HTTP layer
├── rankingController.js
└── inscricaoController.js
services/
├── orchestrator/ # Pattern de orquestracao
│ ├── managers/ # Managers especializados
│ └── index.js
├── saldoService.js
└── rankingService.js
3.3 Pattern Correto
import { getRankingByLiga } from '../services/rankingService.js';
import { apiSuccess, apiError, apiServerError } from '../utils/apiResponse.js';
export async function listarRanking(req, res) {
try {
const { liga_id } = req.params;
const { temporada, rodada } = req.query;
if (!liga_id) {
return apiError(res, 'liga_id obrigatorio', 400);
}
const ranking = await getRankingByLiga(liga_id, temporada, rodada);
return apiSuccess(res, { ranking });
} catch (error) {
console.error('[RankingController] Erro:', error);
return apiServerError(res, 'Erro ao buscar ranking');
}
}
import { getDb } from '../config/database.js';
export async function getRankingByLiga(liga_id, temporada, rodada) {
const db = getDb();
const ranking = await db.collection('rankings')
.find({ liga_id, temporada })
.sort({ pontos_total: -1 })
.lean()
.toArray();
return ranking;
}
3.4 O que vai em cada camada
| Camada | Responsabilidades | NAO deve fazer |
|---|
| Route | Definir path, metodo HTTP, middleware de auth | Logica de negocio |
| Controller | Extrair params, validar entrada, chamar service, formatar resposta | Queries diretas ao DB |
| Service | Logica de negocio, queries ao DB, calculos | Acessar req/res |
| Model/Utils | Schema, validacao de dados, helpers | Logica de negocio |
4. Padronizacao de Respostas API
4.1 Referencia: utils/apiResponse.js
Todas as rotas DEVEM usar as funcoes padronizadas de resposta. NUNCA usar res.json() ou res.status().send() diretamente.
import {
apiSuccess,
apiError,
apiServerError,
apiUnauthorized,
apiConflict
} from '../utils/apiResponse.js';
4.2 Exemplos de Uso
return apiSuccess(res, { participantes: lista }, 'Participantes carregados');
return apiError(res, 'Campo "valor" deve ser numerico', 400);
return apiError(res, 'Liga nao encontrada', 404);
return apiUnauthorized(res, 'Sessao expirada');
return apiConflict(res, 'Inscricao ja existe para esta temporada');
console.error('[Controller] Detalhes:', error);
return apiServerError(res, 'Erro ao processar operacao');
5. Tratamento de Erros
5.1 Async Error Handling em Controllers
export async function meuController(req, res) {
try {
return apiSuccess(res, data);
} catch (error) {
console.error('[MeuController] Erro:', error.message);
return apiServerError(res, 'Erro interno');
}
}
5.2 Error Handler Global (Middleware Final)
function errorHandlerMiddleware(err, req, res, next) {
console.error('[ErrorHandler]', {
method: req.method,
url: req.originalUrl,
error: err.message,
stack: process.env.NODE_ENV === 'development' ? err.stack : undefined
});
const status = err.status || 500;
const message = status === 500
? 'Erro interno do servidor'
: err.message;
res.status(status).json({
success: false,
error: message
});
}
5.3 Erros Comuns a Prevenir
| Erro | Causa | Prevencao |
|---|
Cannot read property of undefined | Acessar campo de documento nulo | Sempre verificar if (!doc) antes de acessar |
Resposta dupla (headers already sent) | Esquecer return antes de apiSuccess/apiError | SEMPRE usar return apiSuccess(...) |
| Timeout sem resposta | Esqueceu de chamar res em algum branch | Todo branch deve terminar com resposta |
| Stack trace no response | res.status(500).json({ error: err }) | Usar apiServerError que sanitiza |
6. Rastreabilidade de Requests
6.1 Request ID
import { v4 as uuidv4 } from 'uuid';
app.use((req, res, next) => {
req.requestId = req.headers['x-request-id'] || uuidv4();
res.setHeader('X-Request-Id', req.requestId);
next();
});
6.2 Logging Padronizado
console.log(`[${req.requestId}] [RankingController] GET /api/ranking/${liga_id}`);
console.error(`[${req.requestId}] [AjusteController] Erro:`, error.message);
7. SPA (Single Page Application)
7.1 Servir Arquivos Estaticos
app.use(express.static('public', {
maxAge: '1d',
etag: true,
lastModified: true
}));
7.2 Catch-All para SPA
app.get('*', (req, res) => {
if (req.path.startsWith('/api/')) {
return res.status(404).json({ success: false, error: 'Rota nao encontrada' });
}
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
7.3 SPA Init Pattern (REGRA DO PROJETO)
Paginas em supportedPages (layout.html) NUNCA devem usar DOMContentLoaded sozinho:
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
8. Definicao de Rotas
8.1 Convencoes de Nomenclatura
router.get('/api/admin/ligas', listarLigas);
router.get('/api/admin/ligas/:liga_id', obterLiga);
router.post('/api/admin/ligas', criarLiga);
router.put('/api/admin/ligas/:liga_id', atualizarLiga);
router.delete('/api/admin/ligas/:liga_id', removerLiga);
router.post('/api/admin/ligas/:liga_id/processar-rodada', processarRodada);
router.get('/api/admin/ligas/:liga_id/extrato/:time_id', obterExtrato);
8.2 Middleware de Autenticacao por Grupo
router.use('/api/admin', verificarAdmin);
router.use('/api/participante', verificarParticipante);
router.use('/api/public', publicRoutes);
8.3 Paginacao
router.get('/api/admin/participantes',
verificarAdmin,
paginationMiddleware,
listarParticipantes
);
const { page, limit, skip } = req.pagination;
const results = await db.collection('participantes')
.find({ liga_id })
.skip(skip)
.limit(limit)
.lean()
.toArray();
const total = await db.collection('participantes').countDocuments({ liga_id });
return apiSuccess(res, {
dados: results,
paginacao: { page, limit, total, totalPages: Math.ceil(total / limit) }
});
9. Checklist para Nova Rota
[ ] Rota segue convencao RESTful?
[ ] Middleware de auth aplicado (verificarAdmin ou verificarParticipante)?
[ ] Controller usa try/catch com apiServerError?
[ ] Respostas usam apiSuccess/apiError (nao res.json direto)?
[ ] Toda query inclui liga_id?
[ ] Queries de leitura usam .lean()?
[ ] Validacao de entrada antes de processar?
[ ] Verificou req.session.usuario antes de acao sensivel?
[ ] Rota registrada ANTES do catch-all SPA?
[ ] Paginacao aplicada em rotas de listagem?
STATUS: Express Best Practices Skill — ATIVO
Versao: 1.0
Ultima atualizacao: 2026-03-12