In Nest+GrqphQL+Prisma+React Full Stack Development series, I will lead you to learn how to use these technology stacks step by step and develop a practical application.

This is the third article, Nest.

  • GraphQL
  • Prisma

A list,

Nestjs is an efficient NodeJS server application development framework with high scalability and high performance, including native SUPPORT for TS, and the underlying is built on a powerful Http framework, you can choose to use Express or Fastify, and support modularity, microservices, decorator syntax. Dependency injection (handing over instantiation of your own dependencies to external containers for control) and so on.

It’s very powerful and useful.

Second, the structure of

Nest mainly consists of the following parts:

  • The controller
  • The provider
  • The module
  • The middleware
  • Abnormal filter
  • The pipe
  • The guards
  • The interceptor
  • A decorator

Let’s take a brief look at these modules one by one:

(1) Controller

A controller is responsible for processing incoming requests and returning responses to the client, and a controller contains multiple routes.

/* cats.controller.ts */ import { Controller, Get, Post, Res, HttpStatus } from '@nestjs/common'; import { Response } from 'express'; @Controller('cats') export class CatsController { @Post() create(@Res() res: Response) { res.status(HttpStatus.CREATED).send(); } @Get() findAll(@Res() res: Response) { res.status(HttpStatus.OK).json([]); }}Copy the code

(2) Providers

Classes annotated with the @Injectable() decorator, such as Service, repository, Factory, helper, etc. in Nest, can be considered providers. Dependency management in Nest is implemented through the dependency injection design pattern, typically injected through the controller’s constructor:

constructor(private readonly catsService: CatsService) {}
Copy the code

It is also possible to Inject based on attributes using @inject () :

import { Injectable, Inject } from '@nestjs/common';

@Injectable()
export class HttpService<T> {
  @Inject('HTTP_OPTIONS')
  private readonly httpClient: T;
}
Copy the code

(3) Modules

Modules are classes with @Module() decorators. The @Module() decorator provides metadata that Nest uses to organize the application. Every Nest application has at least one module, the root module. The root module is where Nest begins to arrange the application tree.

Create a feature module of your own:

import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}
Copy the code

Then import in app.module.ts:

import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class ApplicationModule {}
Copy the code

If you want to share modules, just export from exports:

exports: [CatsService]
Copy the code

If you want to import the same module anywhere, you can set it to global module:

@Global()
@Module({
  ...
})
export class CatsModule {}
Copy the code

Nest also supports dynamic modules that dynamically register and configure providers, using forRoot:

@Module({
 ...
})
export class DatabaseModule {
  static forRoot(entities = [], options?): DynamicModule {
    const providers = createDatabaseProviders(options, entities);
    return {
      module: DatabaseModule,
      providers: providers,
      exports: providers,
    };
  }
}
Copy the code

middleware

Middleware is a function called before the routing handler. Nest middleware is essentially equivalent to Express middleware.

Middleware has the following functions:

  • Make changes to the request and response objects.
  • End the request-response cycle.
  • Call the next middleware function in the stack.
  • If the current middleware function does not end the request-response cycle, it must be callednext()Pass control to the next middleware function. Otherwise, the request will be suspended.

Custom Nest middleware can be implemented in functions or in classes with @Injectable() decorators by simply implementing the NestMiddleware interface.

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log('Request...');
    next();
  }
}
Copy the code

However, the use of middleware needs to pay attention to the following points:

  • Middleware cannot be used in@Module()Listed in decorator
  • Must use module classconfigure()Method to set them
  • Modules containing middleware must be implementedNestModuleinterface
@Module({ ... }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .forRoutes('cats'); }}Copy the code

For multiple middleware, write:

consumer.apply(cors(), helmet(), logger).forRoutes(CatsController);
Copy the code

Global middleware can be registered directly on the APP:

const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(3000);
Copy the code

(5) Abnormal filter

Handles all exceptions thrown throughout the application. Nest provides a series of available exceptions inherited from the core HttpException:

  • BadRequestException
  • UnauthorizedException
  • NotFoundException
  • ForbiddenException
  • NotAcceptableException
  • RequestTimeoutException
  • ConflictException
  • GoneException
  • PayloadTooLargeException
  • UnsupportedMediaTypeException
  • UnprocessableException
  • InternalServerErrorException
  • NotImplementedException
  • BadGatewayException
  • ServiceUnavailableException
  • GatewayTimeoutException

However, we can also customize the exception filter by binding the required metadata to the exception filter using the @catch () decorator. If the @catch () parameter list is empty, every unhandled exception (regardless of the exception type) will be caught.

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, }); }}Copy the code

