Compartir un único esquema Zod entre tu frontend de React y tu backend de Node
Aprenda cómo un único esquema Zod puede validar formularios de React, respuestas de API, cuerpos de solicitudes de Express y variables de entorno, al mismo tiempo que genera tipos TypeScript correspondientes.
La validación es esencial en toda aplicación, pero los equipos a menudo acaban implementándola de forma fragmentada: una biblioteca para el frontend, otra diferente para el backend, y las mismas reglas copiadas en varios lugares. Zod se ha convertido en la opción preferida de los desarrolladores de JavaScript y TypeScript precisamente porque evita ese desorden: escribes un único esquema, y dicho esquema verifica tus datos y genera el tipo correspondiente en TypeScript, listo para usarse de manera idéntica tanto en el navegador como en el servidor.
1. ¿Qué es Zod?
Zod es una biblioteca de esquemas creada desde cero teniendo en cuenta a TypeScript. Describes la estructura de tus datos una sola vez, y Zod utiliza esa descripción para verificar valores en tiempo de ejecución y para derivar automáticamente un tipo en TypeScript; no hay una interfaz separada que escribir ni riesgo de que se desincronice con tus reglas de validación.
import { z } from 'zod';
const UserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
age: z.number().int().positive().optional(),
});
type User = z.infer<typeof UserSchema>;
// { name: string; email: string; age?: number }
Un único esquema como este cubre tres funciones simultáneamente: documenta la estructura de los datos, la aplica en tiempo de ejecución y proporciona el tipo estático del cual dependen el editor y el compilador.
2. Por qué Zod supera a las alternativas
La ventaja principal es la inferencia automática de tipos. Bibliotecas como Yup o Joi suelen requerir que mantengas un esquema de validación junto con una interfaz de TypeScript escrita a mano, confiando en que ambas no se desalineen a medida que cambia el código. Zod elimina por completo ese riesgo: el tipo se deriva directamente del esquema, por lo que no hay nada que sincronizar.
Zod también es ligero y no depende de bibliotecas externas, lo que lo hace igualmente adecuado tanto en un paquete frontend preocupado por el tamaño como en un servicio Node.js. Su API encadenable y componible también significa que incluso las validaciones complejas —objetos anidados, uniones, campos que dependen unos de otros— permanecen claras y fáciles de leer, en lugar de convertirse en un laberinto de funciones auxiliares improvisadas.
3. Usar Zod en una aplicación React
3.1 Validación de formularios con React Hook Form
Zod se integra directamente en React Hook Form a través del paquete @hookform/resolvers.
npm install zod react-hook-form @hookform/resolvers
// components/SignupForm.tsx
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const SignupSchema = z.object({
name: z.string().min(2, 'Name is too short'),
email: z.string().email('Invalid email address'),
password: z.string().min(8, 'Password must be at least 8 characters'),
});
type SignupData = z.infer<typeof SignupSchema>;
export function SignupForm() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<SignupData>({
resolver: zodResolver(SignupSchema),
});
const onSubmit = (data: SignupData) => {
console.log('Valid data:', data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('name')} placeholder="Name" />
{errors.name && <p>{errors.name.message}</p>}
<input {...register('email')} placeholder="Email" />
{errors.email && <p>{errors.email.message}</p>}
<input type="password" {...register('password')} placeholder="Password" />
{errors.password && <p>{errors.password.message}</p>}
<button type="submit">Sign Up</button>
</form>
);
}
No existe un seguimiento manual del estado de errores ni declaraciones duplicadas de tipos que mantener; un único esquema se encarga de la validación, proporciona los mensajes de error que aparecen junto a cada campo y define al mismo tiempo el tipo en TypeScript del objeto data enviado.
3.2 Validación de respuestas API
Zod es igualmente útil en el lado de entrada de tu aplicación; por ejemplo, al verificar que los datos devueltos por una API coincidan realmente con lo esperado, ya que no puedes confiar únicamente en los tipos en tiempo de compilación para garantizarlo.
import { z } from 'zod';
const PostSchema = z.object({
id: z.number(),
title: z.string(),
body: z.string(),
});
const PostsResponseSchema = z.array(PostSchema);
async function fetchPosts() {
const res = await fetch('/api/posts');
const json = await res.json();
const result = PostsResponseSchema.safeParse(json);
if (!result.success) {
console.error(result.error.flatten());
throw new Error('Invalid API response shape');
}
return result.data; // fully typed Post[]
}
Este enfoque permite detectar respuestas mal formadas o inesperadas antes de que causen fallos silenciosos en su interfaz de usuario.
4. Uso de Zod en un backend Node.js / Express
4.1 Validación de los cuerpos de las solicitudes
npm install zod express
// schemas/user-schema.ts
import { z } from 'zod';
export const CreateUserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
age: z.number().int().positive().optional(),
});
export type CreateUserInput = z.infer<typeof CreateUserSchema>;
// middleware/validate.ts
import { Request, Response, NextFunction } from 'express';
import { ZodSchema } from 'zod';
export function validate(schema: ZodSchema) {
return (req: Request, res: Response, next: NextFunction) => {
const result = schema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ errors: result.error.flatten() });
}
req.body = result.data;
next();
};
}
// routes/users.ts
import { Router } from 'express';
import { validate } from '../middleware/validate';
import { CreateUserSchema } from '../schemas/user-schema';
const router = Router();
router.post('/users', validate(CreateUserSchema), (req, res) => {
// req.body is now guaranteed to match CreateUserInput
const { name, email, age } = req.body;
res.status(201).json({ name, email, age });
});
export default router;
Esta configuración proporciona a cada ruta un paso de validación uniforme y declarativo, con el manejo de errores centralizado en lugar de repetirse como comprobaciones if incrustadas en los controladores.
4.2 Validación de variables de entorno
Un uso subestimado pero poderoso de Zod es verificar process.env al iniciar la aplicación, de modo que una mala configuración provoque un fallo inmediato en lugar de un error confuso más adelante.
// config/env.ts
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']),
});
export const env = EnvSchema.parse(process.env);
Si falta alguna variable necesaria o tiene el formato incorrecto, el proceso se cae de inmediato con un mensaje de error legible; es mucho más fácil de diagnosticar que un fallo misterioso oculto dentro de una llamada a la base de datos.
5. La verdadera ventaja: un esquema único, compartido en toda la pila
Dado que los esquemas de Zod son simplemente valores de TypeScript, nada impide colocarlos en un paquete compartido —o en una carpeta compartida dentro de un monorepo— y reutilizar el esquema idéntico tanto en el cliente como en el servidor.
/packages
/shared
/schemas
user-schema.ts <-- used by both React app and Express API
/web (React/Next.js)
/api (Node/Express)
// packages/shared/schemas/user-schema.ts
import { z } from 'zod';
export const CreateUserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
age: z.number().int().positive().optional(),
});
export type CreateUserInput = z.infer<typeof CreateUserSchema>;
La aplicación React depende de este esquema para verificar el formulario de registro antes de enviarlo. La API Express también depende del mismo esquema para validar la carga recibida. Cuando el esquema evoluciona, por ejemplo, al añadir un campo obligatorio nuevo, ambas capas se adaptan juntas, y TypeScript detecta de inmediato cualquier código que aún no se haya ajustado a la nueva estructura. Esto evita toda una clase de errores en los que la validación del lado del cliente y del servidor terminan desalineándose con el paso del tiempo.
6. Mejores prácticas
- Utilice
safeParsecuando el fallo sea un resultado normal y esperado (entradas de formulario, respuestas de APIs de terceros), y reserveparse—que lanza excepciones— para casos que realmente nunca deberían ser inválidos, como las variables de entorno verificadas al iniciar la aplicación.
.transform() para limpiar los datos como parte de la validación en sí misma —eliminando espacios en blanco, forzando tipos— en lugar de ejecutar un proceso separado de normalización posteriormente.z.infer sobre interfaces escritas manualmente para cualquier elemento ya respaldado por un esquema, de modo que sus tipos y su lógica de validación nunca se desalineen.error.flatten() o error.format() en las respuestas de error de la API, lo que permite al código del frontend asociar fácilmente cada error con el campo de formulario correspondiente.7. Conclusión
Zod es más que una biblioteca de validación típica: cambia por completo la relación entre la validación y el tipado. Al generar tipos de TypeScript directamente a partir de los esquemas en tiempo de ejecución, elimina silenciosamente todo el problema de que las definiciones de tipo y las reglas de validación diverjan. Sumado a eso, su bajo consumo de recursos, su diseño componible y su comportamiento consistente tanto en el navegador como en Node, hacen que Zod sea la opción ideal para proyectos full-stack en TypeScript desarrollados con React y Node.js.
Siguientes pasos:
- Explora
zod-to-openapisi necesitas generar documentación OpenAPI directamente a partir de tus esquemas - Investiga
.refine()y.superRefine()para crear lógica de validación personalizada que abarque varios campos - Conoce tRPC, que utiliza nativamente los esquemas de Zod para ofrecer seguridad de tipos de extremo a extremo en tu API
Lecturas relacionadas
- Zod vs express-validator: Dos enfoques para la validación en Express — Compara la validación de solicitudes basada en esquemas con Zod frente al middleware chain-based de express-validator, abordando la configuración, el formato de los errores y las trampas más comunes.
- Propuestas de TC39 en 2026: Decoradores, Temporal y Signals explicados — Una visión práctica de tres propuestas de TC39: los decoradores nativos, la API Temporal y Signals, y su impacto en los desarrolladores de JavaScript y TypeScript full-stack.