Zod en los límites
TypeScript no puede validar cuerpos HTTP, cadenas de consulta o variables de entorno en tiempo de ejecución. Los esquemas Zod en los límites del sistema convierten la entrada desconocida en datos tipados y confiables.
Busca en todas las páginas de la documentación
TypeScript no puede validar cuerpos HTTP, cadenas de consulta o variables de entorno en tiempo de ejecución. Los esquemas Zod en los límites del sistema convierten la entrada desconocida en datos tipados y confiables.
import { z } from 'zod';
const EnvSchema = z.object({
PORT: z.coerce.number().default(3000),
DATABASE_URL: z.string().url(),
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
});
export const env = EnvSchema.parse(process.env);const CreateUser = z.object({
email: z.string().email(),
name: z.string().min(1).max(100),
});
type CreateUser = z.infer<typeof CreateUser>;Cuándo usarlo:
process.envas MyType inseguras en req.bodyimport { z } from 'zod';
import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';
const EnvSchema = z.object({
PORT: z.coerce.number().default(3000),
});
const env = EnvSchema.parse(process.env);
const CreateItem = z.object({
name: z.string().min(1),
qty: z.number().int().positive(),
});
async function readJson(req: IncomingMessage): Promise<unknown> {
const chunks: Buffer[] = [];
for await (const chunk of req) chunks.push(chunk as Buffer);
return JSON.parse(Buffer.concat(chunks).toString('utf8'));
}
function sendJson(res: ServerResponse, status: number, body: unknown): void {
res.writeHead(status, { 'content-type': 'application/json' });
res.end(JSON.stringify(body));
}
const server = createServer(async (req, res) => {
if (req.method === 'POST' && req.url === '/items') {
try {
const raw = await readJson(req);
const item = CreateItem.parse(raw);
sendJson(res, 201, { id: '1', ...item });
} catch (err) {
if (err instanceof z.ZodError) {
sendJson(res, 400, { error: 'validation_failed', issues: err.flatten() });
return;
}
throw err;
}
return;
}
sendJson(res, 404, { error: 'not_found' });
});
server.listen(env.PORT);Lo que esto demuestra:
parse lanza ZodError en caso de fallo: mapea a 400, no a 500z.coerce.number() maneja variables de entorno de cadena como "3000"z.infer deriva tipos de TypeScript del mismo esquema que en tiempo de ejecuciónflatten() produce errores a nivel de campo seguros para los clientesparse se ejecuta sincrónicamente. Mantén los esquemas modestos en rutas críticas o almacena en caché los esquemas compilados.safeParse devuelve { success, data | error } - estilo funcional sin try/catch.z.string().transform(s => s.trim())) se ejecutan después de la validación - documenta los efectos secundarios..refine()) expresan reglas entre campos - confirmación de contraseña, rangos de fechas.| Límite | Ubicación del esquema | Modo de fallo |
|---|---|---|
process.env | config/env.ts en la importación | process.exit(1) |
| Cuerpo HTTP | Manejador de ruta / middleware | 400 JSON |
| SDK de salida | Opcional - confía en los tipos del proveedor | Registro + interrupción del circuito |
import { z } from 'zod';
const IdParams = z.object({ id: z.string().uuid() });
// Patrón de middleware reutilizable
function parseParams<T extends z.ZodType>(schema: T, input: unknown): z.infer<T> {
return schema.parse(input);
}.parse en cargas útiles enormes - compila una vez, reutiliza los objetos de esquema. Solución: const Schema = z.object(...) a nivel de módulo.flatten() o un mapa de errores personalizado. Solución: registra el error completo solo en el servidor.z.infer, sin interface paralelo.undefined falla en esquemas estrictos. Solución: .optional(), .default(), o documenta las variables requeridas.z.string() para columnas HTML/JSON - la validación no es sanitización. Solución: escapa en la salida, SQL parametrizado.| Alternativa | Cuándo usar | Cuándo no usar |
|---|---|---|
| Esquema JSON de Fastify | Rendimiento nativo de Fastify | El ecosistema Zod ya es estándar |
| class-validator (NestJS) | DTOs de decorador en NestJS 11 | Servicio Express mínimo |
| Valibot | Tamaño de paquete más pequeño | El equipo ya estandarizó Zod |
| Guardas manuales | Scripts internos pequeños | Cualquier entrada HTTP externa |
Los tipos se borran en tiempo de compilación. Los atacantes envían JSON malformado; solo la validación en tiempo de ejecución te protege.
parse lanza una excepción, lo cual es bueno con try/catch en la capa HTTP. safeParse es para validación por lotes sin excepciones.
z.object({ page: z.coerce.number().default(1) }).parse(req.query) - convierte cadenas a números.
Sí, usa ConfigModule con una fábrica Zod personalizada o valida en main.ts antes de NestFactory.create.
CreateUser.partial() o .pick({ name: true }) para DTOs de parche.
Publica esquemas desde @acme/validation: el frontend usa el mismo paquete para la pre-validación del lado del cliente.
zod-to-openapi genera la especificación a partir de esquemas: una única fuente para documentación y validación.
Zod es lo suficientemente rápido para la mayoría de las API; perfila antes de micro-optimizar; almacena en caché las variables de entorno analizadas al inicio.
z.discriminatedUnion('type', [...]) para uniones etiquetadas, bueno para tipos de eventos de webhook.
Validación avanzada entre campos con rutas de problemas personalizadas; úsalo con moderación para mayor claridad.
Sí, en el límite del repositorio si el controlador devuelve unknown; no confíes solo en los tipos de ORM para columnas externas.
expect(() => Schema.parse(bad)).toThrow(z.ZodError) en node:test - accesorios inválidos basados en tablas.
Versiones de la pila: Esta página fue escrita para Node.js 24.18.0 (LTS activa), npm 10+, TypeScript 5.6+, Express 5, Fastify 5 y NestJS 11.
Revisado por Chris St. John·Última actualización: 16 jul 2026