Database Integration
⭐ Interview Importance: MEDIUM
⏱️ Revision Time: 9 min
NestJS is database-agnostic. It does not force you to use a specific database or Object Relational Mapper (ORM), but it provides seamless integrations for the most popular options in the Node.js ecosystem.
Overview
Because NestJS relies heavily on Dependency Injection, integrating a database usually involves importing a specific Module (like TypeOrmModule or MongooseModule) into your root AppModule.
This root module creates a singleton connection to the database. You then import “Feature Modules” into your domain modules (e.g., UsersModule) which provide access to specific tables/collections (Repositories or Models).
Key Concepts
forRoot()/forRootAsync(): The standard pattern for initializing a global database connection in theAppModule. You pass in your credentials, host, and port.forFeature(): The pattern used in domain modules to register specific entities/schemas, making their corresponding repositories available for injection in your services.- Entity/Schema: A TypeScript class that represents a single table (SQL) or collection (NoSQL) in your database.
Code Examples
The General Integration Pattern (Using TypeORM as an example)
1. Connecting to the Database (Root Module)
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersModule } from './users/users.module';
import { User } from './users/user.entity';
@Module({
imports: [
// 1. Establish the global connection
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'db_user',
password: 'db_password',
database: 'my_app_db',
entities: [User], // List all entities here
synchronize: process.env.NODE_ENV !== 'production',
}),
// 2. Import your feature modules
UsersModule,
],
})
export class AppModule {}
2. Registering an Entity (Feature Module)
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { User } from './user.entity';
@Module({
imports: [
// 3. Register the specific 'User' entity for this module
// This creates a 'UserRepository' under the hood.
TypeOrmModule.forFeature([User])
],
providers: [UsersService],
controllers: [UsersController],
})
export class UsersModule {}
3. Injecting the Repository (Service)
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
@Injectable()
export class UsersService {
constructor(
// 4. Inject the auto-generated repository to query the DB!
@InjectRepository(User)
private usersRepository: Repository<User>,
) {}
findAll(): Promise<User[]> {
return this.usersRepository.find();
}
}
Best Practices
- Never Hardcode Credentials: Never put raw passwords or host strings in your
forRoot()configuration. Always useforRootAsync()combined with theConfigModuleto load these values from.envfiles. - Understand
synchronize: Many ORMs (like TypeORM) have asynchronize: trueoption. This automatically alters your database schema to match your TypeScript code every time the server starts. NEVER use this in production. It can accidentally drop tables or columns and cause catastrophic data loss. Use migrations instead.