Route Handlers
Route Handlers te permiten crear API endpoints dentro de tu app NextJS. En vez de una API separada, tus endpoints viven junto a tu frontend.
Crear un endpoint
Crea un archivo route.ts dentro de una carpeta en app/:
code
app/
├── api/
│ ├── productos/
│ │ └── route.ts → GET/POST /api/productos
│ └── productos/
│ └── [id]/
│ └── route.ts → GET/PUT/DELETE /api/productos/123
GET
tsx
// app/api/productos/route.ts
import { NextResponse } from "next/server"
export async function GET() {
const productos = await db.producto.findMany()
return NextResponse.json(productos)
}
POST
tsx
export async function POST(request: Request) {
const body = await request.json()
const producto = await db.producto.create({
data: {
nombre: body.nombre,
precio: body.precio,
},
})
return NextResponse.json(producto, { status: 201 })
}
PUT y DELETE con params
tsx
// app/api/productos/[id]/route.ts
import { NextResponse } from "next/server"
export async function GET(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
const producto = await db.producto.findUnique({ where: { id } })
if (!producto) {
return NextResponse.json(
{ error: "Producto no encontrado" },
{ status: 404 }
)
}
return NextResponse.json(producto)
}
export async function PUT(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
const body = await request.json()
const producto = await db.producto.update({
where: { id },
data: body,
})
return NextResponse.json(producto)
}
export async function DELETE(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
await db.producto.delete({ where: { id } })
return new Response(null, { status: 204 })
}
v16Cambio En v16, params en route handlers tambien es Promise (igual que en pages).
Query parameters
tsx
// GET /api/productos?categoria=ropa&orden=precio
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const categoria = searchParams.get("categoria")
const orden = searchParams.get("orden") || "createdAt"
const productos = await db.producto.findMany({
where: categoria ? { categoria } : undefined,
orderBy: { [orden]: "asc" },
})
return NextResponse.json(productos)
}
Headers y cookies
tsx
import { NextResponse } from "next/server"
import { cookies, headers } from "next/headers"
export async function GET() {
// Leer headers
const headersList = await headers()
const userAgent = headersList.get("user-agent")
// Leer cookies
const cookieStore = await cookies()
const token = cookieStore.get("auth-token")
// Responder con headers custom
return NextResponse.json(
{ data: "ok" },
{
headers: {
"Cache-Control": "no-store",
"X-Custom-Header": "valor",
},
}
)
}
Validacion
Siempre valida los datos que llegan del cliente:
tsx
import { NextResponse } from "next/server"
import { z } from "zod"
const productoSchema = z.object({
nombre: z.string().min(2),
precio: z.number().positive(),
categoriaId: z.string().uuid(),
})
export async function POST(request: Request) {
const body = await request.json()
const result = productoSchema.safeParse(body)
if (!result.success) {
return NextResponse.json(
{ errors: result.error.flatten().fieldErrors },
{ status: 400 }
)
}
const producto = await db.producto.create({
data: result.data,
})
return NextResponse.json(producto, { status: 201 })
}
Ejemplo: API CRUD completa
tsx
// app/api/productos/route.ts
import { NextResponse } from "next/server"
import { z } from "zod"
const crearSchema = z.object({
nombre: z.string().min(2),
precio: z.number().positive(),
descripcion: z.string().optional(),
})
// GET /api/productos — Lista todos
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const page = Number(searchParams.get("page")) || 1
const limit = 20
const skip = (page - 1) * limit
const [productos, total] = await Promise.all([
db.producto.findMany({ skip, take: limit, orderBy: { createdAt: "desc" } }),
db.producto.count(),
])
return NextResponse.json({
data: productos,
pagination: {
page,
limit,
total,
pages: Math.ceil(total / limit),
},
})
}
// POST /api/productos — Crea uno nuevo
export async function POST(request: Request) {
const body = await request.json()
const result = crearSchema.safeParse(body)
if (!result.success) {
return NextResponse.json(
{ errors: result.error.flatten().fieldErrors },
{ status: 400 }
)
}
const producto = await db.producto.create({ data: result.data })
return NextResponse.json(producto, { status: 201 })
}
Route Handlers vs Server Actions
Usa Route Handlers cuando necesitas una API REST que otros servicios puedan consumir. Usa Server Actions cuando la mutation es solo desde tu frontend (formularios, botones).