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 called
next()
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 class
configure()
Method to set them - Modules containing middleware must be implemented
NestModule
interface
@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 return
true
, will handle the user call. - If the return
false
,Nest
Requests 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.