Node.js: Express Avançado

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

1. A crise de produção de Bob

A API do Bob enfrentou problemas logo no primeiro dia: um usuário enviou um endereço de e-mail com caracteres ilegíveis, causando um erro no banco de dados que derrubou todo o site; alguém fez o upload de uma imagem de 500 MB, esgotando instantaneamente o espaço em disco; e um concorrente criou um script que enviava 1.000 solicitações por segundo, fazendo com que o servidor retornasse um erro 502.

Basta um único app.use(errorHandler) para evitar 80% dos acidentes de produção.

TEXT 📖 Somente leitura
Bob Mine Clearance Timeline:
Day 1  📧 Invalid email address → Database Error → 500 Error
Day 2  🖼️ 500MB Upload → Disk Full → Service Outage
Day 3  🤖 1000 req/s → CPU 100% → 502 Bad Gateway
Day 4  🔒 Add middleware → Take Them One by One → Stable service


2. Middleware para tratamento de erros

(1) Esquema de assinatura de quatro parâmetros

O Express identifica o middleware de erros pelo número de parâmetros — deve haver 4 parâmetros (err, req, res, next); se faltar um, ele se torna um middleware comum.

▶ Exemplo: Middleware de tratamento global de erros

JAVASCRIPT
const express = require('express');
const app = express();

app.get('/boom', (req, res, next) => {
  try {
    throw new Error('Blown up on purpose');
  } catch (err) {
    next(err);
  }
});

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(err.status || 500).json({
    code: err.status || 500,
    data: null,
    message: err.message
  });
});

app.listen(3000);
▶ Experimente

(2) Classes personalizadas de erros de negócios

▶ Exemplo: Como distinguir erros de negócios de erros HTTP

JAVASCRIPT
class AppError extends Error {
  constructor(message, status) {
    super(message);
    this.status = status;
    this.isOperational = true;
    Error.captureStackTrace(this, this.constructor);
  }
}

class NotFoundError extends AppError {
  constructor(resource) {
    super(`${resource} Not found`, 404);
  }
}

class ValidationError extends AppError {
  constructor(message) {
    super(message, 400);
  }
}

app.get('/users/:id', (req, res, next) => {
  const user = findUser(req.params.id);
  if (!user) return next(new NotFoundError('User'));
  res.json({ code: 0, data: user, message: 'ok' });
});
▶ Experimente

(3) Comparação entre métodos de tratamento de erros

Método Número de parâmetros Escopo Casos de uso Suporte assíncrono
app.use(errHandler) 4 Todos os erros globais Captura final abrangente Requer ação manual next(err)
try/catch + next(err) Rota única Código de sincronização Apenas sincronização
express-async-errors 0 Erro assíncrono global roteamento async/await Automático
Módulo domain (Obsoleto) Nível de processo Não recomendado
process.on('uncaughtException') Nível de processo Última linha de defesa Global
Promessa .catch() Promessa única Operação assíncrona única Única


3. Validação de solicitações com o express-validator

(1) Como usar a cadeia de verificação e o middleware

O express-validator é baseado no validator.js. Ele utiliza uma “cadeia de validação” para definir regras de forma declarativa e coleta automaticamente os erros quando a validação falha.

▶ Exemplo: Verificação do cadastro do usuário

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

app.post('/register',
  body('username')
    .isLength({ min: 3, max: 20 }).withMessage('The username must3-20Character')
    .isAlphanumeric().withMessage('Usernames must consist of alphanumeric characters only.'),
  body('email')
    .isEmail().withMessage('Invalid email address')
    .normalizeEmail(),
  body('password')
    .isLength({ min: 8 }).withMessage('Password must be at least 8 characters')
    .matches(/\d/).withMessage('Passwords must contain numbers')
    .matches(/[A-Z]/).withMessage('The password must contain uppercase letters.'),
  body('age')
    .optional()
    .isInt({ min: 1, max: 150 }).withMessage('Age requirement1-150'),
  (req, res, next) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({
        code: 400,
        data: errors.array(),
        message: 'Request verification failed'
      });
    }
    next();
  },
  (req, res) => {
    res.json({ code: 0, data: req.body, message: 'ok' });
  }
);
▶ Experimente

(2) Validadores comuns no express-validator

