Suggest an editImprove this articleRefine the answer for “How does NestJS integrate with TypeORM?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**NestJS + TypeORM integration** works in three steps: configure the connection once with `TypeOrmModule.forRoot()`, register entities per module with `TypeOrmModule.forFeature([Entity])`, then inject `Repository<Entity>` using `@InjectRepository()`. ```typescript // feature module TypeOrmModule.forFeature([User]) // service constructor @InjectRepository(User) private usersRepo: Repository<User>; ``` **Key point:** `synchronize: true` auto-alters the database schema on startup - safe in development, destructive in production.Shown above the full answer for quick recall.Answer (EN)Image**NestJS + TypeORM integration** - connects the database layer to NestJS's module system through the `@nestjs/typeorm` package, giving each feature module its own typed repository. ## Theory ### TL;DR - Install `@nestjs/typeorm typeorm pg`, configure once with `TypeOrmModule.forRoot()` - Each feature module registers its entities via `TypeOrmModule.forFeature([Entity])` - Inject `Repository<Entity>` into services using the `@InjectRepository()` decorator - `synchronize: true` rewrites database schema automatically - fine in development, destructive in production - For transactions, inject `DataSource` directly, not the repository ### Module setup ```typescript // app.module.ts TypeOrmModule.forRoot({ type: 'postgres', host: process.env.DB_HOST, port: +process.env.DB_PORT, username: process.env.DB_USER, password: process.env.DB_PASS, database: process.env.DB_NAME, autoLoadEntities: true, // picks up all forFeature() entities automatically synchronize: process.env.NODE_ENV !== 'production', // ⚠️ dev only }) ``` `autoLoadEntities: true` means you never have to list entities in `forRoot()`. Each `TypeOrmModule.forFeature([User])` call registers the entity without touching the root config. ### Defining an entity Entities are plain TypeScript classes with TypeORM decorators. `@Entity('users')` maps the class to a table. `@Column`, `@PrimaryGeneratedColumn`, and relation decorators define the schema. ```typescript // users/user.entity.ts @Entity('users') export class User { @PrimaryGeneratedColumn() id: number; @Column({ length: 100 }) name: string; @Column({ unique: true }) email: string; @Column({ select: false }) // excluded from every SELECT unless explicitly requested password: string; @Column({ default: 'user' }) role: string; @OneToMany(() => Post, post => post.user) posts: Post[]; @CreateDateColumn() createdAt: Date; @UpdateDateColumn() updatedAt: Date; } ``` The `select: false` on `password` comes up in interviews. The column exists in the database, but TypeORM drops it from query results unless you call `.addSelect('user.password')` explicitly in a query builder. ### Repository pattern This is the standard way to talk to the database in NestJS. Register the entity in the feature module, then inject the typed repository into the service. ```typescript // users/users.module.ts @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], controllers: [UsersController], }) export class UsersModule {} // users/users.service.ts @Injectable() export class UsersService { constructor( @InjectRepository(User) private readonly usersRepo: Repository<User>, ) {} findAll(): Promise<User[]> { return this.usersRepo.find(); } findOne(id: number): Promise<User | null> { return this.usersRepo.findOneBy({ id }); } async create(dto: CreateUserDto): Promise<User> { const user = this.usersRepo.create(dto); // instantiates, does NOT save return this.usersRepo.save(user); // INSERT or UPDATE } async remove(id: number): Promise<void> { await this.usersRepo.delete(id); } } ``` `create()` just builds the entity object from the DTO. Nothing hits the database until `save()` is called. That distinction trips people up. ### Relations and query builder For simple relation loading, pass `relations` to `find()`: ```typescript const users = await this.usersRepo.find({ relations: { posts: true }, }); ``` When you need filtering, ordering, or joins, use `createQueryBuilder`: ```typescript const activeUsers = await this.usersRepo .createQueryBuilder('user') .leftJoinAndSelect('user.posts', 'post') .where('user.isActive = :active', { active: true }) .orderBy('user.createdAt', 'DESC') .take(10) .getMany(); ``` The query builder solves the N+1 problem that appears when loading relations inside a loop. One query, one round trip. ### Transactions For operations that must succeed or fail together, inject `DataSource` and wrap the logic in `dataSource.transaction()`: ```typescript @Injectable() export class OrdersService { constructor(private readonly dataSource: DataSource) {} async createOrder(dto: CreateOrderDto) { return this.dataSource.transaction(async manager => { const order = manager.create(Order, dto); await manager.save(order); await manager.decrement( Product, { id: dto.productId }, 'stock', dto.quantity, ); return order; }); } } ``` The `manager` inside the callback is an `EntityManager` scoped to the transaction. If anything throws, the whole transaction rolls back automatically. ### Common mistakes **1. `synchronize: true` in production.** TypeORM will alter or drop columns to match the entity definitions. One deployment can silently remove a column with real user data. ```typescript // ❌ never in production config synchronize: true, // ✅ use migrations in production synchronize: process.env.NODE_ENV !== 'production', ``` **2. Missing `TypeOrmModule.forFeature()` in the feature module.** ```typescript // ❌ UsersService will throw "No metadata found for User" @Module({ providers: [UsersService], }) export class UsersModule {} // ✅ register before injecting @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService], }) export class UsersModule {} ``` **3. Expecting `create()` to persist data.** `Repository.create()` only instantiates the class. You still need `save()` to write to the database. **4. Accessing relations without loading them.** TypeORM 0.3+ removed implicit lazy loading. Accessing `user.posts` without `{ relations: { posts: true } }` or a query builder returns an empty array or `undefined`, not a database error. **5. Using a plain repository inside a transaction.** The injected `Repository<Entity>` is not bound to the transaction context. Always use the `manager` argument passed to the `dataSource.transaction()` callback. ### Real-world usage - Auth services: `Repository<User>`, query by email, retrieve password with `.addSelect()` - Paginated feeds: query builder with `.skip()` and `.take()` - Soft deletes: `@DeleteDateColumn()` decorator plus `find({ withDeleted: false })` - Multi-tenant apps: `TypeOrmModule.forRootAsync()` with a factory that reads tenant config at request time - Multiple databases: two `forRoot()` calls with different `name` values, then `@InjectRepository(Entity, 'connectionName')` ### Follow-up questions **Q:** What is the difference between `save()` and `insert()` in TypeORM? **A:** `save()` checks whether the entity already has a primary key. If it does, it runs UPDATE. If not, INSERT. `insert()` always runs a raw INSERT and skips lifecycle hooks and cascade logic. **Q:** Why use `DataSource` for transactions instead of the repository? **A:** The repository does not know about the transaction context. The `EntityManager` from `dataSource.transaction()` shares one database connection for the whole callback, so all operations run inside the same transaction. **Q:** What exactly does `autoLoadEntities: true` do? **A:** It tells TypeORM to register any entity passed to `TypeOrmModule.forFeature()` anywhere in the application. Without it, you would list every entity manually in `forRoot({ entities: [...] })`. **Q:** How do you avoid N+1 queries when loading relations? **A:** Use `createQueryBuilder` with `leftJoinAndSelect` instead of loading relations in a loop. For bounded, small result sets, `find({ relations: { posts: true } })` is also fine. **Q:** Can you run multiple database connections in one NestJS app? **A:** Yes. Call `TypeOrmModule.forRoot()` multiple times, each with a unique `name` option. Then pass that name as the second argument to `@InjectRepository(Entity, 'secondaryDb')`. ## Examples ### Basic CRUD service with duplicate check ```typescript // users/users.service.ts @Injectable() export class UsersService { constructor( @InjectRepository(User) private readonly usersRepo: Repository<User>, ) {} async findByEmail(email: string): Promise<User | null> { return this.usersRepo.findOneBy({ email }); } async createUser(dto: CreateUserDto): Promise<User> { const existing = await this.findByEmail(dto.email); if (existing) throw new ConflictException('Email already taken'); const user = this.usersRepo.create(dto); return this.usersRepo.save(user); } } ``` `findByEmail` uses the short `findOneBy` form. The duplicate check runs before saving, so the service returns a clear `409` instead of a raw database constraint error. ### Paginated list with total count ```typescript // articles/articles.service.ts async findPaginated(page: number, limit: number) { const [items, total] = await this.articlesRepo .createQueryBuilder('article') .leftJoinAndSelect('article.author', 'author') .where('article.published = :published', { published: true }) .orderBy('article.createdAt', 'DESC') .skip((page - 1) * limit) .take(limit) .getManyAndCount(); return { items, total, page, totalPages: Math.ceil(total / limit) }; } ``` `getManyAndCount()` returns a tuple: the result array and the total row count without pagination applied. One database round trip covers both. ### Order placement with a transaction ```typescript // orders/orders.service.ts async placeOrder(userId: number, dto: CreateOrderDto): Promise<Order> { return this.dataSource.transaction(async manager => { const product = await manager.findOneByOrFail(Product, { id: dto.productId }); if (product.stock < dto.quantity) { throw new BadRequestException('Not enough stock'); } const order = manager.create(Order, { userId, ...dto }); await manager.save(order); await manager.decrement(Product, { id: dto.productId }, 'stock', dto.quantity); return order; }); } ``` `findOneByOrFail` throws `EntityNotFoundError` when the product does not exist. NestJS maps that to a 500 by default. Add a global exception filter or a try/catch to return a 404 instead.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.