NestJS e TypeORM nel 2026: Migrazioni, Relazioni e Domande da Colloquio
Integrazione NestJS TypeORM con migrazioni TypeORM 1.1, relazioni tra entity, pattern repository e domande frequenti nei colloqui per sviluppatori backend.

NestJS TypeORM resta una delle integrazioni ORM più collaudate per backend Node.js enterprise. Con TypeORM che raggiunge la versione 1.0 a maggio 2026, seguita dalla serie di patch 1.1.x che risolve la gestione delle connessioni e la persistenza delle relazioni, e NestJS 12 che completa la migrazione ESM, lo stack rappresenta una base stabile per applicazioni in produzione. Questa guida copre i pattern essenziali per ogni sviluppatore backend: migrazioni, relazioni tra entity, il pattern repository e le domande da colloquio associate.
NestJS 12 distribuisce i pacchetti core come ESM, richiedendo Node.js 20.19+ o 22.12+. Il pacchetto @nestjs/typeorm v12 segue lo stesso approccio. TypeORM 1.1.2 corregge le perdite di connessione in DataSource.initialize() e risolve la persistenza delle righe junction many-to-many quando le relazioni non sono caricate.
Configurare le migrazioni TypeORM in NestJS 12
La prima regola per i database di produzione: mai usare synchronize: true. Auto-sync confronta i metadati delle entity con lo schema live e applica le modifiche direttamente, rischiando la perdita di dati su rimozione di colonne, cambio di tipo o rinomina di campi. Le migrazioni forniscono un'alternativa versionata, verificabile e reversibile.
TypeORM 1.x richiede un file di configurazione DataSource dedicato per la CLI, separato dalla configurazione del modulo NestJS. La CLI non può risolvere la dependency injection di NestJS, quindi necessita di un entry point standalone.
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 configurazione del modulo NestJS rispecchia queste impostazioni ma utilizza il pattern TypeOrmModule.forRootAsync con dependency injection:
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 sostituisce l'array manuale entities registrando automaticamente ogni entity importata tramite TypeOrmModule.forFeature() nei feature module.
Workflow delle migrazioni: generare, verificare, eseguire
TypeORM 1.x include typeorm-ts-node-commonjs come runner CLI raccomandato, sostituendo il vecchio approccio ts-node ./node_modules/typeorm/cli. Questi script vanno aggiunti a 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"
}Il comando generate confronta i metadati delle entity con il database live e produce statement SQL. L'output va sempre verificato prima del commit. TypeORM rileva aggiunte di colonne, rimozioni, cambi di tipo, modifiche agli indici e cambi di foreign key, ma non rileva backfill di dati, rinomina di tabelle (che appaiono come drop seguito da create), e operazioni che coinvolgono estensioni database o trigger.
Una colonna rinominata genera un DROP + ADD invece di un ALTER RENAME. Questo distrugge i dati esistenti. Le istruzioni SQL nel metodo up() vanno sempre ispezionate e corrette manualmente quando necessario.
Relazioni tra entity: OneToMany, ManyToOne, ManyToMany
Le relazioni delle entity TypeORM mappano direttamente su foreign key SQL usando i decorator. Comprendere i tre tipi di relazione principali e le loro opzioni e fondamentale per qualsiasi modulo database NestJS.
Un User con multipli record Order dimostra il pattern piu comune:
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;
}Il lato @ManyToOne possiede la foreign key. @JoinColumn e opzionale su @ManyToOne (TypeORM lo deduce), ma la denominazione esplicita evita sorprese nelle raw query o nei diff delle migrazioni.
Per le relazioni many-to-many, TypeORM crea automaticamente una tabella junction:
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 deve apparire su esattamente un lato della relazione. Definisce quale entity possiede la tabella junction e ne controlla il nome.
Pronto a superare i tuoi colloqui su Node.js / NestJS?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
Pattern Repository e Custom Repository
Il sistema di moduli e DI di NestJS integra i repository TypeORM tramite TypeOrmModule.forFeature(). Iniettare il Repository<T> di default copre le operazioni CRUD standard. Quando le query diventano complesse, i custom repository incapsulano quella logica.
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();
}
}L'API find() gestisce query semplici. Il QueryBuilder gestisce join, subquery, aggregazioni e tutto cio che richiede controllo SQL fine. Mescolare entrambi nello stesso service e perfettamente valido: usare find() per lookup semplici e QueryBuilder quando la query non puo essere espressa tramite l'API delle opzioni.
Gestione delle transazioni per l'integrita dei dati
Le operazioni che modificano piu tabelle necessitano di transazioni. TypeORM offre due approcci: il metodo DataSource.transaction() e l'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);
});
}Il metodo transaction() basato su callback esegue automaticamente il commit in caso di successo e il rollback in caso di errore. Ogni operazione database all'interno del callback deve usare il manager fornito invece del repository iniettato, altrimenti quelle operazioni vengono eseguite fuori dalla transazione.
Usare DataSource.transaction() per operazioni multi-step semplici. Passare a QueryRunner quando e richiesto controllo manuale sui punti di commit/rollback o sui savepoint, come nei rollback parziali nell'elaborazione batch.
Domande frequenti nei colloqui su NestJS TypeORM
Queste domande appaiono frequentemente nei colloqui per sviluppatori backend. Ogni risposta mira alla profondita attesa in una discussione tecnica di livello senior.
Perche synchronize: true non dovrebbe mai essere usato in produzione?
Auto-sync applica modifiche distruttive allo schema senza conferma. La rimozione di una colonna, il cambio di un tipo o la rimozione di un indice avviene immediatamente sui dati live. Le migrazioni forniscono controllo di versione, code review e capacita di rollback. L'unico uso sicuro di synchronize e lo sviluppo locale con dati usa e getta.
Qual e la differenza tra relazioni eager e lazy?
Le relazioni eager si caricano automaticamente con ogni query sull'entity parent. Le relazioni lazy restituiscono una Promise ed eseguono una query separata solo quando vi si accede. L'eager loading causa overhead tipo N+1 quando i dati correlati non sono necessari. Il lazy loading differisce il costo ma puo innescare query inaspettate in profondita nel call stack. L'opzione esplicita relations in find() o leftJoinAndSelect nel QueryBuilder da pieno controllo su cosa viene caricato per query.
Come funziona cascade nelle relazioni TypeORM?
Impostare cascade: true su una relazione significa che salvare l'entity parent persiste anche le entity child non salvate. cascade: ['insert', 'update'] limita il comportamento a operazioni specifiche. I cascade operano a livello ORM, non a livello database. Per cascade delete a livello database, usare onDelete: 'CASCADE' sul decorator @ManyToOne, che genera un constraint ON DELETE CASCADE nella migrazione.
Quando usare un custom repository invece del Repository<T> di default?
I custom repository incapsulano logica di query complessa che non appartiene ai service. Segnali per estrarre un custom repository: la stessa catena QueryBuilder appare in piu service, la query coinvolge join multipli o subquery, o la query richiede SQL raw. Semplici chiamate find() non giustificano un custom repository.
Per altre domande sull'architettura NestJS, consultare la guida completa NestJS guards e interceptors.
Pronto a superare i tuoi colloqui su Node.js / NestJS?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
Sources
- TypeORM 1.0.0 Release - Requisito ECMAScript 2023, rimozione Connection, default di validazione piu rigorosi
- TypeORM 1.1.2 Release - Fix perdite connessione in DataSource.initialize(), fix righe junction many-to-many
- NestJS 12 Release - Migrazione ESM, requisito Node.js 20.19+/22.12+
- @nestjs/typeorm 12.0.0 - Moduli ES nativi, compatibilita CommonJS via Node require(esm)
Punti chiave per applicazioni NestJS TypeORM in produzione
- Usare un file di configurazione
DataSourceseparato per la CLI TypeORM, indipendente dalla configurazione del modulo NestJS - Mai abilitare
synchronizein produzione; fare affidamento su migrazioni generate e verificate per tutte le modifiche allo schema - Posizionare
@ManyToOnesull'entity che possiede la foreign key, e usare denominazione esplicita@JoinColumnper chiarezza nelle raw query e nell'output delle migrazioni - Scegliere tra API opzioni
find()per query semplici eQueryBuilderper join complessi, aggregazioni e logica condizionale - Avvolgere le mutazioni multi-tabella in transazioni usando
DataSource.transaction(), e assicurarsi che ogni operazione all'interno del callback usi ilmanagertransazionale - Con TypeORM 1.1.x che stabilizza la superficie API e NestJS 12 che completa la migrazione ESM, questo stack resta una scelta solida per backend enterprise che richiedono accesso database strutturato e type-safe
Inizia a praticare!
Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.
Sapresti trovare il bug in Node.js / NestJS?
Uno snippet reale, un bug nascosto, un tentativo al giorno. Senza account per provare.

Scritto da
Anthony Fillion-MailletFondatore di SharpSkill
Sviluppatore fullstack da oltre 10 anni. Guida SharpSkill e risponde di tutto ciò che vi viene pubblicato.
Aggiornato il 21 ottobre 2026
Tag
Condividi
Articoli correlati

NestJS + Prisma: lo stack backend moderno per Node.js
Guida completa per costruire un'API backend moderna con NestJS e Prisma. Configurazione, modelli, servizi, transazioni e best practice spiegate.

NestJS: Creare una REST API completa da zero
Guida passo dopo passo per costruire una REST API pronta per la produzione con NestJS, TypeScript, Prisma e class-validator. CRUD, validazione, gestione errori e interceptor.

Microservizi con NestJS nel 2026: Architettura, gRPC e Domande da Colloquio
Guida pratica all'architettura microservizi NestJS con gRPC, che copre confini dei servizi, layer di trasporto, pattern di streaming e domande comuni da colloquio per il 2026.