# NestJS y TypeORM en 2026: migraciones, relaciones y preguntas de entrevista > Dominar la integración de NestJS con TypeORM usando las migraciones de TypeORM 1.0, las relaciones entre entidades, el patrón repository y las preguntas de entrevista habituales para desarrolladores backend. - Published: 2026-06-19 - Updated: 2026-06-19 - Author: SharpSkill - Tags: nestjs, typeorm, migrations, database, typescript, backend - Reading time: 9 min --- La integración de NestJS con TypeORM sigue siendo una de las más probadas en producción para backends de Node.js empresariales. Con [TypeORM llegando a la versión 1.0](https://typeorm.io/blog/) en mayo de 2026, tras casi una década de desarrollo, y [NestJS 11](https://docs.nestjs.com/) entregando Express v5 de forma predeterminada, el stack maduró hasta convertirse en una base estable para aplicaciones en producción. Esta guía cubre los patrones esenciales que todo desarrollador backend debe dominar: migraciones, relaciones entre entidades, el patrón repository y las preguntas de entrevista que los acompañan. > **El hito de TypeORM 1.0** > > TypeORM 1.0.0, publicado el 19 de mayo de 2026, moderniza los requisitos de la plataforma hacia ECMAScript 2023, elimina las API obsoletas y corrige el orden de las migraciones: las migraciones pendientes ahora se ejecutan antes de la sincronización del esquema cuando ambas están habilitadas. ## Configurar las migraciones de TypeORM en NestJS 11 Primera regla de las bases de datos en producción: nunca usar `synchronize: true`. La sincronización automática compara los metadatos de las entidades con el esquema activo y aplica los cambios de forma directa, lo que arriesga la pérdida de datos al eliminar columnas, cambiar tipos o renombrar campos. Las migraciones ofrecen una alternativa versionada, revisable y reversible. TypeORM 1.0 exige un archivo de configuración `DataSource` dedicado para el CLI, independiente de la configuración del módulo de NestJS. El CLI no puede resolver la inyección de dependencias de NestJS, así que necesita un punto de entrada autónomo. ```typescript // src/config/typeorm.config.ts import { DataSource } from 'typeorm'; import { config } from 'dotenv'; config(); // Load .env variables export default new DataSource({ type: 'postgres', host: process.env.DB_HOST, port: parseInt(process.env.DB_PORT || '5432'), username: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, entities: ['src/**/*.entity.ts'], migrations: ['src/migrations/*.ts'], synchronize: false, }); ``` La configuración del módulo de NestJS replica estos ajustes pero usa el patrón `TypeOrmModule.forRootAsync` con inyección de dependencias: ```typescript // src/app.module.ts import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ConfigModule, ConfigService } from '@nestjs/config'; @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true }), TypeOrmModule.forRootAsync({ inject: [ConfigService], useFactory: (config: ConfigService) => ({ type: 'postgres', host: config.get('DB_HOST'), port: config.getOrThrow('DB_PORT'), username: config.get('DB_USER'), password: config.get('DB_PASSWORD'), database: config.get('DB_NAME'), autoLoadEntities: true, // Auto-register entities from feature modules synchronize: false, // Always false in production }), }), ], }) export class AppModule {} ``` `autoLoadEntities: true` reemplaza el arreglo manual `entities` al registrar automáticamente cada entidad importada mediante `TypeOrmModule.forFeature()` en los módulos de funcionalidad. ## El flujo de migración: generar, revisar, ejecutar TypeORM 1.0 entrega `typeorm-ts-node-commonjs` como el runner de CLI recomendado, en sustitución del enfoque anterior `ts-node ./node_modules/typeorm/cli`. Agregar estos scripts al `package.json`: ```json // package.json (scripts section) { "migration:generate": "typeorm-ts-node-commonjs migration:generate src/migrations/$npm_config_name -d src/config/typeorm.config.ts", "migration:run": "typeorm-ts-node-commonjs migration:run -d src/config/typeorm.config.ts", "migration:revert": "typeorm-ts-node-commonjs migration:revert -d src/config/typeorm.config.ts" } ``` El comando de generación compara los metadatos de las entidades con la base de datos activa y produce sentencias SQL. Siempre revisar la salida antes de hacer commit. TypeORM detecta el agregado de columnas, las eliminaciones, los cambios de tipo, las modificaciones de índices y los cambios en claves foráneas, pero no detecta los rellenos de datos, el renombrado de tablas (que aparece como un drop seguido de una creación) ni nada que involucre extensiones o triggers de la base de datos. > **Revisar cada migración generada** > > Una columna renombrada genera un DROP + ADD en lugar de un ALTER RENAME. Esto destruye los datos existentes. Siempre inspeccionar el SQL en el método `up()` y ajustarlo de forma manual cuando haga falta. ## Relaciones entre entidades: OneToMany, ManyToOne, ManyToMany Las relaciones entre entidades de TypeORM se traducen de forma directa en claves foráneas de SQL mediante decoradores. Comprender los tres tipos de relación principales y sus opciones es fundamental para cualquier [módulo de base de datos en NestJS](/technologies/node-nestjs/interview-questions/database-typeorm). Un `User` con varios registros `Order` ilustra el patrón más común: ```typescript // src/entities/user.entity.ts import { Entity, PrimaryGeneratedColumn, Column, OneToMany, CreateDateColumn } from 'typeorm'; import { Order } from './order.entity'; @Entity('users') export class User { @PrimaryGeneratedColumn('uuid') id: string; @Column({ unique: true }) email: string; @Column() name: string; @OneToMany(() => Order, (order) => order.user) orders: Order[]; // No DB column created here; relation lives on the Order side @CreateDateColumn() createdAt: Date; } ``` ```typescript // src/entities/order.entity.ts import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm'; import { User } from './user.entity'; @Entity('orders') export class Order { @PrimaryGeneratedColumn('uuid') id: string; @Column('decimal', { precision: 10, scale: 2 }) total: number; @Column({ default: 'pending' }) status: string; @ManyToOne(() => User, (user) => user.orders, { onDelete: 'CASCADE' }) @JoinColumn({ name: 'user_id' }) // Explicit FK column name user: User; } ``` El lado `@ManyToOne` es dueño de la clave foránea. `@JoinColumn` es opcional en `@ManyToOne` (TypeORM lo infiere), pero un nombre explícito evita sorpresas cuando el nombre de la columna importa para consultas crudas o diffs de migración. Para las relaciones many-to-many, TypeORM crea una tabla de unión de forma automática: ```typescript // src/entities/product.entity.ts import { Entity, PrimaryGeneratedColumn, Column, ManyToMany, JoinTable } from 'typeorm'; import { Category } from './category.entity'; @Entity('products') export class Product { @PrimaryGeneratedColumn('uuid') id: string; @Column() name: string; @Column('decimal', { precision: 10, scale: 2 }) price: number; @ManyToMany(() => Category, (category) => category.products) @JoinTable({ name: 'product_categories' }) // Owns the junction table categories: Category[]; } ``` `@JoinTable` debe aparecer en exactamente un lado de la relación. Define qué entidad es dueña de la tabla de unión y controla su nombre. ## El patrón repository y los repositories personalizados El [sistema de módulos e inyección de dependencias de NestJS](/technologies/node-nestjs/interview-questions/nestjs-modules-di) integra los repositories de TypeORM mediante `TypeOrmModule.forFeature()`. Inyectar el `Repository` predeterminado cubre las operaciones CRUD estándar. Cuando las consultas crecen en complejidad, los repositories personalizados encapsulan esa lógica. ```typescript // src/orders/orders.module.ts import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Order } from '../entities/order.entity'; import { OrdersService } from './orders.service'; import { OrdersController } from './orders.controller'; @Module({ imports: [TypeOrmModule.forFeature([Order])], providers: [OrdersService], controllers: [OrdersController], }) export class OrdersModule {} ``` ```typescript // src/orders/orders.service.ts import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Order } from '../entities/order.entity'; @Injectable() export class OrdersService { constructor( @InjectRepository(Order) private readonly orderRepo: Repository, ) {} async findByUser(userId: string): Promise { return this.orderRepo.find({ where: { user: { id: userId } }, relations: ['user'], // Eager-load the user relation order: { createdAt: 'DESC' }, // Most recent first }); } async findWithFilters(status: string, minTotal: number): Promise { // QueryBuilder for complex queries return this.orderRepo .createQueryBuilder('order') .leftJoinAndSelect('order.user', 'user') .where('order.status = :status', { status }) .andWhere('order.total >= :minTotal', { minTotal }) .orderBy('order.total', 'DESC') .getMany(); } } ``` La API `find()` resuelve las consultas sencillas. El `QueryBuilder` resuelve los joins, las subconsultas, las agregaciones y todo lo que requiera un control fino del SQL. Combinar ambos en el mismo servicio es totalmente válido: usar `find()` para búsquedas simples y `QueryBuilder` cuando la consulta no se pueda expresar mediante la API de opciones. ## Gestión de transacciones para la integridad de los datos Las operaciones que modifican varias tablas necesitan transacciones. TypeORM ofrece dos enfoques: el método `DataSource.transaction()` y la API `QueryRunner`. ```typescript // src/orders/orders.service.ts (additional method) async createOrderWithItems( userId: string, items: { productId: string; quantity: number }[], dataSource: DataSource, ): Promise { return dataSource.transaction(async (manager) => { // All operations use the transactional manager const order = manager.create(Order, { user: { id: userId }, total: 0, status: 'pending', }); const savedOrder = await manager.save(order); let total = 0; for (const item of items) { const product = await manager.findOneByOrFail(Product, { id: item.productId }); total += product.price * item.quantity; await manager.save(OrderItem, { order: savedOrder, product, quantity: item.quantity, unitPrice: product.price, }); } savedOrder.total = total; return manager.save(savedOrder); }); } ``` El método `transaction()` basado en callback hace commit automáticamente al tener éxito y revierte ante cualquier error lanzado. Cada operación de base de datos dentro del callback debe usar el `manager` provisto en lugar del repository inyectado; de lo contrario, esas operaciones se ejecutan fuera de la transacción. > **¿QueryRunner o transaction()?** > > Usar `DataSource.transaction()` para las operaciones multipaso sencillas. Pasar a `QueryRunner` cuando se requiera control manual sobre los puntos de commit/rollback o sobre los savepoints, como en los rollbacks parciales en el procesamiento por lotes. ## Preguntas de entrevista habituales sobre NestJS y TypeORM Estas preguntas aparecen con frecuencia en las entrevistas de desarrolladores backend. Cada respuesta apunta a la profundidad esperada en una conversación técnica de nivel senior. **¿Por qué nunca se debe usar `synchronize: true` en producción?** La sincronización automática aplica cambios de esquema destructivos sin confirmación. Eliminar una columna, cambiar un tipo o quitar un índice ocurre de inmediato sobre datos en producción. Las migraciones aportan control de versiones, revisión de código y capacidad de rollback. El único uso seguro de `synchronize` es el desarrollo local con datos descartables. **¿Cuál es la diferencia entre las relaciones `eager` y `lazy`?** Las relaciones eager se cargan de forma automática en cada consulta sobre la entidad padre. Las relaciones lazy devuelven una `Promise` y ejecutan una consulta aparte solo cuando se accede a ellas. La carga eager genera una sobrecarga de tipo N+1 cuando los datos relacionados no son necesarios. La carga lazy difiere el costo, pero puede disparar consultas inesperadas en lo profundo de la pila de llamadas. La opción explícita `relations` en `find()` o `leftJoinAndSelect` en `QueryBuilder` da control total sobre lo que se carga por consulta. **¿Cómo funciona `cascade` en las relaciones de TypeORM?** Definir `cascade: true` en una relación significa que guardar la entidad padre también persiste las entidades hijas no guardadas. `cascade: ['insert', 'update']` limita el comportamiento a operaciones específicas. Las cascadas operan a nivel del ORM, no a nivel de la base de datos. Para eliminaciones en cascada a nivel de base de datos, usar `onDelete: 'CASCADE'` en el decorador `@ManyToOne`, que genera una restricción `ON DELETE CASCADE` en la migración. **¿Cuándo se debe usar un repository personalizado en lugar del `Repository` predeterminado?** Los repositories personalizados encapsulan lógica de consulta compleja que no pertenece a los servicios. Señales para extraer un repository personalizado: la misma cadena de `QueryBuilder` aparece en varios servicios, la consulta involucra varios joins o subconsultas, o la consulta requiere SQL crudo. Las simples llamadas a `find()` no justifican un repository personalizado. Para más preguntas sobre la arquitectura de NestJS, ver la guía completa sobre [guards e interceptors en NestJS](/blog/node-nestjs/nestjs-guards-interceptors-modular-architecture). ## Conclusión - Usar un **archivo de configuración `DataSource` separado** para el CLI de TypeORM, independiente de la configuración del módulo de NestJS - **Nunca habilitar `synchronize` en entornos de producción**; apoyarse en migraciones generadas y revisadas para todos los cambios de esquema - Colocar `@ManyToOne` en la entidad que es dueña de la clave foránea, y usar un nombre explícito con `@JoinColumn` para mayor claridad en las consultas crudas y la salida de las migraciones - Elegir entre la API de opciones `find()` para consultas simples y `QueryBuilder` para joins complejos, agregaciones y lógica condicional - Envolver las mutaciones multitabla en **transacciones** con `DataSource.transaction()`, y asegurar que cada operación dentro del callback use el `manager` transaccional - Con TypeORM 1.0 estabilizando la superficie de su API y NestJS 11 madurando su [arquitectura de módulos](/technologies/node-nestjs), este stack sigue siendo una opción sólida para backends empresariales que requieren un acceso a datos estructurado y tipado --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/es/blog/node-nestjs/nestjs-typeorm-migrations-relations-best-practices