Data Validation
transformation
When it is not valid - throw an exception
If valid - pass it through unchanged
of data to the desired type
Pipes target the arguments of the Controller handler functions
ValidationPipe
ParseIntPipe
ParseBoolPipe
ParseArrayPipe
ParseUUIDPipe
DefaultValuePipe
To a single route handler argument
To the route handler method
To the Controller class
Globally at the app level
import { Get, Param, ParseIntPipe } from '@nestjs/common';
...
@Get(':id')
getPetByID(@Param('id', ParseIntPipe) id: string) {
// if :id is not a numeric string - ParseIntPipe throws
return this.petsService.getPetByID(id);
}
To a single route handler argument
pets.controller.ts
import { Post, Body, ValidationPipe } from '@nestjs/common';
...
@Post()
@UsePipes(ValidationPipe)
createPet(@Body() createPetDto: CreatePetDto) {
return this.petsService.createPet(createPetDto);
}
To the route handler method
pets.controller.ts
pets.controller.ts
import { ... , ValidationPipe } from '@nestjs/common';
@Controller('pets')
@UsePipes(ValidationPipe)
export class PetsController {
constructor(private readonly petsService: PetsService) {}
...
}
To the Controller class
main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
Globally at the app level
app.module.ts
import { APP_PIPE } from '@nestjs/core';
@Module({
imports: [PetsModule],
providers: [
{ provide: APP_PIPE, useClass: ValidationPipe }
],
})
export class AppModule {}
At the Module level
Nest has many built-in exception types you can throw
Use them as is or extend and customize them.
BadRequestException
UnauthorizedException
NotFoundException
ForbiddenException
NotAcceptableException
RequestTimeoutException
ConflictException
GoneException
HttpVersionNotSupportedException
PayloadTooLargeException
UnsupportedMediaTypeException
UnprocessableEntityException
InternalServerErrorException
NotImplementedException
ImATeapotException
MethodNotAllowedException
BadGatewayException
ServiceUnavailableException
GatewayTimeoutException
PreconditionFailedException
You can also define your own custom exception filters
What
This is handled by the built-in global exception filter
Exceptions you throw, or unhandled runtime errors are caught by Nest, which automatically sends an appropriate user-friendly response.
for more control over handling output of those errors
i.e. stdout, error log files, error aggregation services,
monitoring, analysis, formatting etc...
import { Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';
import * as log from '@ajar/marker';
@Catch()
export class ExceptionsLoggerFilter extends BaseExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
console.log('Exception thrown', exception.message); //Boring...
log.error(exception); //Fancy color logger... :)
super.catch(exception, host);
}
}
Other than logging to the console you might want to
write an entry in a log file, send a notification to admins
aggregate errors in a service like:
- sentry.io
- elastic.co/logstash
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
response
.status(status)
.json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
Accessing the response object directly to gain full control on the response
object values and shape.
The @Catch() decorator may take a single parameter
or a comma-separated list.
@Post()
@UseFilters(ExceptionsLoggerFilter)
async create(@Body() createCatDto: CreateCatDto) {
throw new ForbiddenException();
}
At the route handler method
As soon as an uncaught exception is encountered, the rest of the lifecycle is ignored and the request skips straight to the filter.
The @UseFilters() decorator may take a single filter or a comma-separated list.
@UseFilters(new ExceptionsLoggerFilter())
export class PetsController {}
At the Controller class level
If several filters exist at different levels - only one filter will be activated.
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new ExceptionsLoggerFilter());
await app.listen(3000);
}
bootstrap();
Global scoped filter
Outside of the application scope. No DI...
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
@Module({
providers: [
{ provide: APP_FILTER, useClass: ExceptionsLoggerFilter}
],
})
export class AppModule {}
Better to use at the Module level
A function or a sequence of functions
called before the route handler
Nest middleware are essentially express middleware
They can access the request and response objects,
and the next() middleware function in the queue
Execute code
Apply changes to the request and/or the response objects
End the request-response cycle or redirect to another route.
Must call the next() middleware in the queue if it didn't redirect or ended the response cycle.
* Otherwise, the request is left hanging and doesn't reach the route handler
logger.middleware.ts
Example
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log(`${req.method} ${req.originalUrl} ${res.statusCode}`);
next();
}
}
app.module.ts
Apply using a Module
Modules that include middleware have to implement the NestModule interface
Use the configure() method of the module class.
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { LoggerMiddleware } from './middleware/logger.middleware';
import { PetsModule } from './pets/pets.module';
@Module({
imports: [PetsModule],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggerMiddleware).forRoutes('pets');
}
}
The .forRoutes() method
Can take a single string, multiple strings, a RouteInfo object, a controller class, a list of controllers separated by commas, wildcard routes similar to express
Routes wildcards
a single string
a RouteInfo object
a Controller class
.forRoutes('pets');
.forRoutes({ path: 'cats', method: RequestMethod.GET });
.forRoutes({ path: 'ab*cd', method: RequestMethod.ALL });
.forRoutes(PetsController);
Excluding routes
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.exclude(
{ path: 'pets', method: RequestMethod.PUT },
{ path: 'pets', method: RequestMethod.DELETE },
'pets/(.*)', // exclude nested routes...
)
.forRoutes(PetsController);
}
}
Declared as a function
import { Request, Response, NextFunction } from 'express';
export function logger(req: Request, res: Response, next: NextFunction) {
console.log(`${req.method} ${req.originalUrl} ${res.statusCode}`);
next();
}
logger.middleware.ts
consumer
.apply(logger)
.forRoutes(PetsController);
app.module.ts
use multiple middleware functions
import * as helmet from 'helmet';
import * as morgan from 'morgan';
import * as responseTime from 'response-time';
@Module({
imports: [PetsModule],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(helmet(), morgan('dev'), responseTime(), logger)
.forRoutes(PetsController);
}
}
app.module.ts
Apply middleware globally
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(helmet(), morgan('dev'), responseTime(), logger);
await app.listen(3000);
}
bootstrap()
main.ts
Guards have a single responsibility. They either allow a request to be handled by a route handler or not.
As their name implies, they protect our routes handlers
This is determined by a set of authorization conditions
like permissions, roles, ACLs, etc.
Unlike middleware that knows nothing about the next() step in the chain, Guards know exactly what's going to be executed next via an ExecutionContext instance.
What
A guard is a class annotated with the @Injectable() decorator.
Guards should implement the CanActivate interface.
Much like Pipes or exception filters, Guards can be:
controller-scoped, method-scoped, or global-scoped.
How
They make it possible to:
Bind extra logic before / after method execution
Transform the result returned from a function
Transform the exception thrown from a function
Extend the basic function behavior
Completely override a function depending on specific conditions (e.g., for caching purposes)
What
Should implement the NestInterceptor interface.
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@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`)),
);
}
}
The CallHandler interface implements the handle() method
A class annotated with the @Injectable() decorator
How
@UseInterceptors(LoggingInterceptor)
export class PetsController {}
At the route handler method
At the Controller class level
@Post()
@UseInterceptors(LoggingInterceptor)
createPet(@Body() createPetDto: CreatePetDto) {
return this.petsService.createPet(createPetDto);
}
How
Applied to all handler methods of the Controller
What
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new LoggingInterceptor());
Global scoped
Outside of the application scope. No DI...
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
@Module({
providers: [
{ provide: APP_INTERCEPTOR, useClass: LoggingInterceptor }
],
})
export class AppModule {}
Better to use at the Module level
Incoming request
The complete request lifecycle is: