NestJS is a powerful framework for building scalable and maintainable server-side applications using Node.js. In this article, we will cover some best practices that can help you write clean and efficient code when developing applications with NestJS.
1. Use Dependency Injection
Dependency Injection (DI) is a design pattern that allows you to inject dependencies into a class instead of creating them inside the class. This makes your code more modular and testable. NestJS has built-in support for DI, and it is recommended to use it whenever possible. You can use the @Injectable
decorator to mark a class as a provider, and then inject it into other classes using the @Inject
decorator.
@Injectable() export class CatsService { // ... } @Injectable() export class CatsController { constructor(private readonly catsService: CatsService) {} // ... }
2. Use Interceptors
Interceptors are a powerful feature of NestJS that allow you to intercept incoming requests and outgoing responses. You can use interceptors to modify the data before it is sent to the client, or to add custom headers to the response. You can use the @UseInterceptors
decorator to apply an interceptor to a controller or a specific method.
@Injectable() export class LoggingInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { console.log('Before...'); const now = Date.now(); return next .handle() .pipe(tap(() => console.log(`After... ${Date.now() - now}ms`))); } } @Controller('cats') @UseInterceptors(LoggingInterceptor) export class CatsController { // ... }
3. Use Guards
Guards are another powerful feature of NestJS that allow you to control access to your endpoints based on certain conditions. You can use guards to check if a user is authenticated, or to limit access to certain endpoints based on a user’s role. You can use the @UseGuards
decorator to apply a guard to a controller or a specific method.
@Injectable() export class AuthGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest(); return request.isAuthenticated(); } } @Controller('cats') @UseGuards(AuthGuard) export class CatsController { // ... }
4. Use Pipes
Pipes are a way to transform the data that is sent to your endpoint. You can use pipes to validate the data, or to transform it into a different format. You can use the built-in pipes provided by NestJS, or you can create your own custom pipes. You can use the @UsePipes
decorator to apply a pipe to a controller or a specific method.
@Controller('cats') export class CatsController { @Post() @UsePipes(new JoiValidationPipe(createCatSchema)) create(@Body() createCatDto: CreateCatDto) { this.catsService.create(createCatDto); } }
5. Use DTOs
DTOs (Data Transfer Objects) are objects that are used to transfer data between different layers of your application. You can use DTOs to validate and transform the data that is sent to your endpoint, and to prevent exposing sensitive information to the client. You can use the class-validator
library to validate your DTOs, and the class-transformer
library to transform your DTOs into different formats.
export class CreateCatDto { @IsString() @IsNotEmpty() readonly name: string; @IsString() @IsNotEmpty() readonly breed: string; @IsOptional() @IsNumber() readonly age?: number; }
6. Use Configurations
Configurations allow you to define application settings that can be easily changed without modifying the code. You can use the @nestjs/config
package to load configurations from environment variables, configuration files, or command-line arguments. It is recommended to define your configurations in a separate module and use the ConfigService
to access them.
@Module({ imports: [ConfigModule.forRoot()], }) export class AppModule { constructor(private readonly configService: ConfigService) {} get port(): number { return this.configService.get<number>('PORT'); } }
7. Use Logging
Logging is an important part of any application, as it allows you to troubleshoot issues and monitor the performance of your application. You can use the built-in logging module provided by NestJS, or you can use a third-party logging library. It is recommended to define a logger that conforms to the LoggerService
interface and use it throughout your application.
@Injectable() export class MyLogger implements LoggerService { log(message: string) { // ... } error(message: string, trace: string) { // ... } warn(message: string) { // ... } debug(message: string) { // ... } verbose(message: string) { // ... } } @Module({ providers: [{ provide: LoggerService, useClass: MyLogger }], }) export class AppModule {}
8. Use Testing
Testing is an essential part of developing any application, as it allows you to ensure that your code works as expected and to catch any bugs before they make it to production. NestJS provides built-in support for testing, and it is recommended to write tests for your application using tools such as Jest or Mocha.
describe('CatsService', () => { let service: CatsService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [CatsService], }).compile(); service = module.get<CatsService>(CatsService); }); it('should be defined', () => { expect(service).toBeDefined(); }); });
Conclusion
In this article, we covered some best practices for developing applications with NestJS. By following these best practices, you can write clean and efficient code that is easy to maintain and test.