# NestJS e TypeORM em 2026: migrations, relações e perguntas de entrevista > Dominar a integração do NestJS com o TypeORM usando as migrations do TypeORM 1.0, as relações entre entidades, o padrão repository e as perguntas de entrevista comuns para desenvolvedores backend. - Published: 2026-06-19 - Updated: 2026-06-19 - Author: SharpSkill - Tags: nestjs, typeorm, migrations, database, typescript, backend - Reading time: 9 min --- A integração do NestJS com o TypeORM continua sendo uma das mais testadas em produção para backends Node.js corporativos. Com o [TypeORM chegando à versão 1.0](https://typeorm.io/blog/) em maio de 2026, depois de quase uma década de desenvolvimento, e o [NestJS 11](https://docs.nestjs.com/) entregando o Express v5 por padrão, a stack amadureceu e virou uma base estável para aplicações em produção. Este guia cobre os padrões essenciais que todo desenvolvedor backend precisa dominar: migrations, relações entre entidades, o padrão repository e as perguntas de entrevista que vêm junto. > **O marco do TypeORM 1.0** > > O TypeORM 1.0.0, lançado em 19 de maio de 2026, moderniza os requisitos da plataforma para ECMAScript 2023, remove as APIs descontinuadas e corrige a ordem das migrations: as migrations pendentes agora rodam antes da sincronização do esquema quando ambas estão habilitadas. ## Configurar as migrations do TypeORM no NestJS 11 Primeira regra dos bancos de dados em produção: nunca usar `synchronize: true`. A sincronização automática compara os metadados das entidades com o esquema ativo e aplica as mudanças diretamente, o que arrisca perda de dados ao remover colunas, mudar tipos ou renomear campos. As migrations oferecem uma alternativa versionada, revisável e reversível. O TypeORM 1.0 exige um arquivo de configuração `DataSource` dedicado para o CLI, separado da configuração do módulo do NestJS. O CLI não consegue resolver a injeção de dependências do NestJS, então precisa de um ponto de entrada independente. ```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, }); ``` A configuração do módulo do NestJS reflete esses ajustes, mas usa o padrão `TypeOrmModule.forRootAsync` com injeção de dependências: ```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` substitui o array manual `entities` ao registrar automaticamente cada entidade importada via `TypeOrmModule.forFeature()` nos módulos de funcionalidade. ## O fluxo de migration: gerar, revisar, executar O TypeORM 1.0 entrega o `typeorm-ts-node-commonjs` como o runner de CLI recomendado, substituindo a abordagem antiga `ts-node ./node_modules/typeorm/cli`. Adicionar estes scripts ao `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" } ``` O comando de geração compara os metadados das entidades com o banco de dados ativo e produz instruções SQL. Sempre revisar a saída antes de fazer o commit. O TypeORM detecta adições de colunas, remoções, mudanças de tipo, alterações de índices e mudanças em chaves estrangeiras, mas deixa passar os backfills de dados, os renomeios de tabelas (que aparecem como um drop seguido de uma criação) e qualquer coisa que envolva extensões ou triggers do banco. > **Revisar cada migration gerada** > > Uma coluna renomeada gera um DROP + ADD em vez de um ALTER RENAME. Isso destrói os dados existentes. Sempre inspecionar o SQL no método `up()` e ajustar manualmente quando necessário. ## Relações entre entidades: OneToMany, ManyToOne, ManyToMany As relações entre entidades do TypeORM se traduzem diretamente em chaves estrangeiras SQL por meio de decorators. Entender os três tipos de relação principais e suas opções é fundamental para qualquer [módulo de banco de dados no NestJS](/technologies/node-nestjs/interview-questions/database-typeorm). Um `User` com vários registros `Order` ilustra o padrão mais comum: ```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; } ``` O lado `@ManyToOne` é dono da chave estrangeira. `@JoinColumn` é opcional no `@ManyToOne` (o TypeORM o infere), mas um nome explícito evita surpresas quando o nome da coluna importa para consultas cruas ou diffs de migration. Para as relações many-to-many, o TypeORM cria uma tabela de junção automaticamente: ```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` precisa aparecer em exatamente um lado da relação. Ele define qual entidade é dona da tabela de junção e controla o nome dela. ## O padrão repository e os repositories personalizados O [sistema de módulos e injeção de dependências do NestJS](/technologies/node-nestjs/interview-questions/nestjs-modules-di) integra os repositories do TypeORM via `TypeOrmModule.forFeature()`. Injetar o `Repository` padrão cobre as operações CRUD comuns. Quando as consultas ficam complexas, os repositories personalizados encapsulam essa 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(); } } ``` A API `find()` resolve as consultas simples. O `QueryBuilder` resolve os joins, as subconsultas, as agregações e tudo que exige um controle fino do SQL. Combinar os dois no mesmo serviço é perfeitamente válido: usar `find()` para buscas simples e `QueryBuilder` quando a consulta não puder ser expressa pela API de opções. ## Gestão de transações para a integridade dos dados As operações que modificam várias tabelas precisam de transações. O TypeORM oferece duas abordagens: o método `DataSource.transaction()` e a 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); }); } ``` O método `transaction()` baseado em callback faz commit automaticamente em caso de sucesso e faz rollback diante de qualquer erro lançado. Cada operação de banco de dados dentro do callback precisa usar o `manager` fornecido em vez do repository injetado, caso contrário essas operações rodam fora da transação. > **QueryRunner ou transaction()?** > > Usar `DataSource.transaction()` para as operações de múltiplas etapas mais simples. Migrar para o `QueryRunner` quando for necessário controle manual sobre os pontos de commit/rollback ou sobre os savepoints, como nos rollbacks parciais em processamento por lotes. ## Perguntas de entrevista comuns sobre NestJS e TypeORM Essas perguntas aparecem com frequência nas entrevistas de desenvolvedores backend. Cada resposta mira a profundidade esperada em uma discussão técnica de nível sênior. **Por que `synchronize: true` nunca deve ser usado em produção?** A sincronização automática aplica mudanças de esquema destrutivas sem confirmação. Remover uma coluna, mudar um tipo ou tirar um índice acontece de imediato sobre dados em produção. As migrations trazem controle de versão, revisão de código e capacidade de rollback. O único uso seguro do `synchronize` é o desenvolvimento local com dados descartáveis. **Qual é a diferença entre as relações `eager` e `lazy`?** As relações eager são carregadas automaticamente em cada consulta sobre a entidade pai. As relações lazy retornam uma `Promise` e executam uma consulta separada apenas quando acessadas. O carregamento eager gera uma sobrecarga do tipo N+1 quando os dados relacionados não são necessários. O carregamento lazy adia o custo, mas pode disparar consultas inesperadas no fundo da pilha de chamadas. A opção explícita `relations` no `find()` ou `leftJoinAndSelect` no `QueryBuilder` dá controle total sobre o que é carregado por consulta. **Como o `cascade` funciona nas relações do TypeORM?** Definir `cascade: true` em uma relação significa que salvar a entidade pai também persiste as entidades filhas não salvas. `cascade: ['insert', 'update']` limita o comportamento a operações específicas. Os cascades operam no nível do ORM, não no nível do banco de dados. Para exclusões em cascata no nível do banco de dados, usar `onDelete: 'CASCADE'` no decorator `@ManyToOne`, que gera uma restrição `ON DELETE CASCADE` na migration. **Quando um repository personalizado deve ser usado em vez do `Repository` padrão?** Os repositories personalizados encapsulam lógica de consulta complexa que não pertence aos serviços. Sinais para extrair um repository personalizado: a mesma cadeia de `QueryBuilder` aparece em vários serviços, a consulta envolve vários joins ou subconsultas, ou a consulta exige SQL cru. Chamadas simples de `find()` não justificam um repository personalizado. Para mais perguntas sobre a arquitetura do NestJS, ver o guia completo sobre [guards e interceptors no NestJS](/blog/node-nestjs/nestjs-guards-interceptors-modular-architecture). ## Conclusão - Usar um **arquivo de configuração `DataSource` separado** para o CLI do TypeORM, independente da configuração do módulo do NestJS - **Nunca habilitar `synchronize` em ambientes de produção**; apoiar-se em migrations geradas e revisadas para todas as mudanças de esquema - Colocar `@ManyToOne` na entidade que é dona da chave estrangeira, e usar um nome explícito com `@JoinColumn` para mais clareza nas consultas cruas e na saída das migrations - Escolher entre a API de opções `find()` para consultas simples e o `QueryBuilder` para joins complexos, agregações e lógica condicional - Envolver as mutações multitabela em **transações** com `DataSource.transaction()`, e garantir que cada operação dentro do callback use o `manager` transacional - Com o TypeORM 1.0 estabilizando a superfície da sua API e o NestJS 11 amadurecendo sua [arquitetura de módulos](/technologies/node-nestjs), essa stack continua sendo uma escolha sólida para backends corporativos que exigem um acesso a dados estruturado e tipado --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pt/blog/node-nestjs/nestjs-typeorm-migrations-relations-best-practices