Inicio > Cursos > TypeScript > Proyecto: API Full-Stack Tipada
TypeScriptTypeScript en PrácticaLección 17 de 18

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[]>([]);
Consejo🏆 El patrón de compartir tipos entre frontend y backend en un monorepo es un superpoder de TypeScript. Si cambias el schema de la DB, el compilador te dice exactamente qué hay que actualizar en toda la app.
← Error Handling Tipadotsconfig Avanzado y Tooling →

Practica esta lección

Resuelve los ejercicios del curso de TypeScript en la app, gratis y con corrección automática.

Empezar a practicar