EmpiezaTuSaaS

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 zod

Uso básico en API routes

Definir el schema

lib/schemas/user.ts
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

app/api/users/route.ts
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

lib/schemas/checkout.ts
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

lib/schemas/contact.ts
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

app/api/users/route.ts
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 schemas
lib/schemas/index.ts
export { 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:

app/actions/newsletter.ts
"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.

On this page