Validador Finalidade Exemplo Modificadores associados
isEmail() E-mail body('email').isEmail() .normalizeEmail()
isLength() Comprimento body('name').isLength({min:2,max:50})
isInt() / isFloat() Número query('page').isInt({min:1}) .toInt()
isBoolean() Booleano body('active').isBoolean() .toBoolean()
isDate() Data body('birthday').isDate() .toDate()
isURL() URL body('website').isURL()
isIn() Enumeração body('role').isIn(['admin','user'])
matches() Expressão regular body('code').matches(/^\d{6}$/)
isMongoId() ObjectId param('id').isMongoId()
optional() Opcional body('nickname').optional().isLength({max:30}) {nullable:true}

(3) Comparando o express-validator e o Joi

Critérios de comparação express-validator Joi
Método de integração Middleware nativo do Express Biblioteca de validação independente; requer integração manual
Estilo de sintaxe Chamadas encadeadas, definição campo a campo Objeto de esquema, definição única
Dependências subjacentes validator.js Implementação personalizada
Coleta de erros validationResult() Coleta automática validate().error Processamento manual
Casos de uso Integração rápida com projetos Express Qualquer projeto Node.js
Curva de aprendizado Baixa (se já estiver familiarizado com o validator.js) Média (sintaxe exclusiva do Schema)
Conversão de tipo .toInt() .toDate(), etc. Conversão automática de tipo
Tamanho da comunidade Downloads semanais ~1,5 milhão Downloads semanais ~5 milhões


4. Envio de arquivos com o multer

(1) Três estratégias de armazenamento

O Multer oferece três modos de upload: single, array e fields. As opções de armazenamento incluem memoryStorage (memória) e diskStorage (disco).

▶ Exemplo: Armazenamento em disco + Filtragem de arquivos

JAVASCRIPT
const multer = require('multer');
const path = require('path');

const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'uploads/');
  },
  filename: (req, file, cb) => {
    const ext = path.extname(file.originalname);
    const uniqueName = `${Date.now()}-${Math.round(Math.random() * 1e9)}${ext}`;
    cb(null, uniqueName);
  }
});

const fileFilter = (req, file, cb) => {
  const allowed = /\.(jpg|jpeg|png|gif|webp)$/i;
  if (allowed.test(path.extname(file.originalname))) {
    cb(null, true);
  } else {
    cb(new Error('Supports only jpg/png/gif/webp Format'), false);
  }
};

const upload = multer({
  storage,
  fileFilter,
  limits: { fileSize: 5 * 1024 * 1024 }
});

app.post('/avatar', upload.single('avatar'), (req, res) => {
  if (!req.file) return res.status(400).json({ code: 400, data: null, message: 'Please upload the file' });
  res.json({
    code: 0,
    data: { url: `/uploads/${req.file.filename}`, size: req.file.size },
    message: 'ok'
  });
});

app.post('/photos', upload.array('photos', 9), (req, res) => {
  const urls = req.files.map(f => `/uploads/${f.filename}`);
  res.json({ code: 0, data: urls, message: 'ok' });
});
▶ Experimente

(2) Comparação das opções de configuração do multer

Item de configuração Tipo Valor padrão Descrição Exemplo
storage Mecanismo de armazenamento memoryStorage Mecanismo de armazenamento multer.diskStorage({...})
dest string Diretório de destino (este ou “storage”) 'uploads/'
fileFilter Função Permitir tudo Callback do filtro de arquivos (req,file,cb)=>{...}
limits.fileSize número Sem limite Número máximo de bytes por arquivo 5*1024*1024
limits.files número Ilimitado Número máximo de arquivos para envio 9
limits.fields número Ilimitado Número máximo de campos que não sejam de arquivo 10
limits.fieldSize número 1 MB Número máximo de bytes para campos que não sejam de arquivo 1024*100
limits.parts número Ilimitado Número total de partes de uma mensagem com várias partes 20

(3) armazenamento em memória vs. armazenamento em disco

Dimensão armazenamento em memória armazenamento em disco
Local de armazenamento Memória (buffer) Arquivo em disco
req.file Imóvel buffer path, filename
Casos de uso Arquivos pequenos, processamento em tempo real (por exemplo, compactação e salvamento de imagens) Arquivos grandes, armazenamento persistente
Desempenho Rápido (sem E/S de disco) Um pouco mais lento (requer gravação em disco)
Risco de memória Erro de falta de memória (OOM) ao lidar com arquivos grandes ou em condições de alta simultaneidade Nenhum
Reiniciar o Lost Sim Não
Controle do nome do arquivo Não necessário Requer callback personalizado filename


5. Configurando o serviço de arquivos estáticos

(1) Detalhes do express.static

