> ## Documentation Index
> Fetch the complete documentation index at: https://docs.devinenterprise.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Dividir un router monolítico de Express en rutas de dominio

export const UseCaseHero = ({title, description, prompt, category, features, devinUrl, agent, intent, playbookId, type}) => {
  const encodedPrompt = encodeURIComponent(prompt || '');
  const tag = 'docs-use-case-gallery';
  const utm = 'utm_source=docs&utm_medium=use-case-gallery&utm_campaign=hero-cta';
  const agentParams = (agent ? '&agent=' + agent : '') + (intent ? '&intent=' + intent : '') + (playbookId ? '&playbookId=' + playbookId : '');
  const devinHref = type === 'schedule' ? 'https://app.devin.ai/settings/schedules/create?' + utm + agentParams + (prompt ? '&prompt=' + encodedPrompt : '') : type === 'review' ? 'https://app.devin.ai/review?' + utm : agent === 'ada' ? 'https://app.devin.ai/search?' + utm + '&noSubmit=true' + (prompt ? '&prompt=' + encodedPrompt : '') : devinUrl ? devinUrl.includes('?') ? devinUrl + '&' + utm + agentParams : devinUrl + '?' + utm + agentParams : prompt ? 'https://app.devin.ai/?tags=' + tag + '&' + utm + agentParams + '&prompt=' + encodedPrompt : 'https://app.devin.ai/?' + utm + agentParams;
  const buttonLabel = type === 'schedule' ? 'Schedule in Devin ↗' : type === 'review' ? 'Set Up Devin Review ↗' : agent === 'advanced' ? 'Try in Devin ↗' : agent === 'dana' ? 'Try in Dana ↗' : agent === 'ada' ? 'Try in Ask Devin ↗' : 'Try in Devin ↗';
  const featureList = features ? features.split(',').map(f => f.trim()) : [];
  return <div className="uc-hero">
      <div className="uc-hero-inner">
        <div className="uc-hero-left">
          <h1 className="uc-hero-title">{title}</h1>
          <p className="uc-hero-desc">{description}</p>
          <div>
            <a href={devinHref} target="_blank" rel="noopener noreferrer" className="try-in-devin-btn">
              {buttonLabel}
            </a>
          </div>
        </div>
        <div className="uc-hero-meta">
          <div className="uc-meta-item">
            <span className="uc-meta-label">Author</span>
            <span className="uc-meta-value">Cognition</span>
          </div>
          <div className="uc-meta-item">
            <span className="uc-meta-label">Category</span>
            <span className="uc-meta-value">{category}</span>
          </div>
          {featureList.length > 0 && <div className="uc-meta-item">
              <span className="uc-meta-label">Features</span>
              <span className="uc-meta-value">{featureList.join(', ')}</span>
            </div>}
        </div>
      </div>
    </div>;
};

export const PromptBlock = ({children, type, agent, intent, playbookId}) => {
  var utm = 'utm_source=docs&utm_medium=use-case-gallery&utm_campaign=prompt-block';
  var tag = 'docs-use-case-gallery';
  var agentParams = (agent ? '&agent=' + agent : '') + (intent ? '&intent=' + intent : '') + (playbookId ? '&playbookId=' + playbookId : '');
  var label = type === 'schedule' ? 'Schedule in Devin' : type === 'playbook' ? 'Create Playbook' : type === 'knowledge' ? 'Add to Knowledge' : agent === 'advanced' ? 'Try in Devin' : agent === 'dana' ? 'Try in Dana' : agent === 'ada' ? 'Try in Ask Devin' : 'Try in Devin';
  var buildUrl = function (text) {
    var encoded = encodeURIComponent(text);
    if (type === 'schedule') return 'https://app.devin.ai/settings/schedules/create?' + utm + agentParams + '&prompt=' + encoded;
    if (type === 'playbook') return 'https://app.devin.ai/settings/playbooks/create?' + utm + '&body=' + encoded;
    if (type === 'knowledge') return 'https://app.devin.ai/knowledge?' + utm + '&body=' + encoded;
    if (agent === 'ada') return 'https://app.devin.ai/search?' + utm + '&noSubmit=true&prompt=' + encoded;
    return 'https://app.devin.ai/?tags=' + tag + '&' + utm + agentParams + '&prompt=' + encoded;
  };
  const ref = React.useRef(null);
  const [href, setHref] = React.useState('#');
  React.useEffect(() => {
    if (!ref.current) return;
    var codeEl = ref.current.querySelector('pre code');
    if (codeEl) {
      var text = codeEl.textContent.trim();
      if (text) setHref(buildUrl(text));
    }
    var header = ref.current.querySelector('[data-component-part="code-block-header"]');
    if (header && !header.querySelector('.prompt-block-devin-link')) {
      var link = document.createElement('a');
      link.href = href;
      link.target = '_blank';
      link.rel = 'noopener noreferrer';
      link.className = 'prompt-block-devin-link';
      link.style.cssText = 'display:inline-flex;align-items:center;gap:6px;text-decoration:none;color:#fff;font-size:11px;font-weight:500;padding:4px 10px;border-radius:6px;white-space:nowrap;background:#317CFF;transition:background 0.2s;margin-left:8px;';
      link.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg> ' + label;
      link.onmouseenter = function () {
        link.style.background = '#2968D9';
      };
      link.onmouseleave = function () {
        link.style.background = '#317CFF';
      };
      header.appendChild(link);
    }
    var existingLink = ref.current.querySelector('.prompt-block-devin-link');
    if (existingLink && href !== '#') existingLink.href = href;
  });
  return <div className="prompt-block" ref={ref}>{children}</div>;
};

