Saltar al contenido

Cómo construir APIs escalables con Route Handlers de Next.js

Guía completa para diseñar APIs escalables y listas para producción con Route Handlers de Next.js, TypeScript, validación y manejo de errores.

2 min de lectura
Diagrama de arquitectura de API que muestra el flujo de peticiones a través de los route handlers de Next.js

Introducción

Construir APIs que escalen no es solo cuestión de manejar más peticiones: se trata de diseñar sistemas que sigan siendo mantenibles, testeables y fiables a medida que tu aplicación crece. En este post repaso los patrones que uso al construir APIs de producción con Route Handlers de Next.js.

¿Por qué Route Handlers de Next.js?

El App Router de Next.js introdujo los Route Handlers como una forma de primera clase para construir endpoints de API. Ofrecen varias ventajas sobre las API routes heredadas:

  • Colocación — Las rutas de API viven junto a tus páginas en el directorio app
  • APIs estándar web — Construidas sobre los objetos Request y Response
  • Soporte de Edge Runtime — Despliega más cerca de tus usuarios
  • Estático y dinámico — Elige la estrategia de renderizado adecuada por ruta

Configurando una estructura escalable

Así organizo las rutas de API en proyectos grandes:

tstypescript
// src/app/api/v1/posts/route.ts
import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
 
const createPostSchema = z.object({
  title: z.string().min(1).max(200),
  content: z.string().min(1),
  tags: z.array(z.string()).max(10).optional(),
});
 
export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    const validated = createPostSchema.parse(body);
 
    // Process the validated data
    const post = await createPost(validated);
 
    return NextResponse.json(post, { status: 201 });
  } catch (error) {
    if (error instanceof z.ZodError) {
      return NextResponse.json(
        { errors: error.flatten().fieldErrors },
        { status: 400 },
      );
    }
    return NextResponse.json(
      { error: "Internal server error" },
      { status: 500 },
    );
  }
}

Validación en los límites

Un principio que sigo a rajatabla: valida en los límites del sistema, confía internamente. Toda entrada externa —cuerpos de petición, parámetros de consulta, cabeceras— se valida con esquemas de Zod antes de tocar la lógica de negocio.

tstypescript
const querySchema = z.object({
  page: z.coerce.number().int().positive().default(1),
  limit: z.coerce.number().int().min(1).max(100).default(20),
  sort: z.enum(["date", "title", "popularity"]).default("date"),
});
 
export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const query = querySchema.parse(Object.fromEntries(searchParams));
 
  const posts = await getPosts(query);
  return NextResponse.json(posts);
}

Patrones de manejo de errores

Una estrategia de manejo de errores consistente hace que la depuración y la integración con clientes sean mucho más sencillas:

tstypescript
class AppError extends Error {
  constructor(
    message: string,
    public statusCode: number,
    public code: string,
  ) {
    super(message);
  }
}
 
function handleError(error: unknown): NextResponse {
  if (error instanceof AppError) {
    return NextResponse.json(
      { error: error.message, code: error.code },
      { status: error.statusCode },
    );
  }
 
  console.error("Unhandled error:", error);
  return NextResponse.json(
    { error: "Internal server error", code: "INTERNAL_ERROR" },
    { status: 500 },
  );
}

Limitación de peticiones (rate limiting)

Para APIs en producción, el rate limiting es imprescindible. Aquí tienes un enfoque sencillo en memoria adecuado para serverless:

tstypescript
const rateLimitMap = new Map<string, { count: number; resetAt: number }>();
 
function rateLimit(ip: string, maxRequests = 60, windowMs = 60000): boolean {
  const now = Date.now();
  const entry = rateLimitMap.get(ip);
 
  if (!entry || now > entry.resetAt) {
    rateLimitMap.set(ip, { count: 1, resetAt: now + windowMs });
    return true;
  }
 
  if (entry.count >= maxRequests) return false;
 
  entry.count++;
  return true;
}

Conclusiones clave

  1. Valida en los límites — Usa esquemas de Zod para toda entrada externa
  2. Respuestas de error consistentes — Estandariza el formato de errores desde el principio
  3. Aplica rate limiting a todo — Protege tus APIs contra abusos
  4. Tipa todo — TypeScript detecta errores antes de que lleguen a producción
  5. Mantén los handlers ligeros — Extrae la lógica de negocio en módulos separados

Construir APIs escalables es un proceso iterativo. Empieza con estos patrones, mide tu uso real y optimiza donde los datos te lo indiquen.

Wilfredo Rujel

Wilfredo Rujel

Ingeniero de Software Full Stack

Compartir esta publicaciónX