▶ Exemplo: Serviço estático com vários diretórios + controle de cache

JAVASCRIPT
const express = require('express');
const app = express();

app.use('/static', express.static('public', {
  maxAge: '7d',
  etag: true,
  lastModified: true,
  immutable: true,
  setHeaders: (res, filePath) => {
    if (filePath.endsWith('.html')) {
      res.setHeader('Cache-Control', 'no-cache');
    }
    if (filePath.match(/\.(jpg|png|gif|webp|svg)$/)) {
      res.setHeader('Cache-Control', 'public, max-age=2592000, immutable');
    }
  }
}));

app.use('/uploads', express.static('uploads', {
  maxAge: '30d',
  dotfiles: 'deny'
}));
▶ Experimente

(2) Considerações de segurança para serviços estáticos



6. Formato padronizado de resposta

(1) Especificação {código, dados, mensagem}

▶ Exemplo: Middleware de encapsulamento de resposta

JAVASCRIPT
const responseHandler = (req, res, next) => {
  res.success = (data = null, message = 'ok') => {
    res.json({ code: 0, data, message });
  };
  res.fail = (message = 'Operation Failed', code = -1, data = null) => {
    res.json({ code, data, message });
  };
  res.paginate = (list, total, page, pageSize) => {
    res.json({
      code: 0,
      data: { list, total, page, pageSize, totalPages: Math.ceil(total / pageSize) },
      message: 'ok'
    });
  };
  next();
};

app.use(responseHandler);

app.get('/users', (req, res) => {
  const users = getUserList();
  res.success(users);
});

app.get('/users/:id', (req, res, next) => {
  const user = findUser(req.params.id);
  if (!user) return res.fail('User does not exist', 404);
  res.success(user);
});
▶ Experimente

(2) As 6 Leis de Ferro da Internacionalização



7. Limitação de taxa (Throttling)

(1) Configuração do express-rate-limit

▶ Exemplo: Estratégia de limitação de taxa em níveis

JAVASCRIPT
const rateLimit = require('express-rate-limit');

const globalLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  standardHeaders: true,
  legacyHeaders: false,
  message: { code: 429, data: null, message: 'Too many requests,Please try again later.' }
});

const apiLimiter = rateLimit({
  windowMs: 60 * 1000,
  max: 30,
  message: { code: 429, data: null, message: 'API Exceeded the call limit' }
});

const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,
  skipSuccessfulRequests: true,
  message: { code: 429, data: null, message: 'Too many failed login attempts, please try again in 15 minutes' }
});

app.use(globalLimiter);
app.use('/api/', apiLimiter);
app.use('/auth/login', loginLimiter);
▶ Experimente

(2) Lista de middleware de segurança

Middleware Finalidade Comportamento padrão Configuração principal Pacote npm
helmet Cabeçalhos de segurança HTTP Definir 15 cabeçalhos de segurança contentSecurityPolicy, hsts helmet
express-rate-limit Limitação de taxa windowMs, max express-rate-limit
cors Controle entre domínios Rejeitar todas as solicitações entre domínios origin, methods, credentials cors
express-validator Validação de entrada body(), query(), param() express-validator
multer Envio de arquivo limits, fileFilter multer
express-mongo-sanitize Injeção NoSQL Remover $ e . express-mongo-sanitize
xss-clean Limpeza de XSS Escapamento de HTML xss-clean
hpp Poluição de parâmetros Usar o último valor whitelist hpp
compression compactação gzip — compactação threshold, level


8. Configuração do ambiente: dotenv

(1) Usos básicos do dotenv

▶ Exemplo: Carregamento hierárquico de variáveis de ambiente

JAVASCRIPT
const dotenv = require('dotenv');
const path = require('path');

dotenv.config({ path: path.resolve(process.env.NODE_ENV ? `.env.${process.env.NODE_ENV}` : '.env') });

const config = {
  port: parseInt(process.env.PORT, 10) || 3000,
  env: process.env.NODE_ENV || 'development',
  db: {
    host: process.env.DB_HOST || 'localhost',
    port: parseInt(process.env.DB_PORT, 10) || 27017,
    name: process.env.DB_NAME || 'myapp_dev'
  },
  jwt: {
    secret: process.env.JWT_SECRET,
    expiresIn: process.env.JWT_EXPIRES_IN || '7d'
  },
  upload: {
    maxFileSize: parseInt(process.env.MAX_FILE_SIZE, 10) || 5 * 1024 * 1024,
    allowedTypes: (process.env.ALLOWED_TYPES || 'jpg,jpeg,png,gif,webp').split(',')
  },
  rateLimit: {
    windowMs: parseInt(process.env.RATE_WINDOW_MS, 10) || 15 * 60 * 1000,
    max: parseInt(process.env.RATE_MAX, 10) || 100
  }
};

