TypeScript monorepo schemas: la estructura que evita bugs

¿Tenés un proyecto TypeScript que creció tanto que ya no sabés dónde poner cada cosa? La solución que propone Stefan Werfling en su artículo del 1 de junio de 2026 es un esquema TypeScript monorepo schemas: separás todo lo que se usa en más de un lugar (tipos, enums, validadores) en un paquete compartido, y el resto de la app depende de él. Sin webpack, sin bundlers pesados. Solo npm workspaces y tsc.

En 30 segundos

  • La estructura se basa en 4 directorios: /schemas (tipos puros), /core (sistema de plugins), /backend (Node.js) y /frontend (browser).
  • El paquete /schemas no tiene lógica runtime ni I/O: solo interfaces, enums y validadores.
  • Un solo cambio en un DTO se propaga a todos los consumidores en la próxima compilación, gracias a npm workspaces.
  • No necesitás un build tool pesado: alcanza con tsc y los workspaces nativos de npm.
  • Backend y frontend dependen ambos de /schemas y /core, así que los tipos quedan sincronizados al 100%.

Un monorepo de TypeScript es un único repositorio que contiene varios sub-proyectos relacionados, cada uno con su propio package.json, que comparten código y tipos a través de los workspaces de npm. La idea no es nueva, pero la implementación de Werfling tiene una gracia: prescinde de cualquier herramienta de build externa.

Por qué partir un proyecto TypeScript en sub-proyectos

Cualquiera que haya laburado en un proyecto que arrancó chiquito sabe cómo termina la película. Empezás con un src/ prolijo, y dos años después tenés 400 archivos donde el tipo User está definido en tres lugares distintos, cada uno con campos ligeramente diferentes.

Ese es el momento de partir.

El principio que propone Werfling es simple: extraé todo lo que se usa en más de un lugar a su propio paquete. El resto de la aplicación depende de ese paquete compartido. Subís el modelo de datos a un lugar central, lo importás desde el backend, lo importás desde el frontend, cambiás un campo una sola vez y de golpe todos los consumidores ven el cambio la próxima vez que compilan, sin que tengas que andar sincronizando a mano archivo por archivo.

El beneficio concreto: una sola fuente de verdad para tus tipos. Si el backend devuelve un objeto y el frontend lo consume, ambos hablan exactamente el mismo idioma. Cuando rompés el contrato, lo rompés en compilación, no en producción a las 3 de la mañana.

La estructura de un TypeScript monorepo schemas: componentes clave

La estructura a la que Werfling dice que “vuelve una y otra vez” tiene cuatro directorios bajo la raíz. Cada uno tiene una responsabilidad clara y una relación de dependencia definida. Cubrimos ese tema en detalle en pipelines de integración continua modernos.

DirectorioQué contieneDepende de
/schemasTipos puros, enums, interfaces, validadores. Sin runtime, sin I/O.Nada
/coreSistema de plugins, contratos y helpers de runtime compartidos./schemas
/backendEl servidor Node.js./schemas, /core
/frontendLa aplicación del navegador./schemas, /core
typescript monorepo schemas diagrama explicativo

Fijate en la dirección de las flechas. Todo apunta hacia /schemas, y /schemas no apunta a nada. Esa jerarquía no es decorativa: es lo que te salva de las dependencias circulares, que son el cáncer de cualquier monorepo. En la raíz viven dos archivos: el package.json que define los workspaces y las dev-deps, y un tsconfig.json opcional con las referencias del proyecto.

El directorio /schemas: tipos y validadores, nada más

Acá viene la regla más importante de todo el esquema, y la que más gente se saltea: en /schemas no va lógica de runtime. Punto.

Ponele que tenés un tipo Pedido. En /schemas va la interface que describe sus campos, el enum con los estados posibles (pendiente, enviado, entregado) y, en el caso de Werfling, el validador que verifica que un objeto cumpla esa forma. Lo que NO va es la función que guarda el pedido en la base, ni la que le pega a una API, ni nada que toque el disco o la red.

¿Por qué tanto rigor? Porque /schemas es el paquete del que dependen todos los demás. Si le metés una dependencia de Node.js adentro (un import fs perdido, por ejemplo), de repente tu frontend que corre en el navegador no compila más, porque arrastró algo que jamás debería haber tocado. Mantener este paquete puro es lo que permite que tanto el server como el browser lo usen sin drama.

El beneficio es directo. Cambiás un DTO una vez, y todos los consumidores lo levantan en la próxima compilación. Sin copiar y pegar tipos entre carpetas.

El directorio /core: el sistema de plugins compartido

Si /schemas son los datos, /core es el comportamiento compartido. Acá viven los contratos de plugins y los helpers de runtime que tanto el backend como el frontend podrían necesitar. Te puede servir nuestra cobertura de automatización con GitHub Actions.

La diferencia con /schemas es importante: /core sí tiene lógica que se ejecuta. La distinción está en que es lógica agnóstica del entorno. Un helper para parsear un formato, una interfaz de plugin que define cómo se registra un módulo, una función de transformación que no asume si está corriendo en Node o en el browser. Eso es /core.

Pensalo así: si escribís algo y te das cuenta de que lo vas a necesitar en los dos lados pero no es solo una definición de tipo, probablemente vaya en /core. Y como /core depende de /schemas, puede usar libremente todos esos tipos compartidos.

Configuración: package.json y tsconfig.json para workspaces

Werfling arranca con un directorio vacío y crea el package.json de la raíz. Ahí es donde declarás los workspaces, que es la pieza que hace toda la magia. Con la propiedad workspaces apuntando a tus sub-proyectos, npm entiende que /backend puede importar /schemas como si fuera un paquete instalado, aunque viva en la misma carpeta.

El tsconfig.json de la raíz es opcional y se usa para las project references de TypeScript, que le dicen al compilador en qué orden construir las cosas. Eso sí: lo interesante es que no hace falta nada más pesado. Nada de webpack, nada de un bundler complejo para coordinar los paquetes. Como dice el propio Werfling, con tsc alcanza.

La compilación se hace en orden de dependencia. Primero los paquetes base, después los que dependen de ellos:

  • cd ./schemas/ && npm run compile && cd ../core/ && npm run compile

Primero compila /schemas, después /core (que ya tiene los tipos de schemas listos), y de ahí seguís con backend y frontend. El orden importa porque cada paquete necesita que sus dependencias estén compiladas antes. Complementá con para proyectos internacionales.

Flujo de desarrollo: un cambio que se propaga solo

Acá está el verdadero golazo del esquema. Imaginate que el negocio decide que ahora cada pedido necesita un campo fechaEstimada. En un monolito clásico, eso significa buscar todas las definiciones del tipo, actualizarlas una por una y rezar para no olvidarte de ninguna.

Con esta estructura, agregás el campo en la interface de /schemas, y listo. La próxima vez que compiles el backend, TypeScript te marca dónde tenés que setear ese campo. La próxima vez que compiles el frontend, te marca dónde lo tenés que mostrar. El compilador se convierte en tu lista de tareas.

Esa es la ventaja sobre un único proyecto sin partes independientes: los errores aparecen donde corresponde, en el momento de compilar, y nadie consume una versión vieja del tipo por accidente.

Errores comunes al armar un monorepo de TypeScript

Meter lógica de runtime en /schemas

El error número uno. Alguien necesita una funcioncita rápida y la tira en schemas “porque ahí ya está el tipo”. Antes de que te des cuenta, schemas importa algo de Node y el frontend dejó de compilar. La corrección: si tiene lógica que se ejecuta, va en /core, no en /schemas.

Crear dependencias circulares

Si /core empieza a importar de /backend, rompiste la jerarquía. Las flechas tienen que ir en una sola dirección, siempre hacia los paquetes base. Respetá el orden: schemas no depende de nadie, core depende de schemas, y las apps dependen de ambos.

No versionar bien el paquete compartido

Un cambio en /schemas puede romper a todos sus consumidores a la vez. Tratá ese paquete con el cuidado que tratarías una API pública: documentá los contratos, y si tu equipo es grande, pasá los cambios compartidos por CI/CD para que nada llegue roto a la rama principal. Más contexto en ejecución de agentes sin conexión.

Qué significa para equipos en Latinoamérica

Si tu equipo arma productos full-stack en TypeScript (un backend en Node y un frontend en React o lo que sea), este patrón te ahorra una clase entera de bugs: los de “el front esperaba un campo y el back mandaba otro”. Para equipos chicos o medianos que no quieren la complejidad de Nx o Turborepo, esta versión con npm workspaces puro es un punto de entrada razonable.

Y si vas a desplegar ese backend en algún lado, podés alojarlo en un VPS o en un plan de hosting en donweb.com sin necesidad de infraestructura especial, porque al final del día lo que corre es Node.js común y corriente.

Preguntas Frecuentes

¿Qué es un monorepo con TypeScript?

Un monorepo con TypeScript es un único repositorio que contiene varios sub-proyectos (por ejemplo schemas, core, backend y frontend), cada uno con su propio package.json, que comparten tipos y código a través de los workspaces de npm. Un cambio en un tipo compartido se propaga a todos los consumidores en la próxima compilación.

¿Cómo comparto tipos entre módulos en TypeScript?

Extraés todos los tipos, enums e interfaces a un paquete dedicado (en este esquema, /schemas) y hacés que los demás paquetes dependan de él vía npm workspaces. Con eso, cualquier cambio en un DTO lo levantan automáticamente todos los que lo importan al recompilar.

¿Necesito webpack o un bundler para esto?

No. La propuesta de Stefan Werfling funciona solo con los npm workspaces nativos y el compilador tsc. No hace falta una herramienta de build pesada para coordinar los sub-proyectos; alcanza con compilar cada paquete en orden de dependencia.

¿Qué va en /schemas y qué va en /core?

En /schemas van solo tipos puros: interfaces, enums y validadores, sin lógica de runtime ni I/O. En /core va el comportamiento compartido agnóstico del entorno: contratos de plugins y helpers de runtime que tanto el backend como el frontend pueden necesitar.

¿Cuál es el orden correcto para compilar?

Siempre de los paquetes base hacia los que dependen de ellos: primero /schemas, después /core, y por último /backend y /frontend. El comando de ejemplo del artículo es cd ./schemas/ && npm run compile && cd ../core/ && npm run compile.

Conclusión

Lo que cambió acá no es una tecnología nueva, sino una forma sensata de usar herramientas que ya tenés. El esquema de Werfling separa datos (/schemas) de comportamiento compartido (/core) y deja que las apps dependan de ambos, todo con npm workspaces y tsc, sin sumar un bundler al medio.

Si tu proyecto TypeScript ya empezó a sentirse pesado, el próximo paso es concreto: identificá los tipos que tenés duplicados en más de un lugar y movelos a un paquete /schemas puro. Ese solo movimiento ya te da una fuente de verdad única y te empuja a respetar las fronteras entre módulos. El resto de la estructura viene casi solo después.

Fuentes

Te puede interesar...