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的参数上,import引入其它模块,controllers提供控制器,处理请求和响应。根模块引入其它模块,其它模块也提供了controller, 其它模块再引入其它模块,它们也提供了controller,通过import 构建起了整个应用,对应的controller分别处理各个模块的请求,职责清晰,因此模块是构建NestJs应用的基石。

  控制器是注解了@Controller的类,类的每一个方法再注解@Get,@Post等,该方法就处理get和post请求。项目启动,从AppModule开始,找到所有import的module,根据每一个module提供的controllers,NestJs会构建一个路由映射表,请求和Controller的方法一一对应。当客户端发来请求时,NestJs就能知道调用哪个方法。AppController

@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 { }

  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;
  }
}

  写一个真实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}]

 

posted @ 2024-08-30 18:38  SamWeb  阅读(33)  评论(0编辑  收藏  举报