Validación con Zod
Valida datos de entrada en tus API routes con Zod
Validación con Zod
Nunca confies en los datos que llegan del cliente. Zod te permite validar y tipar datos de entrada en una sola línea.
Por que validar
Sin validación, un atacante puede enviar datos malformados a tus API routes: strings donde esperas números, campos faltantes, inyeccion de SQL o XSS. Zod previene todo esto.
Instalacion
Zod ya viene incluido en el boilerplate:
npm install zodUso básico en API routes
Definir el schema
import { z } from "zod";
export const createUserSchema = z.object({
name: z.string().min(2, "El nombre debe tener al menos 2 caracteres"),
email: z.string().email("Email invalido"),
plan: z.enum(["starter", "pro", "lifetime"]),
});
export type CreateUserInput = z.infer<typeof createUserSchema>;Validar en la API route
import { NextRequest, NextResponse } from "next/server";
import { createUserSchema } from "@/lib/schemas/user";
export async function POST(request: NextRequest) {
const body = await request.json();
const result = createUserSchema.safeParse(body);
if (!result.success) {
return NextResponse.json(
{
error: "Datos invalidos",
details: result.error.flatten().fieldErrors,
},
{ status: 400 }
);
}
// result.data está tipado como CreateUserInput
const { name, email, plan } = result.data;
// ... crear usuario en la base de datos
return NextResponse.json({ success: true });
}Usa safeParse() en vez de parse(). Con safeParse puedes manejar errores y devolver un 400. Con parse, Zod lanza una excepción que resulta en un 500.
Schemas comunes para SaaS
Validar parametros de checkout
import { z } from "zod";
export const checkoutSchema = z.object({
priceId: z.string().startsWith("price_", "Price ID invalido"),
successUrl: z.string().url().optional(),
cancelUrl: z.string().url().optional(),
});Validar datos de contacto
import { z } from "zod";
export const contactSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
message: z.string().min(10).max(2000),
});Validar query params
import { z } from "zod";
const querySchema = z.object({
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().min(1).max(100).default(20),
search: z.string().optional(),
});
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const result = querySchema.safeParse({
page: searchParams.get("page"),
limit: searchParams.get("limit"),
search: searchParams.get("search"),
});
if (!result.success) {
return NextResponse.json({ error: "Parametros invalidos" }, { status: 400 });
}
const { page, limit, search } = result.data;
// ... consultar base de datos con páginacion
}Patron recomendado
Organiza tus schemas en lib/schemas/:
lib/
└── schemas/
├── user.ts
├── checkout.ts
├── contact.ts
└── index.ts # Re-exporta todos los schemasexport { createUserSchema, type CreateUserInput } from "./user";
export { checkoutSchema } from "./checkout";
export { contactSchema } from "./contact";Validación en Server Actions
Zod también funciona en Server Actions de Next.js:
"use server";
import { z } from "zod";
const newsletterSchema = z.object({
email: z.string().email("Email invalido"),
});
export async function subscribeToNewsletter(formData: FormData) {
const result = newsletterSchema.safeParse({
email: formData.get("email"),
});
if (!result.success) {
return { error: "Email invalido" };
}
// ... guardar email en la base de datos
return { success: true };
}Valida siempre en el servidor, incluso si ya validas en el cliente. La validación del cliente es solo UX, la del servidor es seguridad.