NestJs 快速入门
npm i -g @nestjs/cli,nest new car-report 快速创建car-report 项目。src目录下面有main,controller,service,module文件。main.ts是项目的入口,它引入AppModule,创建服务,监听3000端口。AppModule是一个注解@Module()的类,也称为App模块。由于项目启动时引入AppModule,它也称为根模块。模块就是注解@Module()的类,有什么作用,体现在@Module的参数上,import引入其它模块,controllers提供控制器,处理请求和响应。根模块引入其它模块,其它模块也提供了controller, 其它模块再引入其它模块,它们也提供了controller,通过import 构建起了整个应用,对应的controller分别处理各个模块的请求,职责清晰,因此模块是构建NestJs应用的基石。
控制器是注解了@Controller的类,类的每一个方法再注解@Get,@Post等,该方法就处理get和post请求。定义路由,控制器处理传入的请求并向客户端返回响应。项目启动,从AppModule开始,找到所有import的module,根据每一个module提供的controllers,NestJs会构建一个路由映射表,请求和Controller的方法一一对应。当客户端发来请求时,NestJs就能知道调用哪个方法。AppController. NestJS 中的控制器负责接收传入的请求、处理它们并向客户端返回响应。它们通常带有 @Controller() 装饰器,并包含不同路由的处理程序方法。
@Controller() export class AppController { @Get() getHello(): string { return this.appService.getHello(); } }
@Controller()没有参数,@Get也没有参数,就相当于根路径。路由映射就是 / --> getHelllo()。当请求 / 时会调用getHello方法,方法也称为路由处理器。npm run start:dev 启动服务,postman get请求localhost:3000,返回hello world。controller调用了appService,这是一种设计模式,controller负责接受客户端请求,service负责处理业务逻辑,repository负责和数据库打交道,controller调用service,service调用repository。创建一个messages模块,新建messages目录,目录里面messages.controller.ts,messages.module.ts, messages.service.ts, messages.repository.ts。messages.repository.ts 假设从数据库中查数据,
export class MessageRepo { findOne() { return { message: 'first NestJs demo' } } }
servcie调用repository,但repository是个类,所以在service 中要先创建repository的一个实例,messages.service.ts
import { MessageRepo } from './messages.repository'; export class MessagesService { private messageRepo: MessageRepo; constructor() { this.messageRepo = new MessageRepo(); } getMessages() { return this.messageRepo.findOne(); } }
controller调用service,messages.controller.ts类的构造函数中,创建一个service的实例。
import { Controller, Get } from '@nestjs/common'; import { MessagesService } from './messages.service'; @Controller('messages') export class MessageController { messagesService: MessagesService constructor() { this.messagesService = new MessagesService() } @Get() getMessages() { return this.messagesService.getMessages() } }
@Controlloer有一个参数messages,表示它只处理以messages开头的请求, 由于@Get没有参数 ,/messages就会调用getMessages方法。messages.module.ts,注册controller,
import { Module } from '@nestjs/common'; import { MessageController } from './messages.controller'; @Module({ controllers: [MessageController] }) export class MessagesModule {}
AppModule 中,import MessagesModule,
import { Module } from '@nestjs/common'; import { MessagesModule } from './messages/messages.module'; @Module({ imports: [MessagesModule], }) export class AppModule { }
动态路由和参数
@Get('users/:userId') fetchUserDetails(@Param('userId') userId: string): string { return `Details for user with ID: ${userId}`; }
@Param() 装饰器抓取路由的动态部分,使其在您的处理程序方法中可用。
@Controller('users') export class UsersController { @Get(':id/:sex/:minAge') filterUsers( @Param('id') id: string, @Param('sex') sex: string, @Param('minAge') minAge: number, @Query('salary') salary?: number ) { return `Fetching users with ID: ${id}, Sex: ${sex}, Minimum Age: ${minAge}, and Salary: ${salary || 'Not Specified'}`; } }
添加请求
@Get('annotate') annotateRequest(@Req() request: Request) { // Adding custom metadata request['customMetadata'] = { timestamp: new Date(), userAgent: request.headers['user-agent'], route: request.route.path }; // Further processing... }
当处理复杂对象的时候,使用DTO. They define the shape and expectations of the data you’ll be working with, ensuring type safety and validation.
import { IsString, IsNotEmpty, IsNumber, ValidateNested } from 'class-validator'; class AddressDTO { @IsString() @IsNotEmpty() street: string; @IsNumber() houseNumber: number; } export class UserDTO { @IsString() @IsNotEmpty() name: string; @ValidateNested() address: AddressDTO; }
可选传参:
import { IsString, IsNotEmpty, IsOptional } from 'class-validator'; export class UpdateUserDTO { @IsString() @IsNotEmpty({ groups: ['create'] }) name: string; @IsString() @IsOptional() bio?: string; }
验证完数据,可能需要转换数据,
import { Transform } from 'class-transformer'; export class UserDTO { @IsString() name: string; @Transform(value => value.toUpperCase()) favoriteColor: string; }
@Patch(':id')
updateUser(
@Body(new ValidationPipe()) updateUserDto:
CreateUserDto,
@Param('id', ParseIntPipe) id: string,
) {
return this.usersService.updateUser({
...updateUserDto,
id
});
}
npm run start:dev启动服务,AppModule import了MessagesModule,找到了MessagesModule注册的controller,创建了一个controller实例,同时建立了/messages到getMessages的映射,postman请求http://localhost:3000/messages, 返回了{ "message": "first NestJs demo" }。当controller的方法返回基本数据类型时,直接返回。当返回对象或数组时,会序列化为json。NestJs有一个依赖注入的概念,不需要在构造函数中手动创建实例,而是使用参数声明需要什么实例(依赖),程序运行时,让NestJs自动注入这个实例。controller依赖service, 所以MessagesController 的构造函数修改如下
constructor(messageService: MessagesService) {// 让NestJs注入MessagesService的一个实例 this.messagesService = messageService; }
service依赖repository,MessagesService 的构造函数修改如下
constructor(messageRepo: MessageRepo) { // 让NestJs注入MessageRepo的一个实例 this.messageRepo = messageRepo; }
参数声明了依赖,怎么让NestJs提供依赖?NestJs怎么知道提供哪个依赖?NestJs的依赖注入,需要你手动告诉它,提供哪些依赖,这就是@Module的provider,provider是对NestJs说的,NestJs是一个provider。
import { Module } from '@nestjs/common'; import { MessageController } from './messages.controller'; import { MessagesService } from './messages.service'; import { MessageRepo } from './messages.repository'; @Module({ controllers: [MessageController], providers: [MessagesService, MessageRepo] }) export class MessagesModule {}
providers: [MessagesService, MessageRepo]表示,当NestJs碰到MessagesService,就注入一个MessagesService的实例,当碰到MessageRepo时,就注入MessageRepo的一个实例。当创建Contoller的实例时,就会碰到MessageService,就会注入一个MessagesService的实例。NestJs需要创建MessagesService和MessageRepo的实例,所以这两个类要用@Injectable()修饰,表示是可以被注入的。
import { Injectable } from '@nestjs/common'; @Injectable() export class MessagesService {} @Injectable() export class MessageRepo {}
NestJs 启动后,创建一个依赖注入的容器或注入器(Nest DI Container/Injector),它是一个对象,包含class列表和他们的依赖。它会查找项目中的provider,然后把它们注册到容器中。然后容器开始分析类的依赖(看构造函数的参数),然后创建一些内部记录来表示依赖,比如,service类依赖repositroy, repositroy类它没有依赖。然后在某些时候, 告诉DI容器创建controller的实例,DI就会分析contoller的依赖,它依赖Service,service再依赖resposotry。DI先创建reposiory的实例,然后再创建service的实例, 最后创建controller的实例,并返回。DI第一步是分析类的依赖,第二步是创建contoller实例并返回。controller实例是自动创建的
AppModule怎么使用MessagesModule中的service 呢?service属于自己的module,当一个service 要被其他module 使用时,它要被export 出去。MessagesModule export 出去MessagesService
@Module({ controllers: [MessageController], providers: [MessagesService, MessageRepo], exports: [MessagesService] }) export class MessagesModule {}
一个模块要使用另一个模块的service,就要把另一个模块引入,AppModule要import MessagesModule, 然后在使用service的地方依赖注入到构造函数中,AppModule已经import 过了MessagesModule,假设AppService使用MessagesService,AppService修改如下
import { Injectable } from '@nestjs/common'; import { MessagesService } from './messages/messages.service'; @Injectable() export class AppService { constructor(private messageService: MessagesService) {} getHello(): string { return 'Hello World!' + this.messageService.getMessages().message; } }
Essentially, providers can be anything
that can return a value, such as a service, factory, value, or class.
写一个真实user的登录注册。首先要有数据库,用TypeORM操作MySQL,npm install --save @nestjs/typeorm typeorm mysql2 安装依赖。TypeORM有实体(Entity)的概念,对应MySql的一张表,它是一个类,代表表名,属性代表列。创建users目录,user目录下创建users.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; @Entity() export class User { @PrimaryGeneratedColumn() id: number; @Column() admin: boolean; @Column() email: string; @Column({default: true}) password: string; }
连接数据库,在AppModule
import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './users/users.entity'; @Module({ imports: [TypeOrmModule.forRoot({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: '123', database: 'test', entities: [User], synchronize: true, })] }) export class AppModule { }
TypeORM模块的import方式不太一样,它是调用forRoot方法返回一个模块,这种模块称为动态模块,相对应的,MessagesModule 称为静态模块。静态模块功能是固定的,由@Module定义,import的时候,直接import 模块名,不用调用方法。不管是静态模块还是动态模块,模块一旦创建,它是单例的,存在某个地方,但又不全局可用(every module has its own set of imported modules in its scope)。Typeorm模块在AppMoule 中创建,它就已经存在,连接到数据库了,users模块中只使用user功能,所以在users中,Typeorm.forfeature('user'),让user功能在users模块中使用。当使用一个动态module 的时候,要么配置module的行为,要么引入某个feature。users目录下,创建users.module.ts
import { Module } from '@nestjs/common'; import { UsersController } from './users.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from './users.entity'; import { UsersService } from './users.service'; @Module({ imports: [TypeOrmModule.forFeature([User])], controllers: [UsersController], providers: [UsersService] }) export class UsersModule { }
创建users.controller.ts
import { Controller, Get, Post } from '@nestjs/common'; import { UsersService } from './users.service'; @Controller('users') export class UsersController { constructor(private usersService: UsersService) { } @Post('/signup') async createUser() {} @Post('/signin') async singin() {} @Get('/:id') async getUser() {} }
创建users.service.ts,本来是要创建users.repository.ts的,但有了实体,TypeORM会自动创建对应的repostiory对象来操作表,不用手动创建Repository类了,只需要在service中注入创建好的repostiory对象,users.service.ts 如下
import { Injectable } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; import { User } from "./users.entity"; @Injectable() export class UsersService { constructor( @InjectRepository(User) private usersRepository: Repository<User>, ) {} }
Repositoy类型使用泛型Repository<User>。创建user就要接收客户端传递过来的参数,NestJs有@Req装饰器,只要函数参数被@Req修饰,参数就是request请求对象,函数中可以获取到request.body, request.params, request.query等对象,由于这些对象非常常见,NestJs为每一个对象都创建装饰器,@Body,@Params, @Query。@Body修饰的参数直接就是客户端传递过来的数据。那参数是什么类型?客户端传递过来emai和password, 那就创建一个类,有email 和password属性,参数就是这个类类型。创建create-user.ts
export class CreateUser { email:string; password: string }
users controller中的createUser 函数
@Post('/signup') async createUser(@Body() body: CreateUser) {}
CreateUser类也称为DTO(Data Transfer object)类,用于接受客户端传递过来的数据。客户端传递过来email和password,它是以字符串(JSON.stringfy())的形式传递过来的,服务端需要JSON.parse进行解析,然后创建CreateUser一个实例对象,把解析出来的数据赋值给对象,然后再把对象赋值给body,body是CreateUser的一个实例对象。能接受数据了,还要对数据进行验证。NestJs有一个pipe 的概念,数据到达路由处理函数之前要经过一系列过程,pipe就是其中之一,可用于验证。NestJ提供ValidationPipe和两个npm包。Class-transformer包把plain object转化成一个类的实例。Class-validation 包使用注解验证,然后把验证结果给到validation pipe。npm install class-transformer class-validator, createUser 函数@Body(ValidationPipe),CreateUser 类添加验证
import { IsString, IsEmail } from 'class-validator'; export class CreateUser { @IsEmail() email:string; @IsString() password: string }
validation 还有一个group的概念,按照条件进行验证,给ValidationPipe进行传参group: ['create],然后在CreateUser中添加验证条件的时候,也添加group: ['create]。如果对每个客户端数据都进行验证,在每一个路由处理函数都添加ValidationPipe,比较麻烦,可以开启全局验证。useGlobalPipes(new ValidationPipe()), useGlobalPipes对所有的请求都应用它包含的pipe。ValidationPipe 验证每一个请求,如果DTO类没有添加验证规则,也不会对请求进行验证。main.ts
import { ValidationPipe } from "@nestjs/common"; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe({ whitelist: true })) await app.listen(3000); } bootstrap();
验证的过程如下
有个问题,validation pipe是怎么知道使用哪个验证的规则的来验证哪一个路由的?尤其是typescript 编译成js,类型擦除后?ts 配置emitDecoratorMetadata, 把类型信息添加到js中, 编译后在js代码,dist目录,users下面的users.controller.js
exports.UsersController = UsersController; __decorate([ (0, common_1.Post)('/signup'), __param(0, (0, common_1.Body)(common_1.ValidationPipe)), __metadata("design:type", Function), __metadata("design:paramtypes", [create_user_1.CreateUser]), __metadata("design:returntype", Promise) ], UsersController.prototype, "createUser", null);
现在把接受到的参数存储到数据库,调用userService中的create 方法,
async createUser(@Body(ValidationPipe) body: CreateUser) { await this.usersService.create(body.email, body.password) return {message: 'create successfully'} }
在UsersService类下面添加create方法,Typeorm生成的repository对象有create,save,insert,update,delete,find等方法。向数据库中插入数据,有两种实现方式,create之后调用save,
async create(email: string, password: string) { const user = this.usersRepository.create({ email, password, admin: true }); await this.usersRepository.save(user); }
和直接调用insert。
async create(email: string, password: string) {await this.usersRepository.insert({ email, password, admin: false })
}
在AppModule中import UserModule, npm run start:dev启动服务,
同样的,update也有两种方式,先findOne,再调用save方式,或直接调用update 方法。delete也有两种方式,先findOne,再调用remove方法,或直接调用delete方法。为什么会有save和remove 方法呢?因为在Entity 中可以定义一些hooks,@AfterInsert, @AfterUpdate, 只有调用这两个方法的时候,它们才会执行,直接调用insert,update,delete不会执行,但save 方法,性能可能不高,因为,当实体不存在时,它执行insert操作,当实体存在时,它执行update操作,每天都要执行两次query查询,先find,再insert或update。
登录singin,都会返回token,以后每一个请求都带有token,就知道谁在请求。npm install --save @nestjs/jwt。在UserModule中,
import { JwtModule } from '@nestjs/jwt'; export const secret = 'addfsdsfdf' imports: [TypeOrmModule.forFeature([User]), JwtModule.register({ global: true, secret: secret, signOptions: { expiresIn: '1h' }, })]
再UserService中注入JwtService,实现signIn方法
import { JwtService } from '@nestjs/jwt'; import { Injectable, UnauthorizedException } from "@nestjs/common"; constructor( @InjectRepository(User) private usersRepository: Repository<User>, private jwtService: JwtService ) { } async signIn( email: string, password: string) { const user = await this.usersRepository.findOne({where: { email }}); if (user?.password !== password) { throw new UnauthorizedException(); } const payload = { sub: user.id }; return await this.jwtService.signAsync(payload) }
在UserController 中
@Post('/signin') async singin(@Body(ValidationPipe) body: CreateUser) { const token = await this.usersService.signIn(body.email, body.password) return { access_token: token }; }
getUser方法,只有用户登录,才能调用访问,有些路由是要保护起来的,如果没有登录,就不能访问,这要用到guard,有一个canActivate(), 返回true or false,true表示允许访问,false表示不允许访问。守卫决定请求是否应该继续进行到其路由处理程序。用户登录就是验证token,创建AuthGuard.ts
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { secret } from './users.module'; import { Request } from 'express'; @Injectable() export class AuthGuard implements CanActivate { constructor(private jwtService: JwtService) { } async canActivate(context: ExecutionContext): Promise<boolean> { const request = context.switchToHttp().getRequest(); const token = this.extractTokenFromHeader(request); if (!token) { throw new UnauthorizedException(); } try { await this.jwtService.verifyAsync(token,{secret}); } catch { throw new UnauthorizedException(); } return true; } private extractTokenFromHeader(request: Request): string | undefined { const [type, token] = request.headers.authorization?.split(' ') ?? []; return type === 'Bearer' ? token : undefined; } }
getUser方法,那就使用UseGuard进行保护
@Get('/:id') @UseGuards(AuthGuard) async getUser() { }
实现getUser,获取id参数,用@Param装饰器,
async getUser(@Param('id') id: string) { return await this.usersService.findOne(id); }
userService实现findOne,
findOne(id: number) { if (!id) return null; return this.usersRepository.findOneBy({ id }); }
但这时有一个问题,controller中调用findOne的id是string类型,但service中,id接受的是number类型,这是可以用pipe,@param('id', ParseIntPipe) 把id转换成int 类型。
async getUser(@Param('id', ParseIntPipe) id: number) { return await this.usersService.findOne(id); }
pipe通常做两件事情,一个是类型转换,一个是验证用户的输入。在以上的方法中,抛出了异常,比如throw new UnauthorizedException(),NestJs有一层Exception filter,当应用程序中抛出了异常,而没有被捕获时,它会把异常转换成合适response,比如 throw NotFoundExeption 时, nextJs会返回404,not found。对exception 进行过滤,返回合适的响应。
但返回值中有password,应该要去掉才对,这样用到拦截器。拦截器实现一个NestInterface,intercept 里面正常写,拦截请求,return next.handle() 对拦截响应,对路由处理器的返回值进行处理。它返回的是rxjs的observer,有map等操作,map中的data 就是路由处理器返回的data。 返回值去掉password,创建serialInteceptor.ts
import { CallHandler, ExecutionContext, NestInterceptor, UseInterceptors } from "@nestjs/common"; import { Observable, map } from "rxjs"; export class SerializeIntercepter implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> | Promise<Observable<any>> { return next.handle().pipe( map(data => { Reflect.deleteProperty(data, 'password'); // true return data; }) ) } }
getUser用userInterceptor.
@Get('/:id') @UseGuards(AuthGuard) @UseInterceptors(SerializeIntercepter) async getUser(@Param('id', ParseIntPipe) id: number) { return await this.usersService.findOne(id); }
可以把拦截器包起来,形成一个装饰器,serialInteceptor.ts
export function Serialize() { return UseInterceptors(SerializeIntercepter); }
getUser 去掉@UseInterceptors(SerializeIntercepter), 直接使用@Serialize()。再创建一个report 模块,一辆汽车的报告,用户创建它,admin 用户批准它。nest cli 提供了一些命令来创建module,controller和service, nest g module reports,nest g controller reports, nest g service reports,手动在reports目录建reports.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm'; @Entity() export class Report { @PrimaryGeneratedColumn() id: number; @Column({default: false}) approved: boolean; @Column() price: number; @Column() year: number @Column() mileage: number }
然后在AppModule 中,Typeorm的配置项entities中,添加Report
entities: [User, Report],
用户创建report, createReport 中要知道用户的信息,admin批准report,那还要判断登录的用户是不是admin,如要不是,批准的api就不能被调用,需要创建AdminGuard。从客户端请求中,只能得到userId,所以其它信息还要从数据库里面取。这里要用到中间件,这是由中间件,guard,拦截器的执行顺序决定的。
在中间件中,调用userService,获取到用户信息,然后把信息添加到request对象上,后面执行的guard,拦截器,路由处理器都能获取到request对象上在user信息。在src目录下,创建current-user.middlewire.ts
import { Injectable, NestMiddleware } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express'; import { UsersService } from './users/users.service'; import { JwtService } from '@nestjs/jwt'; import { secret } from './users/users.module'; @Injectable() export class CurrentUserMiddleware implements NestMiddleware { constructor(private user: UsersService, private jwtService: JwtService) { } async use(req: Request, res: Response, next: NextFunction) { const [, token] = req.headers.authorization?.split(' ') ?? []; if (token) { try { const result = await this.jwtService.verify(token, { secret }); const user = await this.user.findOne(result.sub); // @ts-ignore req.currentUser = user; } catch (error) { console.log(error) } } next(); } }
中间件的使用比较特别,使用中间件的module要实现NestModule, 在configure中配置,比如在AppModule中配置中间件
import { CurrentUserMiddleware } from './current-user.middlewire'; import { MiddlewareConsumer, NestModule } from '@nestjs/common'; export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(CurrentUserMiddleware) .forRoutes('*'); // 或for('/reports') } }
由于中间件在AppModule中引入的,使用了UserService,UserModule还要exports 出去UserService。
@Module({ // .... providers: [UsersService], exports: [UsersService] }) export class UsersModule { }
现在createReport可以获取到user信息了,但怎么在report中保存user信息呢?这涉及到了关系,report和user有1对多的关系,
在user实体中, 添加属性
@Entity() export class User { // ... @OneToMany(() => Report, (report) => report.user ) reports: Report[] // 数组表示多个report }
在report 实体添加属性
@Entity() export class Report { // ... @ManyToOne(() => User, (user) => user.reports) user: User }
oneToMany或ManyToOne为什么第一个参数是函数。这是因为,User Entity中使用Report Entity, Report Entity 中又使用User Entity,循环依赖了,不能直接使用,所以要用函数包起来,以后执行,而不是加载文件的时候执行。第二个函数参数的意思是关联的实体,返回值是定义的实体, 通过关联的实体report怎么找回到定义report的实体(User),report entity 有一个user字段,就是定义reports的实体(User实体中有reports属性)。Report实体有一个user字段,存储report时,给report的user属性赋值一个user实体,当真正存储到数据库时,会从user实体中取出id,存储到数据库。ReporstController
import { Body, Controller, Post, Req, UseGuards } from '@nestjs/common'; import { AuthGuard } from '../users/AuthGuard'; import { ReportsService } from './reports.service'; @Controller('reports') export class ReportsController { constructor(private readonly reportsService: ReportsService) { } @Post() @UseGuards(AuthGuard) async createReport(@Body() body: any, @Req() req: any) { //body 的类型本来是一个DTO类型,简单起见,写了any const userReturn = await this.reportsService.create(body, req.currentUser) return userReturn } }
ReportsService
import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { User } from 'src/users/users.entity'; import { Repository } from 'typeorm'; import { Report } from './reports.entity' @Injectable() export class ReportsService { constructor( @InjectRepository(Report) private reportsRepository: Repository<Report>, ) { } create(reportDto: any, user: User) { const report = this.reportsRepository.create(reportDto); // @ts-ignore report.user = user; return this.reportsRepository.save(report) } }
由于在Service中注入了Report,所以在ReportsModule中 imports: [TypeOrmModule.forFeature([Report])],
repository的save方法把整个关联的user 实体都返回了。还有就是controller 接收了@req参数,能不能也像@Body一样,直接获取user?这要自定义一个参数装饰器createParaDecorator. 在src目录下,currentUser.ts
import {createParamDecorator, ExecutionContext} from '@nestjs/common' export const CurrentUser = createParamDecorator( (data: never, context: ExecutionContext) => { const request = context.switchToHttp().getRequest(); return request.currentUser } )
controller
import { CurrentUser } from '../currentUser'; import { User } from '../users/users.entity'; @Post() @UseGuards(AuthGuard) async createReport(@Body() body: any, @CurrentUser() user: User) { const userReturn = await this.reportsService.create(body, user) // @ts-ignore const newUser = { ...userReturn, userId: userReturn.user.id }; // @ts-ignore delete newUser.user; return newUser }
现在写一个approve, 就是把report的approve属性,改成true. 它需要admin权限,写一个AdminGuard。在report目录下,admin.guard.ts
import { CanActivate, ExecutionContext} from '@nestjs/common' export class AdminGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest(); if(!request.currentUser) { return false } if(request.currentUser.admin) { return true } else { return false } } }
ReportsController 添加一个patch 路由
@Patch('/:id') @UseGuards(AdminGuard) async approveReport(@Param('id') id: number, @Body() body: { approved: boolean }) { return await this.reportsService.changeApproval(id, body.approved); }
ReportsService 添加 changeApproval 方法
async changeApproval(id: number, approved: boolean) { const report = await this.reportsRepository.findOne({ where: { id } }) if (!report) { throw new NotFoundException('not found') } report.approved = approved; return this.reportsRepository.save(report) }
当查询条件比较复杂的时候,就不能简单地用findOne和find了,就要使用createQueryBuilder,比如查询price是5000, mileage 也是5000等。在Controller 中,
@Get() async getOneReport() { return this.reportsService.getReport(); }
在Service 中
async getReport() { return await this.reportsRepository.createQueryBuilder('report') .where('report.price= :price', {price: 5000}) .andWhere("report.mileage = :mileage", { mileage: 5000 }) .getOne() }
当fetch reprot时,不会自动fetch user。同样的,当fetch user的时候,也不会自动fetch report。
config 配置环境,npm i @nestjs/config, @nestjs/config内部使用dotenv。Dotenv的目的是, 把不同的环境变量(命令行定义的环境变量, .env 文件定义的环境变量)收集起来, 形成一个对象(process.env),返回给你。 如果各个方法定义的环境变量有冲突,命令行中定义的优先级高。
@nestjs/config 提供了依赖注入的功能。 每一个环境不同的.env 文件,然后,configroot.forRoot() 加载不同的配置文件(命令行配置env环境变量),
provider 定制化:当provider提供一个类名时,是标准引入,提供一个对象时,是定制引入。对象的一个key是provide,表示提供什么,真实的作用就是一个token标示符。另外一个key是useValue或useClass或useFactory, 当NestJs遇到provider指定的token后,就会用useValue指定的value,或useClass指定的类的实例对象,或调用useFactory,来注到token中。标准引入其实就是provide和useClass都是类名。providers: [{provide: ReportsService, useClass:ReportsService}]
异常过滤器让您可以控制应用程序抛出的异常。通过扩展 ExceptionFilter 基类,您可以定义发生错误时的自定义响应。Nest’s hierarchical injector
NestJS not only leverages DI but also elevates it with its hierarchical injector system. This system
works in layers, ensuring that each module and its components get their dependencies from the closest
injector, be it module-specific or global.
NestJS provides a mechanism called forwardRef() that allows you to
reference classes before they are defined, hence resolving the cyclic dependency issue.
Once defined
as global, a module doesn’t need to be imported into other modules; its providers and controllers
become instantly accessible everywhere:
Middleware is great for tasks that do not involve decision-making concerning the continuation of the request-response cycle based on business logic. 中间件非常适合那些不涉及基于业务逻辑的请求-响应周期延续的决策的任务。
Route guards are a fundamental part of the NestJS framework, crucial for ensuring that a certain set of
logic is fulfilled before a route handler is executed. These guards are particularly vital for implementing
authorization and authentication logic in an application.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)