Best Practices for Developing Applications with NestJS

Best Practices for Developing Applications with NestJS

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.

Best Practices for Developing Applications with NestJS
Best Practices for 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.

Article Writter By

Long Phạm

"Thành công nuôi dưỡng sự hoàn hảo. Sự hoàn hảo lại nuôi lớn thất bại. Chỉ có trí tưởng tượng mới tồn tại."