NestJS through the eyes of developer

episode #2 - NestJS API request pipeline

2023

@ValentinKononov

What we'll cover today

# plan
  • Request handling pipeline - what happens with request
  • NestJS building bricks for request handling, abstraction layer
  • Pipes for Validation of incoming requests
  • Interceptors
  • Guards
  • Exceptions

  • Hands-on experience

# repo

Samples added to repo

2023

@ValentinKononov

https://github.com/valentinkononov/nester
# framework

Journey of Request

Middleware

Guards

Before Router

After Router

Exception Filters

Pipes

Interceptors

Controller

# middleware

Middleware

@Injectable()
export class CustomMiddleware implements NestMiddleware {
    use(req: Request, res: Response, next: () => void): any {
        Logger.debug('Middleware');
        next();
    }
}
# guards

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

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

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

Exception 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

Let's code it!

2023

@ValentinKononov

Made with Slides.com