Skip to main content
1

Mostre o monólito para o Devin

Você conhece aquele arquivo — um único router do Express que foi crescendo por dezoito meses. Todo endpoint de todo domínio fica em src/routes/index.ts: cadastro de usuário ao lado de webhooks de pagamento ao lado de busca de produto. Verificações de autenticação inline são copiadas e coladas em 40 handlers. Ninguém quer mexer nele porque uma alteração na lógica de pedidos pode quebrar os endpoints de usuário trezentas linhas acima.É mais ou menos assim que o topo do arquivo costuma ser:
src/routes/index.ts (before — 2,000 lines)
import { Router } from "express";
import { db } from "../db";
import { stripe } from "../lib/stripe";
import { sendEmail } from "../lib/email";
import { logger } from "../lib/logger";

const router = Router();

// ---- Middleware de autenticação (copiado em todo lugar) ----
const requireAuth = (req, res, next) => {
  const token = req.headers.authorization?.split(" ")[1];
  if (!token) return res.status(401).json({ error: "Unauthorized" });
  try {
    req.user = jwt.verify(token, process.env.JWT_SECRET);
    next();
  } catch {
    res.status(401).json({ error: "Invalid token" });
  }
};

// ---- Rotas de usuário ----
router.post("/users/register", async (req, res) => { /* 45 lines */ });
router.post("/users/login", async (req, res) => { /* 30 lines */ });
router.get("/users/:id", requireAuth, async (req, res) => { /* 25 lines */ });
router.put("/users/:id", requireAuth, async (req, res) => { /* 40 lines */ });

// ---- Rotas de produto ----
router.get("/products", async (req, res) => { /* 60 lines */ });
router.get("/products/:id", async (req, res) => { /* 35 lines */ });
router.post("/products", requireAuth, async (req, res) => { /* 50 lines */ });

// ---- Rotas de pedido ----
router.post("/orders", requireAuth, async (req, res) => { /* 80 lines */ });
router.get("/orders/:id", requireAuth, async (req, res) => { /* 40 lines */ });
router.post("/orders/:id/refund", requireAuth, async (req, res) => { /* 55 lines */ });

// ---- Rotas de pagamento ----
router.post("/payments/webhook", async (req, res) => { /* 90 lines */ });
router.get("/payments/:id/status", requireAuth, async (req, res) => { /* 30 lines */ });

// ... mais 1.400 linhas de handlers misturados, validação inline,
//     e verificações de autenticação duplicadas

export default router;
Diga ao Devin exatamente como você quer que a estrutura desejada fique.
2

Oriente o Devin com convenções

Devin lê sua codebase para inferir padrões, mas é na refatoração que as entradas de Knowledge geram mais impacto. Adicione entradas com as convenções que Devin deve seguir:
  • Padrões de router — “Cada router de domínio usa Router() e é montado com app.use('/domain', domainRouter) na raiz”
  • Middleware — “O middleware de autenticação fica em src/middleware/ e é sempre importado, nunca definido inline”
  • Tratamento de erros — “Todos os route handlers usam nosso wrapper asyncHandler de src/lib/asyncHandler.ts — nunca try/catch direto”
Direcionar o Devin a um router já bem estruturado na sua codebase muitas vezes produz resultados melhores do que descrever convenções do zero. Adicione uma linha como “Siga o padrão em src/routes/admin.ts, que já está claramente separado” ao seu prompt.Você também pode pedir ao Devin para gerar entradas de Knowledge para você — basta descrever suas convenções e ele criará entradas bem estruturadas que você pode revisar e salvar.
3

Revise o PR do Devin

Devin mapeia todos os endpoints, rastreia o grafo de importações, extrai a lógica compartilhada, cria os arquivos de domínio, reconfigura o roteador raiz e executa sua suíte de testes. É assim que o PR geralmente fica:
refactor: Split monolithic router into domain-specific route files

Files changed (8):
  src/routes/users.ts        — 4 endpoints, auth middleware imported
  src/routes/products.ts     — 3 endpoints, public + auth-protected
  src/routes/orders.ts       — 3 endpoints, all auth-protected
  src/routes/payments.ts     — 2 endpoints, webhook + status check
  src/routes/index.ts        — root router mounting all domains
  src/middleware/auth.ts      — requireAuth, requireAdmin (extracted)
  src/middleware/validate.ts  — validateBody schema middleware
  Old src/routes/index.ts     — 2,000-line monolith replaced

All 112 API tests pass. No URL changes.
Veja como fica o roteador raiz limpo após a separação:
src/routes/index.ts (after — 15 lines)
import { Router } from "express";
import usersRouter from "./users";
import productsRouter from "./products";
import ordersRouter from "./orders";
import paymentsRouter from "./payments";

const router = Router();

router.use("/users", usersRouter);
router.use("/products", productsRouter);
router.use("/orders", ordersRouter);
router.use("/payments", paymentsRouter);

export default router;
E um arquivo de rota de domínio em que o middleware compartilhado é importado corretamente:
src/routes/orders.ts (after — excerpt)
import { Router } from "express";
import { requireAuth } from "../middleware/auth";
import { validateBody } from "../middleware/validate";
import { createOrderSchema, refundSchema } from "../schemas/orders";
import { db } from "../db";

const router = Router();

router.post("/", requireAuth, validateBody(createOrderSchema),
  async (req, res) => {
    const order = await db.orders.create({
      userId: req.user.id,
      items: req.body.items,
      total: req.body.total,
    });
    res.status(201).json(order);
  }
);

router.get("/:id", requireAuth, async (req, res) => {
  const order = await db.orders.findByPk(req.params.id);
  if (!order) return res.status(404).json({ error: "Order not found" });
  res.json(order);
});

router.post("/:id/refund", requireAuth, validateBody(refundSchema),
  async (req, res) => {
    // lógica de reembolso extraída de forma limpa do monólito
  }
);

export default router;
Cada caminho de URL permanece o mesmo — /orders agora é tratado pelo ordersRouter montado em /orders, de forma que os clientes e testes existentes continuam funcionando sem nenhuma alteração.
4

(Opcional) Faça o checkout da branch e teste localmente

Para uma refatoração estrutural como esta, vale a pena puxar a branch e verificar localmente antes de fazer o merge. Abra no Windsurf ou na sua IDE preferida, inicie o app e acesse alguns endpoints para confirmar que o roteamento, o middleware e o tratamento de erros continuam se comportando exatamente como antes.
git fetch origin && git checkout devin/refactor-router-split
npm install && npm test
npm run dev
# Teste alguns endpoints: curl http://localhost:3000/users, /orders, /payments
Se notar algo estranho, deixe um comentário no PR — Devin vai detectar e aplicar uma correção.
5

Continuar limpeza

Depois que o router for dividido, use prompts de acompanhamento para ampliar a refatoração: