episode #2 - NestJS API request pipeline
2023
@ValentinKononov
# plan
Exceptions
Hands-on experience
# repo
2023
@ValentinKononov
https://github.com/valentinkononov/nester
# framework
Middleware
Guards
Before Router
After Router
Exception Filters
Pipes
Interceptors
Controller
# middleware
@Injectable()
export class CustomMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: () => void): any {
Logger.debug('Middleware');
next();
}
}
# guards
@Injectable()
export class CustomGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
Logger.debug('Guard');
const request = context.switchToHttp().getRequest();
return true;
}
}
export interface ExecutionContext extends ArgumentsHost {
/**
* Returns the *type* of the controller class which the current handler belongs to.
*/
getClass<T = any>(): Type<T>;
/**
* Returns a reference to the handler (method) that will be invoked next in the
* request pipeline.
*/
getHandler(): Function;
}
# interceptors
@Injectable()
export class PerformanceInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
Logger.debug('Interceptor started');
const now = Date.now();
return next.handle().pipe(
tap(() => {
Logger.debug('Interceptor completed');
Logger.log(
`Request completed in: ${Date.now() - now}ms`,
);
}),
);
}
}
export interface NestInterceptor<T = any, R = any> {
/**
* @param context an `ExecutionContext` object providing methods to access the
* route handler and class about to be invoked.
* @param next a reference to the `CallHandler`, which provides access to an
* `Observable` representing the response stream from the route handler.
*/
intercept(context: ExecutionContext, next: CallHandler<T>): Observable<R> | Promise<Observable<R>>;
}
# pipes
@Injectable()
export class LogPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata): any {
Logger.debug('Pipe');
Logger.debug(metadata.data);
return value;
}
}
export interface ArgumentMetadata {
// Indicates whether argument is a body, query, param, or custom parameter
readonly type: Paramtype;
// Underlying base type (e.g., `String`) of the parameter, based on the type
// definition in the route handler.
readonly metatype?: Type<any> | undefined;
// String passed as an argument to the decorator.
// Example: `@Body('userId')` would yield `userId`
readonly data?: string | undefined;
}
export interface PipeTransform<T = any, R = any> {
// Method to implement a custom pipe. Called with two parameters
// @param value argument before it is received by route handler method
// @param metadata contains metadata about the value
transform(value: T, metadata: ArgumentMetadata): R;
}
# exceptions filters
@Catch(HttpException)
export class CustomExceptionFilter implements ExceptionFilter {
constructor(private readonly httpAdapterHost: HttpAdapterHost) {}
catch(exception: HttpException, host: ArgumentsHost): void {
Logger.debug('Exception');
Logger.error(exception.message, exception.stack);
const { httpAdapter } = this.httpAdapterHost;
const ctx = host.switchToHttp();
const path = httpAdapter.getRequestUrl(ctx.getRequest());
httpAdapter.reply(ctx.getResponse(), {...}, 500);
Logger.error('Request URL:', path);
}
}
# code
2023
@ValentinKononov