Lesson 08-NestJS Injection Scopes and Dependencies

Injection Scopes

Injection scopes in NestJS v10 determine how the lifecycle of dependencies is managed. Properly configuring dependency scopes is crucial for improving application performance and maintainability.

Introduction to Injection Scopes

NestJS supports three primary scopes:

  • Singleton: A single instance is created for the entire application lifecycle.
  • Transient: A new instance is created each time the dependency is injected.
  • Request: A new instance is created for each HTTP request.

Singleton Scope

This is the default scope, meaning a single instance of each dependency is created for the entire application lifecycle.

// src/services/user.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class UserService {
  constructor() {
    console.log('UserService created');
  }

  getUser() {
    return 'John Doe';
  }
}

// src/app.module.ts
import { Module } from '@nestjs/common';
import { UserService } from './services/user.service';

@Module({
  providers: [UserService],
})
export class AppModule {}

Transient Scope

A new instance is created each time the dependency is injected.

// src/services/user.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class UserService {
  constructor() {
    console.log('UserService created');
  }

  getUser() {
    return 'John Doe';
  }
}

// src/app.module.ts
import { Module } from '@nestjs/common';
import { UserService } from './services/user.service';

@Module({
  providers: [
    {
      provide: UserService,
      useFactory: () => {
        console.log('Creating transient instance');
        return new UserService();
      },
      useClass: UserService,
      scope: 'transient',
    },
  ],
})
export class AppModule {}

Request Scope

A new instance is created for each HTTP request.

// src/services/user.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class UserService {
  constructor() {
    console.log('UserService created');
  }

  getUser() {
    return 'John Doe';
  }
}

// src/app.module.ts
import { Module } from '@nestjs/common';
import { UserService } from './services/user.service';

@Module({
  providers: [
    {
      provide: UserService,
      useFactory: () => {
        console.log('Creating request-scoped instance');
        return new UserService();
      },
      useClass: UserService,
      scope: 'request',
    },
  ],
})
export class AppModule {}

Using Dependencies in Controllers

You can inject dependencies into controllers and use them.

// src/controllers/users.controller.ts
import { Controller, Get } from '@nestjs/common';
import { UserService } from './services/user.service';

@Controller('users')
export class UsersController {
  constructor(private readonly userService: UserService) {}

  @Get()
  findAll() {
    return this.userService.getUser();
  }
}

Using Scope Decorators

You can use @InjectScoped or @InjectTransient decorators to specify the scope.

// src/services/user.service.ts
import { Injectable, InjectScoped } from '@nestjs/common';

@InjectScoped()
export class UserService {
  constructor() {
    console.log('UserService created');
  }

  getUser() {
    return 'John Doe';
  }
}

// src/app.module.ts
import { Module } from '@nestjs/common';
import { UserService } from './services/user.service';

@Module({
  providers: [UserService],
})
export class AppModule {}

Custom Scopes

You can define custom scopes to manage dependency lifecycles.

// src/services/user.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class UserService {
  constructor() {
    console.log('UserService created');
  }

  getUser() {
    return 'John Doe';
  }
}

// src/app.module.ts
import { Module, Provider } from '@nestjs/common';
import { UserService } from './services/user.service';

const customScopeProvider: Provider = {
  provide: UserService,
  useFactory: () => {
    console.log('Creating custom-scoped instance');
    return new UserService();
  },
  useClass: UserService,
  scope: 'custom',
};

@Module({
  providers: [customScopeProvider],
})
export class AppModule {}

Using Custom Scopes

You can use custom scopes to manage dependency lifecycles.

// src/services/user.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class UserService {
  constructor() {
    console.log('UserService created');
  }

  getUser() {
    return 'John Doe';
  }
}

// src/app.module.ts
import { Module, Provider } from '@nestjs/common';
import { UserService } from './services/user.service';

const customScopeProvider: Provider = {
  provide: UserService,
  useFactory: () => {
    console.log('Creating custom-scoped instance');
    return new UserService();
  },
  useClass: UserService,
  scope: 'custom',
};

@Module({
  providers: [customScopeProvider],
})
export class AppModule {}

Using Custom Scopes in Controllers

You can inject custom-scoped dependencies into controllers.

// src/controllers/users.controller.ts
import { Controller, Get } from '@nestjs/common';
import { UserService } from './services/user.service';