module.exports = config;
▶ Experimente

▶ Exemplo:(2) Melhores práticas para arquivos .env

TEXT 📖 Somente leitura
# .env              ← Default(Development),Do not submit to Git
# .env.production   ← Production Environment,Strictly Restrict Access
# .env.test         ← Test Environment

PORT=3000
NODE_ENV=development

DB_HOST=localhost
DB_PORT=27017
DB_NAME=myapp_dev

JWT_SECRET=your-super-secret-key-change-in-production
JWT_EXPIRES_IN=7d

MAX_FILE_SIZE=5242880
ALLOWED_TYPES=jpg,jpeg,png,gif,webp

RATE_WINDOW_MS=900000
RATE_MAX=100
TEXT 📖 Somente leitura
# .gitignore Must include
.env
.env.*
!.env.example


9. O processo completo de atendimento a solicitações urgentes

▶ Exemplo:(1) Fluxograma da Sereia

100%
flowchart TD
    A[Client Request] --> B[helmet Safety Head]
    B --> C[cors Cross-domain validation]
    C --> D[rate-limit Traffic Flow Inspection]
    D -->|429| E[Return Rate-Limiting Response]
    D -->|Through| F[express.json Analysis Body]
    F --> G[multer File Upload Processing]
    G -->|The file is too large/Format error| H[next error]
    G -->|Through| I[express-validator Verification]
    I -->|Verification Failed| J[Back 400 Validation Error]
    I -->|Through| K[Business Routing Processing]
    K -->|Business Error| L[next error]
    K -->|Success| M[Unified Response Encapsulation success/fail]
    M --> N[Back JSON Response]
    L --> O[Global Error Handling Middleware]
    H --> O
    O --> P[Standardized Error Responses code/data/message]
    P --> N

    style E fill:#f66,stroke:#333,color:#fff
    style J fill:#f66,stroke:#333,color:#fff
    style P fill:#f66,stroke:#333,color:#fff
    style N fill:#6f6,stroke:#333

(2) Princípios para a ordem de carregamento do middleware



10. Exemplo abrangente: Configuração completa da segurança da API Express

▶ Exemplo: Servidor Express para produção

JAVASCRIPT 📖 Somente leitura
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const rateLimit = require('express-rate-limit');
const multer = require('multer');
const { body, param, validationResult } = require('express-validator');
const compression = require('compression');
const path = require('path');

const config = require('./config');

const app = express();

app.use(helmet());
app.use(cors({ origin: config.corsOrigin, credentials: true }));
app.use(compression({ threshold: 1024 }));
app.use(express.json({ limit: '10kb' }));
app.use(express.urlencoded({ extended: true }));

const globalLimiter = rateLimit({
  windowMs: config.rateLimit.windowMs,
  max: config.rateLimit.max,
  standardHeaders: true,
  legacyHeaders: false,
  message: { code: 429, data: null, message: 'error.rate_limited' }
});
app.use(globalLimiter);

const upload = multer({
  storage: multer.diskStorage({
    destination: 'uploads/',
    filename: (req, file, cb) => {
      const ext = path.extname(file.originalname);
      cb(null, `${Date.now()}-${Math.random().toString(36).slice(2)}${ext}`);
    }
  }),
  fileFilter: (req, file, cb) => {
    const ext = path.extname(file.originalname).toLowerCase();
    if (config.upload.allowedTypes.some(t => `.${t}` === ext)) {
      cb(null, true);
    } else {
      cb(new Error('error.invalid_file_type'), false);
    }
  },
  limits: { fileSize: config.upload.maxFileSize, files: 5 }
});

const responseHandler = (req, res, next) => {
  res.success = (data = null, message = 'ok') => {
    res.json({ code: 0, data, message });
  };
  res.fail = (message = 'error.internal', code = -1, data = null) => {
    res.json({ code, data, message });
  };
  next();
};
app.use(responseHandler);

const validate = (rules) => [
  ...rules,
  (req, res, next) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({
        code: 400,
        data: errors.array().map(e => ({ field: e.path, message: e.msg })),
        message: 'error.validation_failed'
      });
    }
    next();
  }
];

