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.

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 en mayo de 2026, tras casi una década de desarrollo, y NestJS 11 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.
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.
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:
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<number>('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:
{
"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.
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.
Un User con varios registros Order ilustra el patrón más común:
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;
}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:
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.
¿Listo para aprobar tus entrevistas de Node.js / NestJS?
Practica con nuestros simuladores interactivos, flashcards y tests técnicos.
El patrón repository y los repositories personalizados
El sistema de módulos e inyección de dependencias de NestJS integra los repositories de TypeORM mediante TypeOrmModule.forFeature(). Inyectar el Repository<T> predeterminado cubre las operaciones CRUD estándar. Cuando las consultas crecen en complejidad, los repositories personalizados encapsulan esa lógica.
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 {}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<Order>,
) {}
async findByUser(userId: string): Promise<Order[]> {
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<Order[]> {
// 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.
async createOrderWithItems(
userId: string,
items: { productId: string; quantity: number }[],
dataSource: DataSource,
): Promise<Order> {
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.
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<T> 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.
¿Listo para aprobar tus entrevistas de Node.js / NestJS?
Practica con nuestros simuladores interactivos, flashcards y tests técnicos.
Conclusión
- Usar un archivo de configuración
DataSourceseparado para el CLI de TypeORM, independiente de la configuración del módulo de NestJS - Nunca habilitar
synchronizeen entornos de producción; apoyarse en migraciones generadas y revisadas para todos los cambios de esquema - Colocar
@ManyToOneen la entidad que es dueña de la clave foránea, y usar un nombre explícito con@JoinColumnpara mayor claridad en las consultas crudas y la salida de las migraciones - Elegir entre la API de opciones
find()para consultas simples yQueryBuilderpara 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 elmanagertransaccional - Con TypeORM 1.0 estabilizando la superficie de su API y NestJS 11 madurando su arquitectura de módulos, este stack sigue siendo una opción sólida para backends empresariales que requieren un acceso a datos estructurado y tipado
¡Empieza a practicar!
Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.
Etiquetas
Compartir
Artículos relacionados

NestJS + Prisma: el stack backend moderno para Node.js
Guía completa para construir una API backend moderna con NestJS y Prisma. Configuración, modelos, servicios, transacciones y buenas prácticas explicadas.

NestJS: Construir una API REST Completa
Tutorial completo para construir una API REST profesional con NestJS. Controladores, Servicios, Modulos, validacion con class-validator y manejo centralizado de errores.

Microservicios con NestJS en 2026: Arquitectura gRPC, Patrones de Streaming y Preguntas de Entrevista
Guia practica sobre arquitectura de microservicios NestJS con gRPC: capas de transporte, Protocol Buffers, patrones de streaming y preguntas de entrevista para desarrolladores backend en 2026.