@Controller('users')
export class UsersController {
  constructor(private readonly userService: UserService) {}

  @Get()
  findAll() {
    return this.userService.getUser();
  }
}

Using Scope Decorators in Guards

You can use scope decorators in guards.

// src/guards/auth.guard.ts
import { Injectable, CanActivate, InjectScoped } from '@nestjs/common';

@InjectScoped()
export class AuthGuard implements CanActivate {
  canActivate() {
    console.log('AuthGuard created');
    return true;
  }
}

// src/app.module.ts
import { Module } from '@nestjs/common';
import { AuthGuard } from './guards/auth.guard';

@Module({
  providers: [AuthGuard],
})
export class AppModule {}

Using Scope Decorators in Pipes

Pipes can also use scope decorators.

// src/pipes/validation.pipe.ts
import { Injectable, PipeTransform, ArgumentMetadata, InjectScoped } from '@nestjs/common';

@InjectScoped()
export class ValidationPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    console.log('ValidationPipe created');
    return value;
  }
}

// src/app.module.ts
import { Module } from '@nestjs/common';
import { ValidationPipe } from './pipes/validation.pipe';

@Module({
  providers: [ValidationPipe],
})
export class AppModule {}

Using Scope Decorators in Filters

Filters can also use scope decorators.

// src/filters/http-exception.filter.ts
import { Injectable, Catch, ArgumentsHost, HttpException, InjectScoped } from '@nestjs/common';

@Catch(HttpException)
@InjectScoped()
export class HttpExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    console.log('HttpExceptionFilter created');
    // Handle exception...
  }
}

// src/app.module.ts
import { Module } from '@nestjs/common';
import { HttpExceptionFilter } from './filters/http-exception.filter';

@Module({
  providers: [HttpExceptionFilter],
})
export class AppModule {}

Dependencies

Dependency injection (DI) in NestJS v10 is a core feature that helps developers manage dependency relationships in a declarative manner.

Introduction to Dependency Injection

Dependency injection is a design pattern used to manage dependencies between objects. In NestJS, dependency injection is implemented using TypeScript classes and decorators.

Creating a Service

First, create a service class.

// src/services/user.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class UserService {
  constructor() {
    console.log('UserService created');
  }

  getUser() {
    return 'John Doe';
  }
}

Registering a Service

Next, register the service in a module.

// src/app.module.ts
import { Module } from '@nestjs/common';
import { UserService } from './services/user.service';

@Module({
  providers: [UserService],
})
export class AppModule {}

Injecting a Service in a Controller

You can inject and use the service in a controller.

// src/controllers/users.controller.ts
import { Controller, Get } from '@nestjs/common';
import { UserService } from './services/user.service';

@Controller('users')
export class UsersController {
  constructor(private readonly userService: UserService) {}

  @Get()
  findAll() {
    return this.userService.getUser();
  }
}

Using the @Inject Decorator

You can use the @Inject decorator to inject services.

// src/controllers/users.controller.ts
import { Controller, Get, Inject } from '@nestjs/common';
import { UserService } from './services/user.service';

@Controller('users')
export class UsersController {
  constructor(@Inject(UserService) private readonly userService: UserService) {}

  @Get()
  findAll() {
    return this.userService.getUser();
  }
}

Registering Services with useClass and useValue

In addition to registering classes directly, you can use useClass and useValue to register services.

// src/app.module.ts
import { Module, Provider } from '@nestjs/common';
import { UserService } from './services/user.service';

const userServiceProvider: Provider = {
  provide: UserService,
  useClass: UserService,
};

@Module({
  providers: [userServiceProvider],
})
export class AppModule {}

Creating Services with Factories

You can use factory functions to create services.

// src/app.module.ts
import { Module, Provider } from '@nestjs/common';
import { UserService } from './services/user.service';

const userServiceProvider: Provider = {
  provide: UserService,
  useFactory: () => {
    console.log('Creating UserService instance using factory');
    return new UserService();
  },
};

@Module({
  providers: [userServiceProvider],
})
export class AppModule {}

Injecting Multiple Services

You can inject multiple services into a controller.

// src/services/post.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class PostService {
  constructor() {
    console.log('PostService created');
  }

  getPost() {
    return 'Post #1';
  }
}