app.post('/api/users',
  validate([
    body('username').isLength({ min: 3, max: 20 }).withMessage('error.username_length'),
    body('email').isEmail().withMessage('error.invalid_email').normalizeEmail(),
    body('password').isLength({ min: 8 }).withMessage('error.password_length')
  ]),
  (req, res) => {
    const user = createUser(req.body);
    res.success(user, 'ok');
  }
);

app.post('/api/upload',
  upload.array('files', 5),
  (req, res) => {
    if (!req.files || req.files.length === 0) {
      return res.fail('error.no_file_uploaded', 400);
    }
    const urls = req.files.map(f => `/uploads/${f.filename}`);
    res.success(urls, 'ok');
  }
);

app.get('/api/users/:id',
  validate([param('id').isMongoId().withMessage('error.invalid_id')]),
  (req, res) => {
    const user = findUser(req.params.id);
    if (!user) return res.fail('error.user_not_found', 404);
    res.success(user);
  }
);

app.use('/uploads', express.static('uploads', { maxAge: '30d', dotfiles: 'deny' }));

app.use((req, res) => {
  res.status(404).json({ code: 404, data: null, message: 'error.not_found' });
});

class AppError extends Error {
  constructor(message, status) {
    super(message);
    this.status = status;
    this.isOperational = true;
  }
}

app.use((err, req, res, next) => {
  if (err instanceof multer.MulterError) {
    if (err.code === 'LIMIT_FILE_SIZE') {
      return res.status(413).json({ code: 413, data: null, message: 'error.file_too_large' });
    }
    return res.status(400).json({ code: 400, data: null, message: 'error.upload_failed' });
  }
  const status = err.status || 500;
  const message = err.isOperational ? err.message : 'error.internal';
  if (config.env === 'development') console.error(err.stack);
  res.status(status).json({ code: status, data: null, message });
});

app.listen(config.port, () => {
  console.log(`Server running on port ${config.port} [${config.env}]`);
});
120 linhas de lógica (limite de 40, somente leitura)

11. Resumo desta aula


❓ Perguntas Frequentes

P: O que é executado primeiro: o middleware ou as rotas? R: Eles são executados na ordem em que são registrados. O middleware global registrado com app.use é executado antes do middleware das rotas, e o middleware dentro das rotas é executado na ordem definida nas rotas.

P: Como se implementa o controle de versão da API? R: Existem três métodos comuns: caminhos de URL (/v1/users), cabeçalhos de solicitação (Accept: application/vnd.api.v1+json) e parâmetros de consulta (?version=1).

P: Um middleware de tratamento de erros requer 4 parâmetros? R: Sim. O Express identifica os middlewares de tratamento de erros pelo número de parâmetros; eles devem ser assinados como (err, req, res, next); caso contrário, serão tratados como um middleware comum.

P: Como faço para implementar a limitação da taxa de solicitações? R: Use o middleware express-rate-limit, defina a janela de tempo windowMs e o número de solicitações max, e retorne um código de status 429 quando o limite for excedido.

P: Como posso otimizar o desempenho do serviço de arquivos estáticos? R: Em um ambiente de produção, use o Nginx ou uma CDN para hospedar arquivos estáticos e deixe que o Express lide apenas com APIs dinâmicas; em um ambiente de desenvolvimento, você pode usar o cache maxAge fornecido pelo express.static.


📖 Resumo

📝 Exercícios

  1. Adicione um middleware global de tratamento de erros a um projeto Express existente para distinguir entre erros de negócio (isOperational) e erros desconhecidos; no caso de erros desconhecidos, retorne uma mensagem genérica sem expor o rastreamento da pilha.
  2. Use express-validator para escrever uma cadeia de validação completa para a API de registro de usuários (nome de usuário, e-mail, nível de segurança da senha e confirmação da senha) e retorne um array de erros no nível dos campos caso a validação falhe.
  3. Configure o multer para permitir o envio de fotos de perfil (um único arquivo, limitado a 2 MB, somente nos formatos JPG ou PNG). Retorne a URL do arquivo caso o envio seja bem-sucedido e, caso falhe, retorne o motivo específico da falha.
  4. Adicione o express-rate-limit para a limitação de taxa em níveis: 100 solicitações no total a cada 15 minutos, 30 solicitações de API por minuto e 5 tentativas de login malsucedidas a cada 15 minutos.
  5. Crie um arquivo chamado .env.example que liste todas as variáveis de ambiente e seus valores padrão, e certifique-se de que .env tenha sido adicionado a .gitignore.

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%