How do you connect NestJS to a SQL database with TypeORM?
Configure the connection once with
TypeOrmModule.forRootAsync, register entities per feature withforFeature, and inject a repository with@InjectRepository.@Module({ imports: [ TypeOrmModule.forRootAsync({ inject: [ConfigService], useFactory: (config: ConfigService) => ({ type: 'postgres', url: config.get('DATABASE_URL'), autoLoadEntities: true, synchronize: false, }), }), UsersModule, ], }) export class AppModule {} @Module({ imports: [TypeOrmModule.forFeature([User])], providers: [UsersService] }) export class UsersModule {} @Injectable() export class UsersService { constructor(@InjectRepository(User) private users: Repository<User>) {} findByEmail(email: string) { return this.users.findOneBy({ email }); } }Why should
synchronize: truenever be used in production?Because TypeORM then alters the database schema at every startup to match your entities, with no review and no history. Renaming a property can drop the column and its data, and two instances starting at once can race on the same changes.
Use migrations instead: generate a migration from entity changes, review the SQL, commit it, and run it as a deploy step.
npx typeorm migration:generate src/migrations/AddOrderStatus -d src/data-source.ts npx typeorm migration:run -d src/data-source.tssynchronize: trueis fine for a throwaway local database or a test run against an empty database.How do you use Prisma in a NestJS application?
There is no official Nest module; the usual pattern is a
PrismaServicethat extends the generated client, connects in a lifecycle hook, and is exported from a module.@Injectable() export class PrismaService extends PrismaClient implements OnModuleInit { async onModuleInit() { await this.$connect(); } } @Global() @Module({ providers: [PrismaService], exports: [PrismaService] }) export class PrismaModule {} @Injectable() export class OrdersService { constructor(private prisma: PrismaService) {} recent() { return this.prisma.order.findMany({ orderBy: { createdAt: 'desc' }, take: 20 }); } }The schema lives in
schema.prisma;prisma migrate devcreates migrations andprisma migrate deployapplies them in production. From Prisma 7, the client is generated into your project and needs a driver adapter, passed withsuper({ adapter })in a constructor.How do you use MongoDB with Mongoose in NestJS?
@nestjs/mongooseprovidesMongooseModule.forRoot()for the connection, decorators to define a schema from a class, and@InjectModel()for the model.@Schema({ timestamps: true }) export class Product { @Prop({ required: true }) name: string; @Prop({ min: 0 }) price: number; @Prop({ index: true }) category: string; } export const ProductSchema = SchemaFactory.createForClass(Product); @Module({ imports: [MongooseModule.forFeature([{ name: Product.name, schema: ProductSchema }])], providers: [ProductsService], }) export class ProductsModule {} @Injectable() export class ProductsService { constructor(@InjectModel(Product.name) private products: Model<Product>) {} cheap() { return this.products.find({ price: { $lt: 10 } }).lean(); } }.lean()returns plain objects, which are faster to serialize when you do not need document methods. The MongoDB driver underneath works without Mongoose too.TypeORM or Prisma with NestJS: what are the differences?
TypeORM Prisma Model defined in Entity classes with decorators schema.prismafileTypes From your classes Generated from the schema Queries Repository API, QueryBuilder Typed client, results typed per selectNest integration Official @nestjs/typeormA small PrismaServiceyou writeMigrations Generated from entity diff prisma migratefrom schema diffPrisma's result types follow each query's
select, which catches more mistakes at compile time. TypeORM fits teams that want entities as classes. Either way, keep the ORM behind your services.How do you run several database writes in one transaction in NestJS?
Use the ORM's transaction API and make every write inside it use the transactional handle, not the injected repository.
// TypeORM async placeOrder(dto: PlaceOrderDto) { return this.dataSource.transaction(async (manager) => { const order = await manager.save(Order, { userId: dto.userId }); await manager.decrement(Stock, { productId: dto.productId }, 'quantity', 1); return order; }); } // Prisma await this.prisma.$transaction(async (tx) => { await tx.order.create({ data: { userId } }); await tx.stock.update({ where: { productId }, data: { quantity: { decrement: 1 } } }); });The classic bug: calling
this.ordersRepository.save()inside the callback, which runs on another connection outside the transaction. To pass a transaction through several services without threading the manager, teams useAsyncLocalStoragebased libraries (such as@nestjs-cls/transactional).Should services call the ORM directly or go through a repository class?
For a small app, a service using the ORM's repository directly is fine. Add your own repository layer when queries grow complex or are reused, or when you want business logic testable without the ORM.
@Injectable() export class OrdersRepository { constructor(@InjectRepository(Order) private repo: Repository<Order>) {} findOpenForUser(userId: string) { return this.repo.find({ where: { userId, status: In(['pending', 'paid']) }, order: { createdAt: 'DESC' }, }); } }The service then talks in domain terms (
findOpenForUser) and a unit test mocks one small class instead of the ORM's query API. Do not wrap every ORM method one to one: that is code without a purpose.What is the N+1 query problem, and how do you fix it in a NestJS API?
N+1 happens when you load a list with one query and then run one more query per row to load a relation: 1 query for 50 orders, plus 50 for their customers.
Fixes, from the most common:
- Load the relation in the same query: TypeORM
relations: { customer: true }or aleftJoinAndSelect, Prismainclude: { customer: true }. - Batch the lookups: collect the ids, run one
WHERE id IN (...), and map the results. - In GraphQL resolvers, use DataLoader per request, which batches every
customerfield resolved in one tick into one query.
Find it by logging queries in development (
logging: truein TypeORM) and watching the count per request, not the time.- Load the relation in the same query: TypeORM
How do you keep database credentials out of the code in NestJS?
Read them from environment variables through
@nestjs/config, validate them at startup, and injectConfigServiceinto the async module factory.ConfigModule.forRoot({ isGlobal: true, validate: (env) => EnvSchema.parse(env), // fail fast on a missing variable }), TypeOrmModule.forRootAsync({ inject: [ConfigService], useFactory: (config: ConfigService) => ({ type: 'postgres', url: config.getOrThrow<string>('DATABASE_URL'), }), }),Never commit
.envfiles with real secrets; production values come from the platform's secret store. In NestJS 12,@nestjs/configvalidates with Standard Schema (Zod and similar), and Joi still works from Joi 18 on.How do you run database migrations safely when deploying a NestJS service?
Run migrations as a separate deploy step before the new version takes traffic, not from
onModuleInitin every instance, where several replicas would race.Write changes so the old and new code both work during the rollout (expand and contract):
- Add the new column as nullable, deploy code that writes both.
- Backfill the data.
- Deploy code that reads only the new column.
- Drop the old column in a later release.
Renaming a column in one step breaks the instances still running the old code. Keep each migration small, reviewed as SQL, and test it on a copy of production data before it runs for real.
NestJS Database Interview Questions
Connecting TypeORM, Prisma and Mongoose, transactions, migrations and N+1 queries.
10 questions