Proyecto: API Full-Stack Tipada
Este proyecto aplica TypeScript en el escenario más valioso: una API full-stack donde los tipos fluyen automáticamente desde la base de datos hasta el frontend. Prisma genera tipos TypeScript desde el schema de la base de datos. Zod valida los datos entrantes y su z.infer<> convierte el schema de validación en un tipo TypeScript.
En el frontend, importas los mismos tipos desde el backend, garantizando que si el modelo de datos cambia, el compilador te dice exactamente qué hay que actualizar en ambos lados.
La arquitectura de tipos compartidos en un monorepo elimina una categoría entera de bugs: los errores de contrato entre frontend y backend. En APIs sin TypeScript, el frontend asume cierta forma de los datos y el backend la cambia sin avisar, causando crashes en producción.
Con tipos compartidos, cambiar la API sin actualizar el frontend es un error de compilación, no un bug en producción. Este proyecto resume por qué TypeScript es el estándar en proyectos de equipo.
// schema.prisma — fuente de verdad de los tipos
// model Tarea {
// id String @id @default(uuid())
// titulo String
// completada Boolean @default(false)
// usuarioId String
// usuario Usuario @relation(...)
// }
// types/api.ts — tipos compartidos front/back
import { Prisma } from "@prisma/client";
import { z } from "zod";
// Tipos inferidos de Prisma automáticamente
export type Tarea = Prisma.TareaGetPayload<{
include: { usuario: { select: { nombre: true } } };
}>;
// Schemas de validación con Zod
export const CrearTareaSchema = z.object({
titulo: z.string().min(1).max(200),
descripcion: z.string().optional(),
});
export type CrearTareaInput = z.infer<typeof CrearTareaSchema>;
// middleware/validate.ts — genérico para cualquier schema
function validar<T extends z.ZodType>(schema: T) {
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 as z.infer<T>;
next();
};
}
// routes/tareas.ts
router.post("/", authMiddleware, validar(CrearTareaSchema),
async (req: Request<{},{},CrearTareaInput>, res) => {
const tarea = await prisma.tarea.create({
data: { ...req.body, usuarioId: req.usuario!.id },
});
res.status(201).json({ data: tarea });
}
);
// En el frontend: mismo type importado
import type { Tarea, CrearTareaInput } from "../../types/api";
const [tareas, setTareas] = useState<Tarea[]>([]);
Practica esta lección
Resuelve los ejercicios del curso de TypeScript en la app, gratis y con corrección automática.
Empezar a practicar