0
Promoção de Volta das Aulas ! Cursos com 25% OFF no menu "Cursos"
outubro 16, 2025
0
César Fontanella

Roteamento no Express: parâmetros, sub-rotas, controladores e boas práticas (na prática)

Resumo rápido: o roteador do Express combina método + caminho para decidir quem atende a requisição. Neste guia você vê parâmetros de rota e query, sub-routers, middleware por rota, padronização com controladores, ordem/prioridade, versionamento de API e armadilhas comuns — tudo com exemplos prontos.


Setup mínimo

mkdir express-routing && cd express-routing
npm init -y
npm i express
npm i -D nodemon

package.json (trecho):

{
  "type": "module",
  "scripts": { "dev": "nodemon src/server.js" },
  "dependencies": { "express": "^4.19.0" }
}

Estrutura:

src/
  server.js
  app.js
  routes/
    index.js
    users/
      index.js
      users.controller.js
      users.validators.js
    products/
      index.js
      products.controller.js

Conceitos-chave do roteamento

  • Combinação método + caminho: GET /users, POST /users, etc.
  • Parâmetros de rota: segmentos dinâmicos, ex.: /users/:id.
  • Query string: filtros e paginação, ex.: ?page=2&limit=20.
  • Sub-routers: módulos de rotas montados em um prefixo.
  • Ordem importa: rotas são avaliadas na sequência em que são registradas.
  • Middleware de rota: lógica aplicada só em endpoints específicos.

App base com sub-routers

src/app.js

import express from "express";
import indexRouter from "./routes/index.js";
import usersRouter from "./routes/users/index.js";
import productsRouter from "./routes/products/index.js";

const app = express();
app.use(express.json());

// Montagem dos módulos de rota
app.use("/", indexRouter);
app.use("/users", usersRouter);
app.use("/products", productsRouter);

// 404 e erros
app.use((req, res) => res.status(404).json({ error: "Rota não encontrada" }));
app.use((err, req, res, next) => {
  console.error("[ERRO]", err);
  res.status(err.status || 500).json({ error: err.message || "Erro interno" });
});

export default app;

src/server.js

import app from "./app.js";
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`http://localhost:${PORT}`));

Rotas básicas e saúde da API

src/routes/index.js

import { Router } from "express";
const router = Router();

router.get("/", (_req, res) => res.json({ ok: true }));
router.get("/health", (_req, res) => res.status(200).json({ status: "healthy" }));

export default router;

Parâmetros de rota e query string

src/routes/users/index.js

import { Router } from "express";
import * as ctrl from "./users.controller.js";
import { validateCreateUser } from "./users.validators.js";

const router = Router();

// Querystring: /users?page=2&limit=20
router.get("/", ctrl.list);

// Parâmetro de rota: /users/:id
router.get("/:id", ctrl.getById);

// Middleware de rota (validação) só no POST
router.post("/", validateCreateUser, ctrl.create);

export default router;

src/routes/users/users.controller.js

const DB = [{ id: "1", name: "Ada" }, { id: "2", name: "Linus" }];

export async function list(req, res) {
  const page = Number(req.query.page ?? 1);
  const limit = Number(req.query.limit ?? 10);
  const start = (page - 1) * limit;
  const data = DB.slice(start, start + limit);
  res.json({ page, limit, total: DB.length, data });
}

export async function getById(req, res, next) {
  const { id } = req.params;
  const user = DB.find(u => u.id === id);
  if (!user) return next({ status: 404, message: "Usuário não encontrado" });
  res.json(user);
}

export async function create(req, res) {
  const { name } = req.body;
  const newUser = { id: String(Date.now()), name };
  DB.push(newUser);
  res.status(201).json(newUser);
}

src/routes/users/users.validators.js

export function validateCreateUser(req, _res, next) {
  if (!req.body?.name) return next({ status: 400, message: "Campo 'name' é obrigatório" });
  next();
}

Padrões de roteamento úteis

1) Prefixos por domínio (sub-routers)

Mantenha /users e /products isolados em pastas próprias para facilitar testes e evolução.

2) Ordenação e “shadowing”

Rotas mais específicas primeiro:

router.get("/search", ctrl.search);   // específico
router.get("/:id", ctrl.getById);     // genérico

Se inverter, /users/search cairia no /:id.

3) Rota catch-all controlada (opcional)

Para métricas ou logs de 404 por módulo:

router.use((req, res) => res.status(404).json({ error: "Users: rota não encontrada" }));

4) Agrupamento por método

Agrupe rotas do mesmo recurso:

router
  .route("/:id")
  .get(ctrl.getById)
  .patch(ctrl.partialUpdate)
  .delete(ctrl.remove);

Versionamento de API sem dor

Monte versões com prefixo e reutilize controladores quando possível:

// app.js
import v1Users from "./routes/users/index.js";
import v2Users from "./routes/v2/users/index.js";

app.use("/v1/users", v1Users);
app.use("/v2/users", v2Users);

V2 pode trocar contratos (ex.: novos campos) sem quebrar clientes antigos.


Roteamento assíncrono com segurança

Use um wrapper para capturar exceções de async e enviar ao handler central:

export const wrap = (fn) => (req, res, next) =>
  Promise.resolve(fn(req, res, next)).catch(next);

Exemplo:

router.get("/:id", wrap(async (req, res) => {
  const user = await findUser(req.params.id); // I/O real
  if (!user) throw { status: 404, message: "Usuário não encontrado" };
  res.json(user);
}));

Boas práticas e armadilhas comuns

  • Não exponha dados sensíveis em parâmetros de rota (use IDs opacos).
  • Valide entrada (params, query, body) perto da rota.
  • Consistência de status/JSON: responda objetos, use 201 para criação, 400/404 para erros de cliente.
  • Evite lógica pesada no handler: extraia para controladores/serviços.
  • Documente caminhos e contratos (OpenAPI/Swagger ajuda muito).
  • Teste rotas críticas (integração) simulando req/res.