// src/app.module.ts
import { Module } from '@nestjs/common';
import { UserService } from './services/user.service';
import { PostService } from './services/post.service';

@Module({
  providers: [UserService, PostService],
})
export class AppModule {}

// src/controllers/users.controller.ts
import { Controller, Get } from '@nestjs/common';
import { UserService } from './services/user.service';
import { PostService } from './services/post.service';

@Controller('users')
export class UsersController {
  constructor(
    private readonly userService: UserService,
    private readonly postService: PostService,
  ) {}

  @Get()
  findAll() {
    return {
      user: this.userService.getUser(),
      post: this.postService.getPost(),
    };
  }
}

Asynchronous Service Registration

You can register services asynchronously.

// src/app.module.ts
import { Module, Provider } from '@nestjs/common';
import { UserService } from './services/user.service';

const userServiceProvider: Provider = {
  provide: UserService,
  useFactory: async () => {
    console.log('Creating UserService instance asynchronously');
    await new Promise(resolve => setTimeout(resolve, 1000));
    return new UserService();
  },
};

@Module({
  providers: [userServiceProvider],
})
export class AppModule {}

Using @Optional and @SkipSelf Decorators

You can use the @Optional and @SkipSelf decorators to control dependency injection behavior.

// src/controllers/users.controller.ts
import { Controller, Get, Optional, SkipSelf } from '@nestjs/common';
import { UserService } from './services/user.service';

@Controller('users')
export class UsersController {
  constructor(
    @Optional() @SkipSelf() private readonly userService?: UserService,
  ) {}

  @Get()
  findAll() {
    if (this.userService) {
      return this.userService.getUser();
    }
    return 'No UserService provided';
  }
}

Using the @Injectable Decorator

You can use the @Injectable decorator to mark service classes.

// src/services/user.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class UserService {
  constructor() {
    console.log('UserService created');
  }

  getUser() {
    return 'John Doe';
  }
}

Injecting Values with @Inject Decorator

You can use the @Inject decorator to inject values.

// src/app.module.ts
import { Module, Provider } from '@nestjs/common';

const configProvider: Provider = {
  provide: 'ConfigToken',
  useValue: {
    apiVersion: 'v1',
  },
};

@Module({
  providers: [configProvider],
})
export class AppModule {}

// src/controllers/users.controller.ts
import { Controller, Get, Inject } from '@nestjs/common';

@Controller('users')
export class UsersController {
  constructor(@Inject('ConfigToken') private readonly config: any) {}

  @Get()
  findAll() {
    return `API version: ${this.config.apiVersion}`;
  }
}

Injecting Classes with @Inject Decorator

You can use the @Inject decorator to inject classes.

// src/services/user.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class UserService {
  constructor() {
    console.log('UserService created');
  }

  getUser() {
    return 'John Doe';
  }
}

// src/app.module.ts
import { Module, Provider } from '@nestjs/common';
import { UserService } from './services/user.service';

const userServiceProvider: Provider = {
  provide: 'UserServiceToken',
  useClass: UserService,
};

@Module({
  providers: [userServiceProvider],
})
export class AppModule {}

// src/controllers/users.controller.ts
import { Controller, Get, Inject } from '@nestjs/common';
import { UserService } from './services/user.service';

@Controller('users')
export class UsersController {
  constructor(@Inject('UserServiceToken') private readonly userService: UserService) {}

  @Get()
  findAll() {
    return this.userService.getUser();
  }
}

Injecting Factories with @Inject Decorator

You can use the @Inject decorator to inject factories.

// src/app.module.ts
import { Module, Provider } from '@nestjs/common';
import { UserService } from './services/user.service';

const userServiceProvider: Provider = {
  provide: 'UserServiceToken',
  useFactory: () => {
    console.log('Creating UserService instance using factory');
    return new UserService();
  },
};

@Module({
  providers: [userServiceProvider],
})
export class AppModule {}

// src/controllers/users.controller.ts
import { Controller, Get, Inject } from '@nestjs/common';
import { UserService } from './services/user.service';

@Controller('users')
export class UsersController {
  constructor(@Inject('UserServiceToken') private readonly userService: UserService) {}

  @Get()
  findAll() {
    return this.userService.getUser();
  }
}

Injecting Asynchronous Factories with @Inject Decorator

You can use the @Inject decorator to inject asynchronous factories.