Binding filter:

@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}
Copy the code

You can also use globally scoped filters:

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(3000);
}
bootstrap();
Copy the code

(6) Pipelines

Class with @Injectable() decorator that transforms or validates data. Nest comes with eight out-of-the-box tubes, i.e

  • ValidationPipe
  • ParseIntPipe
  • ParseBoolPipe
  • ParseArrayPipe
  • ParseUUIDPipe
  • DefaultValuePipe
  • ParseEnumPipe
  • ParseFloatPipe

If you want to customize a pipe, you should only implement the PipeTransform interface:

import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common'; @Injectable() export class ValidationPipe implements PipeTransform { transform(value: any, metadata: ArgumentMetadata) { return value; }}Copy the code

Bind pipes using @usepipes () :

@Post()
@UsePipes(new JoiValidationPipe(createCatSchema))
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}
Copy the code

(7) Guards

Classes that use the @Injectable() decorator to determine whether a given request is handled by a routing handler based on certain conditions that occur at runtime (such as permissions, roles, access control lists, etc.).

Guards are executed after each middleware, but before any interceptors or pipes.

Guards must implement a canActivate() function. This function should return a Boolean value indicating whether the current request is allowed. It can return a response either synchronously or asynchronously:

  • If the returntrue, will handle the user call.
  • If the returnfalse,NestRequests currently being processed are ignored.

For example, implementing an authorization guard:

@Injectable() export class AuthGuard implements CanActivate { canActivate( context: ExecutionContext, ): boolean | Promise<boolean> | Observable<boolean> { const request = context.switchToHttp().getRequest(); return validateRequest(request); }}Copy the code

It’s also easy to use:

@Controller('cats')
@UseGuards(RolesGuard)
export class CatsController {}
Copy the code

You can also set the global guard:

const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new RolesGuard());
Copy the code

We can also add reflectors to the guard and get different metadata for different routes to make decisions.

SetMetadata using @setmetadata () :

@Post()
@SetMetadata('roles', ['admin'])
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}
Copy the code

Use reflector to get metadata for Settings:

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const roles = this.reflector.get<string[]>('roles', context.getHandler());
    if (!roles) {
      return true;
    }
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    return matchRoles(roles, user.roles);
  }
}
Copy the code

(8) Interceptors

Classes with @Injectable() decorator annotations can transform, extend, override functions, and bind additional logic before and after functions.

Each interceptor has the Intercept () method, which takes two arguments. The first is an ExecutionContext instance (the exact same object as the guard). The second argument is CallHandler returns an Observable.

@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`)),
      );
  }
}
Copy the code

Binding interceptors:

@UseInterceptors(LoggingInterceptor)
export class CatsController {}
Copy the code

Bind global interceptor:

const app = await NestFactory.create(ApplicationModule);
app.useGlobalInterceptors(new LoggingInterceptor());
Copy the code

(9) decoration

Nest is built on a decorator language feature that lets you customize your own.

For example, we can customize a parameter decorator:

import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const User = createParamDecorator((data: unknown, ctx: ExecutionContext) => {
  const request = ctx.switchToHttp().getRequest();
  return request.user;
});
Copy the code

Then use it:

@Get()
async findOne(@User() user: UserEntity) {
  console.log(user);
}
Copy the code

Nest can also aggregate multiple decorators. For example, suppose you want to aggregate all authentication-related decorators into a single decorator. This can be done by:

import { applyDecorators } from '@nestjs/common'; export function Auth(... roles: Role[]) { return applyDecorators( SetMetadata('roles', roles), UseGuards(AuthGuard, RolesGuard), ApiBearerAuth(), ApiUnauthorizedResponse({ description: 'Unauthorized"' }) ); }Copy the code

Use aggregate decorators:

@Get('users')
@Auth('admin')
findAllUsers() {}
Copy the code

Three, use,

$ npm i -g @nestjs/cli 
$ nest new nest-demo
Copy the code

Using nest’s CLI will create a simple new project directory with the following file organization in the SRC directory:

SRC ├ ─ ─ app. Controller. Spec. Ts ├ ─ ─ app. The controller. The ts ├ ─ ─ app. The module. The ts ├ ─ ─ app. Service. The ts └ ─ ─ the main, ts// Application entry file
Copy the code

Main. ts contains the following contents:

/* main.ts */
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();
Copy the code

You can see that the main use of NestFactory to create an application instance, listening on port 3000.