Skip to main content
Advertisement

14.3 Dependency Injection — NestJS DI System and Provider Types

Dependency Injection (DI) Overview

NestJS's DI container automates object creation and dependency management. Register services with @Injectable() decorator and resolve dependencies through constructor injection.

// DI flow
// Module → DI Container → Controller/Service
// ↑
// Provider registration and injection

Provider Types

useClass (Default)

// users/users.module.ts
import { Module } from '@nestjs/common'
import { UsersService } from './users.service'

@Module({
providers: [
UsersService, // Shorthand — { provide: UsersService, useClass: UsersService }
],
})
export class UsersModule {}

useValue

// Inject configuration values or constants
const DB_CONFIG = {
host: 'localhost',
port: 5432,
database: 'mydb',
}

@Module({
providers: [
{
provide: 'DB_CONFIG',
useValue: DB_CONFIG,
},
],
})
export class DatabaseModule {}

// Usage
@Injectable()
export class DatabaseService {
constructor(@Inject('DB_CONFIG') private config: typeof DB_CONFIG) {}
}

useFactory

// Dynamically created Provider
@Module({
providers: [
{
provide: 'DATABASE_CONNECTION',
useFactory: async (configService: ConfigService) => {
return createConnection({
url: configService.get('DATABASE_URL'),
})
},
inject: [ConfigService], // Inject dependency
},
],
})
export class DatabaseModule {}

Interface-based DI

// interfaces/user-repository.interface.ts
export interface IUserRepository {
findById(id: string): Promise<User | null>
findAll(): Promise<User[]>
create(data: CreateUserDto): Promise<User>
update(id: string, data: UpdateUserDto): Promise<User>
delete(id: string): Promise<void>
}

// Token definition
export const USER_REPOSITORY = 'USER_REPOSITORY'
// repositories/user.repository.ts
import { Injectable } from '@nestjs/common'
import { IUserRepository } from '../interfaces/user-repository.interface'

@Injectable()
export class UserRepository implements IUserRepository {
async findById(id: string): Promise<User | null> {
// Actual DB query
return null
}
// ... remaining methods
}
// users.module.ts
@Module({
providers: [
{
provide: USER_REPOSITORY,
useClass: UserRepository,
},
UsersService,
],
})
export class UsersModule {}

// users.service.ts
@Injectable()
export class UsersService {
constructor(
@Inject(USER_REPOSITORY)
private readonly userRepository: IUserRepository
) {}
}

Scope

import { Injectable, Scope } from '@nestjs/common'

// DEFAULT: Singleton (default) — one instance for the entire app
@Injectable()
export class SingletonService {}

// REQUEST: New instance per request
@Injectable({ scope: Scope.REQUEST })
export class RequestScopedService {
// Can store per-request state
}

// TRANSIENT: New instance each time injected
@Injectable({ scope: Scope.TRANSIENT })
export class TransientService {}

Interceptors and Pipe Types

Interceptor

import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common'
import { Observable } from 'rxjs'
import { map, tap } from 'rxjs/operators'

// Response transform interceptor
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, { data: T }> {
intercept(
context: ExecutionContext,
next: CallHandler
): Observable<{ data: T }> {
return next.handle().pipe(
map(data => ({ data })) // Wrap response in { data: ... } format
)
}
}

// Logging interceptor
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
const req = context.switchToHttp().getRequest()
const { method, url } = req
const start = Date.now()

return next.handle().pipe(
tap(() => {
const duration = Date.now() - start
console.log(`${method} ${url} ${duration}ms`)
})
)
}
}

Custom Pipe

import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common'
import { validate } from 'class-validator'
import { plainToInstance } from 'class-transformer'

@Injectable()
export class ValidationPipe implements PipeTransform<unknown> {
async transform(value: unknown, { metatype }: ArgumentMetadata) {
if (!metatype || !this.toValidate(metatype)) {
return value
}

const object = plainToInstance(metatype, value)
const errors = await validate(object)

if (errors.length > 0) {
throw new BadRequestException('Validation failed')
}

return object
}

private toValidate(metatype: Function): boolean {
const types: Function[] = [String, Boolean, Number, Array, Object]
return !types.includes(metatype)
}
}

Guard

import {
Injectable,
CanActivate,
ExecutionContext,
UnauthorizedException,
} from '@nestjs/common'
import { Reflector } from '@nestjs/core'
import { JwtService } from '@nestjs/jwt'

@Injectable()
export class AuthGuard implements CanActivate {
constructor(
private jwtService: JwtService,
private reflector: Reflector
) {}

async canActivate(context: ExecutionContext): Promise<boolean> {
// Check Public decorator
const isPublic = this.reflector.getAllAndOverride<boolean>('isPublic', [
context.getHandler(),
context.getClass(),
])
if (isPublic) return true

const request = context.switchToHttp().getRequest()
const token = request.headers.authorization?.replace('Bearer ', '')

if (!token) {
throw new UnauthorizedException('Authentication required.')
}

try {
const payload = await this.jwtService.verifyAsync(token)
request.user = payload
return true
} catch {
throw new UnauthorizedException('Invalid token.')
}
}
}

Pro Tips

Custom Decorators

import { createParamDecorator, ExecutionContext, SetMetadata } from '@nestjs/common'

// Extract current user decorator
export const CurrentUser = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest()
return request.user
}
)

// Public route decorator
export const Public = () => SetMetadata('isPublic', true)

// Roles decorator
export const Roles = (...roles: string[]) => SetMetadata('roles', roles)

// Usage
@Get('profile')
@UseGuards(AuthGuard)
getProfile(@CurrentUser() user: AuthUser) {
return user
}

@Get('public-data')
@Public()
getPublicData() {
return { message: 'Accessible to everyone' }
}
Advertisement