Tech Corner - 30. June 2021
header_image

Inside Nestjs project

Nestjs is an enterprise-grade backend server solution based on Node.js heavily inspired by the Java server and framework Spring. If you know Java EE syntax, you will find many method names familiar.

Project kick-off

To create a new Nestjs project, you need only to run provided CLI and name the project as

shell

npx @nestjs/cli new ${APP NAME}

Command will scaffold the full structure of the app, and development can be started. What are the basic building blocks of Nestjs?

Modules

Modules are used to organize the application structure. Every application has the root module and descendant modules which form the hierarchical structure. Another feature of the module is to encapsulate the providers and define which providers should be accessible by other modules. Exported providers can be considered the public interface of the module.

javascript

 @Module({
  imports: [TypeOrmModule.forFeature([Record])],
  providers: [RecordsService, SeedingService, Logger],
  controllers: [RecordsController],
})
export class RecordsModule {}

Providers

Providers - the module or other modules can usually instantiate services, factories, repositories, or helpers. Providers can inject dependencies, and it is up to Nestjs to compose the dependencies graph at runtime.

Every provider has to be injectable and can be created as a singleton with an application lifetime or scoped to the lifetime of the request. Again, this is a nonstandard pattern helping to cover specific use cases. Providers might hook on multiple application life cycle events as bootstrapping or shut down.

Providers are explicitly the right ones to implement database operations or delegate business logic from the controllers.

javascript

@Injectable()
export class SeedingService implements OnApplicationBootstrap {
  constructor(
    @InjectRepository(Record)
    private readonly repository: Repository<Record>,
    @Inject(Logger)
    private readonly logger: LoggerService,
  ) {}
  async onApplicationBootstrap() { }
}

Controllers

Controllers in the Nestjs are components responsible for handling requests and providing responses for the specified group of requests. Routing defines which controller is used to process the request and which method to apply.

Usually, a single controller takes care of multiple routes and methods. To emphasize the DRY method and to make creating controllers simpler, Nestjs supports annotations.

Controller methods can benefit from a built-in set of standard exceptions, which are automatically mapped to the error responses or extend the set with custom exceptions.

javascript

@Controller('vaccination-records')
export class RecordsController implements CrudController<Record> {
  constructor(public service: RecordsService) {}

  @Get('export')
  async exportCsv(@Headers('Accept') accept, @Res() response: Response) {
    ...
  }
}

What are other utility classes?

Oh yes, Nestjs contains multiple components to control the request life cycle with fine graded distinction over them. In some other frameworks, most of the operations are handled in the middlewares. Nestjs provides the right tool for each job. This makes architecture easier to understand and cleaner.

Request life cycle looks as following:

null

Middlewares

Middlewares are used to modify the request and response before they are passed to the controllers. Middlewares are chained one after another until all of them run, or one ends the cycle.

Middlewares are executed only before the request is passed to the controller in contracts to Interceptors.

The most simple use case for the Middleware is logging.

javascript

@Injectable()
export class LoggerMiddleware implements NestMiddleware {

  use(req: Request, res: Response, next: NextFunction) {
    ...
    next();
  }
}

Pipes

Pipes, similarly to middlewares, are used to intercept the requests and apply some changes to them.

The difference is that pipes run in the context of the controller and its route handler. A pipe is running just before the controller method is executed.

Pipes can be constructed even as global and applied to all requests in the application.

Common use cases for pipes are value transformations as parsing or providing default values and validation. Nestjs provides the build-in set of pipes to cover both.

Validation pipes in conjunction with Typescript capabilities allow creating simple and powerful validation using annotations. This allows to attach validation to DTOs and have a single source of truth for every data object.

javascript

export class BaseRecordDto implements RecordInterface {

  @ApiProperty()
  @IsNotEmpty()
  @IsString()
  @MinLength(2)
  @MaxLength(100)
  lastName: string;

  @ApiProperty()
  @IsNumber({ allowInfinity: false })
  @Min(18)
  age: number;
}

Interceptors

Interceptors are similar to middlewares but can modify both requests before passing them to controllers and response returned from the controller. Interceptors can completely override the controller behavior for a defined route or even skip it.

A common use case for an interceptor is cache or logger. Similar to pipes, interceptors can be scoped to a single controller and method or used globally for the entire application.

javascript

@Injectable()
export class LoggingInterceptor implements NestInterceptor {

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {

    const now = Date.now();
    const path = context.switchToHttp().getRequest().route?.path;
    console.log(`Request path: ${path}`)

    return next
      .handle()
      .pipe(
        tap(() => console.log(`Request took ${Date.now() - now}ms`)),
      );
  }
}

Guards

Guards are used in Nestjs applications to provide authorization for the controller, which the guard attaches to.

It works similarly to middleware but provides the execution context. Execution context allows accessing request data, controller class, and next handler to be executed. Guards are executed after middlewares and before the pipes.

The denied request will respond with a default error response but can be further caught and refined with specific pipes.

javascript

@Injectable()
export class AuthGuard implements CanActivate {

  canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    return validateRequest(request);
  }
}

Exception filters

Nestjs application automatically catches all unhandled exceptions and responds tries to categorize them or respond with default internal server error.

This is happening in the default exception filter. Custom exception filters allow extending mapping of the application errors to the user-friendly response errors.

Exception filters have access to the Execution context (same as guards do) to provide rich decision context.

javascript

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const request = ctx.getRequest<Request>();
    const status = exception.getStatus();
  
    response
      .status(status)
      .json({
        statusCode: status,
        message: "Custom error message",
        errorCode: "0011",
      });
  }
}


Application bootstrapping

Application bootstrapping happens in a specific typescript module called main.ts

This is a default entry point of the app and defines the global handlers and extends application configuration.

All global components as pipes or providers are defined in the app creation method.

In our example, you can see that we replaced the default application logger with the Winston logger. Added the Swagger module to automatically generate the documentation for the endpoints. At last, there was added the global validation pipe to provide input validation for endpoints.

Nestjs as a powerful tool

As you can see, Nestjs is a mature and potent tool that was built with convention over configuration in mind and is providing a rich set of standard tools. Feel free to see a practical example in our repository, and stay tuned for the next blogs!

about the author

Jozef Radonak

blog author
At the forefront of Hotovo's web technology stream, Jozef strives to stay up to date with the latest web technologies and trends, and is always willing to help others master this ever-evolving industry. Outside of the world of web technology, he is an avid hiker and devoted coffee lover. He takes great pleasure in seeking out new coffee shops and trying different coffee blends. Jozef is all about innovation, whether in technology or in his cup of coffee!
blog author

READ MORE