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提供的controller,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,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' } } }
messages.servcie.ts调用repository的方法,由于repository是个类,所以在service 中,要先创建实例,当然是在构造函数中
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,表示这个Controller处理以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,建立了/messages到getMessages的映射,postman请求http://localhost:3000/messages, 返回了{ "message": "first NestJs demo" }。当controller的方法返回基本数据类型时,直接返回。当返回对象或数组时,会序列化为json。
以上开发方式和普通的Express开发,没有什么区别,只不过把普通对象转化成类。NestJs 有一个依赖注入的概念,不需要在构造函数中手动创建对象,而是声明需要什么类型的对象(依赖),程序运行时,NestJs自动注入这个对象。如果了解Java Spring框架的依赖注入,NestJs的依赖注入有点别扭。程序启动的时候,AppModule只能import其它module,所以其它module要告诉NestJs,它要提供哪些依赖,这就是@Module的provider属性。Provider的意思是 things that can be used as dependcies for other classes。假设provider是 MessagesService, NestJs就会找到MessagesService,需要注意的是MessagesService类需要被@Injectable()修饰,表明让NestJs来管理这个类。你告诉NestJs提供这些依赖,但你又不让NestJs来管理这些东西,那肯定不行。假设MessagesService又依赖MessagesRepo,MessagesRepo也要被Injectable()修饰,并且添加到provider中。controller 依赖service, 所以MessagesController 的构造函数修改如下
constructor(messageService: MessagesService) {// 让NestJs注入MessagesService 对象
this.messagesService = messageService;
}
service依赖repository,MessagesService 的构造函数修改如下
constructor(messageRepo: MessageRepo) { // 让NestJs注入MessageRepo对象
this.messageRepo = messageRepo;
}
MessagesModule的provider告诉NestJs 它要提供MessagesService的依赖和MessageRepo的依赖
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 {}
既然NestJs提供MessagesService和MessageRepo依赖。用@Injectable()修饰MessagesService和MessageRepo
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}]