// src/app.module.ts
import { Module, Provider } from '@nestjs/common';
import { UserService } from './services/user.service';

const userServiceProvider: Provider = {
  provide: 'UserServiceToken',
  useFactory: async () => {
    console.log('Creating UserService instance asynchronously');
    await new Promise(resolve => setTimeout(resolve, 1000));
    return new UserService();
  },
};

@Module({
  providers: [userServiceProvider],
})
export class AppModule {}

// src/controllers/users.controller.ts
import { Controller, Get, Inject } from '@nestjs/common';
import { UserService } from './services/user.service';

@Controller('users')
export class UsersController {
  constructor(@Inject('UserServiceToken') private readonly userService: UserService) {}

  @Get()
  findAll() {
    return this.userService.getUser();
  }
}

Injecting Custom-Scoped Services with @Inject Decorator

You can use the @Inject decorator to inject services with custom scopes.

// src/services/user.service.ts
import { Injectable } from '@nestjs/common';

@Injectable({ scope: 'custom' })
export class UserService {
  constructor() {
    console.log('UserService created');
  }

  getUser() {
    return 'John Doe';
  }
}

// src/app.module.ts
import { Module, Provider } from '@nestjs/common';
import { UserService } from './services/user.service';

const userServiceProvider: Provider = {
  provide: 'UserServiceToken',
  useFactory: () => {
    console.log('Creating UserService instance using factory with custom scope');
    return new UserService();
  },
  scope: 'custom',
};

@Module({
  providers: [userServiceProvider],
})
export class AppModule {}

// src/controllers/users.controller.ts
import { Controller, Get, Inject } from '@nestjs/common';
import { UserService } from './services/user.service';

@Controller('users')
export class UsersController {
  constructor(@Inject('UserServiceToken') private readonly userService: UserService) {}

  @Get()
  findAll() {
    return this.userService.getUser();
  }
}

Injecting Guards with @Inject Decorator

You can use the @Inject decorator to inject guards.

// src/guards/auth.guard.ts
import { Injectable, CanActivate } from '@nestjs/common';

@Injectable()
export class AuthGuard implements CanActivate {
  constructor() {
    console.log('AuthGuard created');
  }

  canActivate() {
    return true;
  }
}

// src/app.module.ts
import { Module, Provider } from '@nestjs/common';
import { AuthGuard } from './guards/auth.guard';

const authGuardProvider: Provider = {
  provide: 'AuthGuardToken',
  useClass: AuthGuard,
};

@Module({
  providers: [authGuardProvider],
})
export class AppModule {}

// src/controllers/users.controller.ts
import { Controller, Get, UseGuards, Inject } from '@nestjs/common';
import { AuthGuard } from './guards/auth.guard';

@Controller('users')
export class UsersController {
  constructor(@Inject('AuthGuardToken') private readonly authGuard: AuthGuard) {}

  @UseGuards(AuthGuard)
  @Get()
  findAll() {
    return 'Users list';
  }
}

Injecting Pipes with @Inject Decorator

You can use the @Inject decorator to inject pipes.

// src/pipes/validation.pipe.ts
import { Injectable, PipeTransform } from '@nestjs/common';

@Injectable()
export class ValidationPipe implements PipeTransform {
  constructor() {
    console.log('ValidationPipe created');
  }

  transform(value: any, metadata: any) {
    return value;
  }
}

// src/app.module.ts
import { Module, Provider } from '@nestjs/common';
import { ValidationPipe } from './pipes/validation.pipe';

const validationPipeProvider: Provider = {
  provide: 'ValidationPipeToken',
  useClass: ValidationPipe,
};

@Module({
  providers: [validationPipeProvider],
})
export class AppModule {}

// src/controllers/users.controller.ts
import { Controller, Get, UsePipes, Inject } from '@nestjs/common';
import { ValidationPipe } from './pipes/validation.pipe';

@Controller('users')
export class UsersController {
  constructor(@Inject('ValidationPipeToken') private readonly validationPipe: ValidationPipe) {}

  @UsePipes(ValidationPipe)
  @Get()
  findAll() {
    return 'Users list';
  }
}

Injecting Filters with @Inject Decorator

You can use the @Inject decorator to inject filters.

// src/filters/http-exception.filter.ts
import { Injectable, Catch } from '@nestjs/common';
import { ArgumentsHost, HttpException } from '@nestjs/common';

