//
Busca en todas las páginas de la documentación
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Tipifica manejadores de rutas de App Router de Next.js con NextRequest, NextResponse, y parámetros de ruta tipificados. Crea endpoints de API seguros de tipos con contratos de request/response apropiados.
// app/api/users/route.ts
import { NextRequest, NextResponse } from "next/server";
type User = {
id: string;
name: string;
email: string;
};
type CreateUserBody = {
name: string;
email: string;
};
type ApiErrorResponse = {
error: string;
};
// Manejador GET
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const role = searchParams.get("role");
const users: User[] = await fetchUsersFromDb(role);
return NextResponse.json(users);
}
// Manejador POST con body tipificado
export async function POST(request: NextRequest) {
const body: unknown = await request.json();
if (!isValidCreateUserBody(body)) {
return NextResponse.json(
{ error: "Invalid request body" } satisfies ApiErrorResponse,
{ status: 400 }
);
}
const newUser: User = {
id: crypto.randomUUID(),
name: body.name,
email: body.email,
};
// ... guardar en base de datos
return NextResponse.json(newUser, { status: 201 });
}
// Type guard para el body de la request
function isValidCreateUserBody(body: unknown): body is CreateUserBody {
return (
typeof body === "object" &&
body !== null &&
"name" in body &&
"email" in body &&
typeof (body as CreateUserBody).name === "string" &&
typeof (body as CreateUserBody).email === "string"
);
}// app/api/users/[id]/route.ts - Parámetros de ruta dinámicos
import { NextRequest, NextResponse } from "next/server";
type RouteContext = {
params: Promise<{ id: string }>;
};
export async function GET(request: NextRequest, context: RouteContext) {
const { id } = await context.params;
const user = await findUserById(id);
if (!user) {
return NextResponse.json({ error: "User not found" }, { status: 404 });
}
return NextResponse.json(user);
}
export async function DELETE(request: NextRequest, context: RouteContext) {
const { id } = await context.params;
await deleteUserById(id);
return new NextResponse(null, { status: 204 });
}GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS.NextRequest extiende la Web API Request con métodos de conveniencia como nextUrl.searchParams para acceso a parámetros de query.NextResponse.json(data, init?) crea una respuesta JSON con headers Content-Type apropiados. El parámetro data acepta cualquier valor serializable.params de ruta están envueltos en una Promise y deben ser awaited. Esto se alinea con el patrón de params async usado en componentes de página.Validación con Zod en manejadores de rutas:
import { z } from "zod";
const CreateUserSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
});
export async function POST(request: NextRequest) {
const body = await request.json();
const parsed = CreateUserSchema.safeParse(body);
if (!parsed.success) {
return NextResponse.json(
{ error: "Validation failed", details: parsed.error.flatten() },
{ status: 400 }
);
}
const { name, email } = parsed.data; // Completamente tipificado
// ... crear usuario
}Configurando headers y cookies:
export async function GET(request: NextRequest) {
const token = request.cookies.get("session")?.value;
if (!token) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const response = NextResponse.json({ data: "protected" });
response.headers.set("X-Custom-Header", "value");
return response;
}Respuesta con streaming:
export async function GET() {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
for (const chunk of ["Hello", " ", "World"]) {
controller.enqueue(encoder.encode(chunk));
await new Promise((r) => setTimeout(r, 100));
}
controller.close();
},
});
return new NextResponse(stream, {
headers: { "Content-Type": "text/plain" },
});
}request.json() devuelve Promise<any>. Siempre valida o convierte el resultado antes de usarlo.NextResponse.json() acepta cualquier valor. Usa satisfies para verificar la forma: NextResponse.json({ error: "msg" } satisfies ApiErrorResponse)./api/users/123 tienen id como string. Parseálos explícitamente: parseInt(id, 10).params es una Promise. Olvidar hacer await te da un objeto Promise en lugar de los parámetros. Este es un problema común en la migración desde Next.js 14.request.json() lanza un error si el body no es JSON válido. Envuélvelo en un try/catch para robustez.new Response() en lugar de NextResponse funciona pero pierde características específicas de Next.js como los helpers cookies() y headers().| Enfoque | Ventajas | Desventajas |
|---|---|---|
| Manejadores de Rutas (App Router) | APIs estándar web, soporte de streaming | Validación manual requerida |
| Server Actions | Integración más estrecha con formularios React | No para consumidores de API de terceros |
| API Routes (Pages Router) | Patrón req/res familiar | Legacy, NextApiRequest es específico de Node |
| tRPC | Seguridad de tipos end-to-end | Dependencia adicional |
| API Externa (Express, Fastify) | Control completo, despliegue independiente | Servicio separado para mantener |
GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONSroute.ts.export async function GET(request: NextRequest) {
const role = request.nextUrl.searchParams.get("role");
// role es string | null
}Request.json() no tiene forma de conocer la forma de tu payload en tiempo de compilación.type RouteContext = {
params: Promise<{ id: string }>;
};
export async function GET(request: NextRequest, context: RouteContext) {
const { id } = await context.params;
}Promise y deben ser awaited.Promise en lugar de los parámetros actuales.return NextResponse.json({ error: "msg" } satisfies ApiErrorResponse);safeParse y verifica parsed.success:const parsed = CreateUserSchema.safeParse(await request.json());
if (!parsed.success) {
return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 });
}
const { name, email } = parsed.data; // Completamente tipificado/api/users/123 te da id como "123" (string).parseInt(id, 10) o Number(id) si necesitas un número.request.json() lanza un error en tiempo de ejecución.new Response() funciona pero carece de helpers específicos de Next.js como cookies() y headers().NextResponse extiende Response con métodos de conveniencia. Prefierelo para características de Next.js.function isValidBody(body: unknown): body is CreateUserBody {
return (
typeof body === "object" &&
body !== null &&
"name" in body &&
typeof (body as CreateUserBody).name === "string"
);
}Revisado por Chris St. John·Última actualización: 19 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