<UseCaseHero title="Dividir un router monolítico de Express en rutas por dominio" description="Devin divide un router de Express de 2.000 líneas en archivos de rutas específicos por dominio con middleware compartido, luego actualiza cada import y verifica que todas las pruebas sigan pasando." prompt="Refactoriza src/routes/index.ts: es un router monolítico de Express de 2.000 líneas que gestiona usuarios, pedidos, productos y pagos. Divídelo en archivos de rutas específicos por dominio dentro de src/routes/, extrae el middleware compartido a src/middleware/ y vuelve a montar todo en un router raíz. Las 112 pruebas de la API deben seguir pasando." category="Calidad de código" features="" />

<div className="uc-detail-wrapper">
  <Tip>¿Prefieres no configurarlo manualmente? Pega un enlace a esta página en una sesión de Devin y pídele que lo configure todo por ti.</Tip>

  <Steps>
    <Step title="Muéstrale el monolito a Devin">
      Conoces ese archivo: un único router de Express que ha ido creciendo durante dieciocho meses. Todos los endpoints de cada dominio viven en `src/routes/index.ts`: el registro de usuarios junto a los webhooks de pagos junto a la búsqueda de productos. Las comprobaciones de autenticación incrustadas en el código están copiadas y pegadas en 40 controladores. Nadie quiere tocarlo porque un cambio en la lógica de pedidos podría romper los endpoints de usuario trescientas líneas más arriba.

      Así es como suele verse la parte superior del archivo:

      ```typescript src/routes/index.ts (before — 2,000 lines) theme={null}
      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 autenticación (copiado y pegado en todos lados) ----
      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" });
        }
      };

      // ---- Rutas de usuarios ----
      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 */ });

      // ---- Rutas de productos ----
      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 */ });

      // ---- Rutas de pedidos ----
      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 */ });

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

      // ... 1.400 líneas más de handlers mezclados, validación en línea,
      //     y verificaciones de autenticación duplicadas

      export default router;
      ```

      Dile a Devin exactamente cómo quieres que se vea la estructura objetivo.

      <PromptBlock>
        ```txt Divide el router monolítico en rutas por dominio theme={null}
        Refactoriza src/routes/index.ts: es un router monolítico de Express
        de 2.000 líneas que gestiona usuarios, pedidos, productos y pagos
        todo en un único archivo.

        Problemas:
        - El middleware de autenticación está copiado y pegado en cada handler en vez de compartirse
        - Validación de solicitudes inline (sin esquemas, solo if dispersos)
        - Dominios mezclados: registro de usuarios junto a webhooks de pagos
        - Sin separación entre la definición de rutas y la lógica de los handlers

        Estructura objetivo:
        - src/routes/users.ts      — todos los endpoints /users/*
        - src/routes/products.ts   — todos los endpoints /products/*
        - src/routes/orders.ts     — todos los endpoints /orders/*
        - src/routes/payments.ts   — todos los endpoints /payments/*
        - src/routes/index.ts      — router raíz que monta cada dominio
        - src/middleware/auth.ts    — requireAuth y requireAdmin compartidos
        - src/middleware/validate.ts — middleware compartido de validación de solicitudes

        Requisitos:
        - Extraer el middleware de autenticación a src/middleware/auth.ts e importarlo
        - Agrupar los handlers por dominio en archivos separados
        - Mantener idénticas todas las rutas (sin cambios de URL que rompan compatibilidad)
        - Las 112 pruebas de API existentes en tests/api/ deben pasar sin cambios
        ```
      </PromptBlock>
    </Step>

    <Step title="Guía a Devin con convenciones">
      Devin lee tu base de código para inferir patrones, pero la refactorización es donde las entradas de [Knowledge](/es/product-guides/knowledge) aportan más valor. Agrega entradas para las convenciones que Devin debe seguir:

      * **Patrones de router** — "Cada router de dominio usa `Router()` y se monta con `app.use('/domain', domainRouter)` en la raíz"
      * **Middleware** — "El middleware de autenticación vive en `src/middleware/` y siempre se importa, nunca se define en línea"
      * **Manejo de errores** — "Todos los manejadores de rutas usan nuestro wrapper `asyncHandler` de `src/lib/asyncHandler.ts` — nunca un try/catch directo"

      Indicarle a Devin un router ya bien estructurado en tu base de código suele producir mejores resultados que describir las convenciones desde cero. Agrega una línea como "Sigue el patrón en `src/routes/admin.ts`, que ya está claramente separado" a tu prompt.

      También puedes pedirle a Devin que genere entradas de Knowledge para ti: solo describe tus convenciones y creará entradas bien estructuradas que podrás revisar y guardar.
    </Step>

    <Step title="Revisar el PR de Devin">
      Devin mapea todos los endpoints, sigue el grafo de imports, extrae la lógica compartida, crea los archivos de dominio, reconfigura el router raíz y ejecuta tu suite de pruebas. Así es como suele verse el PR:

      ```
      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.
      ```

      Así queda el router raíz limpio después de la división:

      ```typescript src/routes/index.ts (after — 15 lines) theme={null}
      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;
      ```

      Y un archivo de rutas de dominio donde se importa correctamente el middleware compartido:

      ```typescript src/routes/orders.ts (after — excerpt) theme={null}
      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 limpia del monolito
        }
      );

      export default router;
      ```

      Cada ruta URL se mantiene idéntica: `/orders` ahora la gestiona `ordersRouter`, montado en `/orders`, así que los clientes y las pruebas existentes siguen funcionando sin cambios.
    </Step>

    <Step title="(Opcional) Haz checkout de la rama y prueba localmente">
      Para una refactorización estructural como esta, vale la pena hacer checkout de la rama y verificarla localmente antes de fusionarla. Pruébala en [Devin Desktop](https://windsurf.com) o en tu IDE preferido, levanta la aplicación y prueba algunos endpoints para confirmar que el enrutamiento, el middleware y el manejo de errores se comportan igual que antes.

      ```bash theme={null}
      git fetch origin && git checkout devin/refactor-router-split
      npm install && npm test
      npm run dev
      # Prueba algunos endpoints: curl http://localhost:3000/users, /orders, /payments
      ```

      Si ves algo raro, deja un comentario en el PR: Devin lo verá y subirá una corrección.
    </Step>

    <Step title="Continuar con la limpieza">
      Una vez que el router esté dividido, usa prompts de seguimiento para ampliar el refactor:

      <PromptBlock>
        ```txt Extrae la validación en línea a esquemas theme={null}
        Los handlers de ruta todavía tienen if-statements dispersos para la
        validación de entrada. Extráelos a esquemas de Zod en src/schemas/:
        un archivo de esquema por dominio (users.ts, orders.ts, etc.). Usa el
        middleware validateBody que acabamos de crear. Las 112 pruebas deben
        pasar.
        ```
      </PromptBlock>

      <PromptBlock>
        ```txt Agrega pruebas de integración para los nuevos archivos de rutas theme={null}
        Cada router de dominio ahora vive en su propio archivo, pero comparte
        la suite de pruebas original. Escribe pruebas de integración
        enfocadas para src/routes/orders.ts que cubran crear, obtener y
        reembolsar, incluidas fallas de autenticación, errores de validación
        y casos extremos como reembolsar un pedido ya reembolsado.
        ```
      </PromptBlock>
    </Step>
  </Steps>
</div>