@Injectable()
@Catch(HttpException)
export class HttpExceptionFilter {
  constructor() {
    console.log('HttpExceptionFilter created');
  }

  catch(exception: HttpException, host: ArgumentsHost) {
    console.log('Handling HTTP exception');
  }
}

// src/app.module.ts
import { Module, Provider } from '@nestjs/common';
import { HttpExceptionFilter } from './filters/http-exception.filter';

const httpExceptionFilterProvider: Provider = {
  provide: 'HttpExceptionFilterToken',
  useClass: HttpExceptionFilter,
};

@Module({
  providers: [httpExceptionFilterProvider],
})
export class AppModule {}

// src/controllers/users.controller.ts
import { Controller, Get, UseFilters, Inject } from '@nestjs/common';
import { HttpExceptionFilter } from './filters/http-exception.filter';

@Controller('users')
export class UsersController {
  constructor(@Inject('HttpExceptionFilterToken') private readonly httpExceptionFilter: HttpExceptionFilter) {}

  @UseFilters(HttpExceptionFilter)
  @Get()
  findAll() {
    throw new HttpException('Not found', 404);
  }
}

Injecting Interceptors with @Inject Decorator

You can use the @Inject decorator to inject interceptors.

// src/interceptors/logging.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  constructor() {
    console.log('LoggingInterceptor created');
  }

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    console.log('Before...');
    const now = Date.now();

    return next.handle().pipe(
      map(data => {
        console.log(`After... ${Date.now() - now}ms`);
        return data;
      }),
    );
  }
}

// src/app.module.ts
import { Module, Provider } from '@nestjs/common';
import { LoggingInterceptor } from './interceptors/logging.interceptor';

const loggingInterceptorProvider: Provider = {
  provide: 'LoggingInterceptorToken',
  useClass: LoggingInterceptor,
};

@Module({
  providers: [loggingInterceptorProvider],
})
export class AppModule {}

// src/controllers/users.controller.ts
import { Controller, Get, UseInterceptors, Inject } from '@nestjs/common';
import { LoggingInterceptor } from './interceptors/logging.interceptor';

@Controller('users')
export class UsersController {
  constructor(@Inject('LoggingInterceptorToken') private readonly loggingInterceptor: LoggingInterceptor) {}

  @UseInterceptors(LoggingInterceptor)
  @Get()
  findAll() {
    return 'Users list';
  }
}

Injecting Custom Providers with @Inject Decorator

You can use the @Inject decorator to inject custom providers.

// src/providers/custom.provider.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class CustomProvider {
  constructor() {
    console.log('CustomProvider created');
  }

  getCustomData() {
    return 'Custom data';
  }
}

// src/app.module.ts
import { Module, Provider } from '@nestjs/common';
import { CustomProvider } from './providers/custom.provider';

const customProvider: Provider = {
  provide: 'CustomProviderToken',
  useClass: CustomProvider,
};

@Module({
  providers: [customProvider],
})
export class AppModule {}

// src/controllers/users.controller.ts
import { Controller, Get, Inject } from '@nestjs/common';
import { CustomProvider } from './providers/custom.provider';

@Controller('users')
export class UsersController {
  constructor(@Inject('CustomProviderToken') private readonly customProvider: CustomProvider) {}

  @Get()
  findAll() {
    return this.customProvider.getCustomData();
  }
}

Injecting Environment Variables with @Inject Decorator

You can use the @Inject decorator to inject environment variables.

// src/config/config.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class ConfigService {
  constructor(@Inject('API_URL') private readonly apiUrl: string) {}

  getApiUrl() {
    return this.apiUrl;
  }
}

// src/app.module.ts
import { Module, Provider } from '@nestjs/common';
import { ConfigService } from './config/config.service';

const configProvider: Provider = {
  provide: 'API_URL',
  useValue: process.env.API_URL || 'http://localhost:3000/api',
};

@Module({
  providers: [ConfigService, configProvider],
})
export class AppModule {}

// src/controllers/users.controller.ts
import { Controller, Get } from '@nestjs/common';
import { ConfigService } from './config/config.service';

@Controller('users')
export class UsersController {
  constructor(private readonly configService: ConfigService) {}

  @Get()
  findAll() {
    return `API URL: ${this.configService.getApiUrl()}`;
  }
}
Share your love