nestjs
介绍
Nest(NestJS)是一个框架,用于构建高效,可扩展的 Node.js 服务器端应用程序。它使用渐进式 JavaScript,使用 TypeScript 构建并完全支持 TypeScript(但仍使开发人员能够使用纯 JavaScript 编码),并结合了 OOP(面向对象编程),FP(函数式编程)和 FRP(函数式响应式编程)的元素。
在引擎盖下,Nest 使用强大的 HTTP 服务器框架,如 Express(默认),并且可以选择配置为使用 Fastify!
Nest 提供了高于这些常见 Node.js 框架(Express/Fastify)的抽象级别,但也直接向开发人员公开其 API。这使开发人员可以自由使用可用于底层平台的大量第三方模块。
安装
node 版本>12
npm i -g @nestjs/cli
nest new project-name
npm i -g @nestjs/cli
nest new project-name
nestjs/cli 命令
nest g resource [name] // crud模块生成器
nest g resource [name] // crud模块生成器
nest 数据传输方式
对于前端来说,后端主要是提供 http 接口来传输数据,而这种数据传输的方式主要有 5 种:
- url param
- query
- form-urlencoded
- form-data
- json
url param
http://guang.zxg/person/1111 // 参数为111
http://guang.zxg/person/1111 // 参数为111
nest 解析
@Controller('api/person')
export class PersonController {
@Get(':id')
urlParam(@Param('id') id: string) {
return `received: id=${id}`
}
}
@Controller('api/person')
export class PersonController {
@Get(':id')
urlParam(@Param('id') id: string) {
return `received: id=${id}`
}
}
@Controller('api/person') 的路由和 @Get(':id') 的路由会拼到一起,也就是只有 /api/person/xxx 的 get 请求才会走到这个方法。
前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
</head>
<body>
<script>
async function urlParam() {
const res = await axios.get('/api/person/1')
console.log(res)
}
urlParam()
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
</head>
<body>
<script>
async function urlParam() {
const res = await axios.get('/api/person/1')
console.log(res)
}
urlParam()
</script>
</body>
</html>
query
通过 url 中 ?后面的用 & 分隔的字符串传递数据。比如:
http://guang.zxg/person?name=guang&age=20
http://guang.zxg/person?name=guang&age=20
这里的 name 和 age 就是 query 传递的数据。
nest 解析
@Controller('api/person')
export class PersonController {
@Get('find')
query(@Query('name') name: string, @Query('age') age: number) {
return `received: name=${name},age=${age}`
}
}
@Controller('api/person')
export class PersonController {
@Get('find')
query(@Query('name') name: string, @Query('age') age: number) {
return `received: name=${name},age=${age}`
}
}
前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
</head>
<body>
<script>
async function query() {
const res = await axios.get('/api/person/find', {
params: {
name: 'ayu',
age: 20,
},
})
console.log(res)
}
query()
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
</head>
<body>
<script>
async function query() {
const res = await axios.get('/api/person/find', {
params: {
name: 'ayu',
age: 20,
},
})
console.log(res)
}
query()
</script>
</body>
</html>
form urlencoded
form urlencoded 是通过 body 传输数据,其实是把 query 字符串放在了 body 里,所以需要做 url encode:
前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
<script src="https://unpkg.com/qs@6.10.2/dist/qs.js"></script>
</head>
<body>
<script>
async function formUrlEncoded() {
const res = await axios.post(
'/api/person',
Qs.stringify({
name: '光',
age: 20,
}),
{
headers: { 'content-type': 'application/x-www-form-urlencoded' },
}
)
console.log(res)
}
formUrlEncoded()
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
<script src="https://unpkg.com/qs@6.10.2/dist/qs.js"></script>
</head>
<body>
<script>
async function formUrlEncoded() {
const res = await axios.post(
'/api/person',
Qs.stringify({
name: '光',
age: 20,
}),
{
headers: { 'content-type': 'application/x-www-form-urlencoded' },
}
)
console.log(res)
}
formUrlEncoded()
</script>
</body>
</html>
nest 解析
@Controller('api/person')
export class PersonController {
@Post()
body(@Body() createPersonDto) {
return `received: ${JSON.stringify(createPersonDto)}`
}
}
@Controller('api/person')
export class PersonController {
@Post()
body(@Body() createPersonDto) {
return `received: ${JSON.stringify(createPersonDto)}`
}
}
json
nest 解析
@Controller('api/person')
export class PersonController {
@Post()
body(@Body() createPersonDto) {
return `received: ${JSON.stringify(createPersonDto)}`
}
}
@Controller('api/person')
export class PersonController {
@Post()
body(@Body() createPersonDto) {
return `received: ${JSON.stringify(createPersonDto)}`
}
}
前端代码使用 axios 发送 post 请求,默认传输 json 就会指定 content type 为 application/json,不需要手动指定:
前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
</head>
<body>
<script>
async function json() {
const res = await axios.post('/api/person', {
name: 'ayu',
age: 20,
})
console.log(res)
}
json()
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
</head>
<body>
<script>
async function json() {
const res = await axios.post('/api/person', {
name: 'ayu',
age: 20,
})
console.log(res)
}
json()
</script>
</body>
</html>
总结 我们用 axios 发送请求,使用 Nest 起后端服务,实现了 4 种 http/https 的数据传输方式:
其中前两种是 url 中的:
url param: url 中的参数,Nest 中使用 @Param 来取 query:url 中 ? 后的字符串,Nest 中使用 @Query 来取 后三种是 body 中的:
form urlencoded: 类似 query 字符串,只不过是放在 body 中。Nest 中使用 @Body 来取,axios 中需要指定 content type 为 application/x-www-form-urlencoded,并且对数据用 qs 或者 query-string 库做 url encode json: json 格式的数据。Nest 中使用 @Body 来取,axios 中不需要单独指定 content type,axios 内部会处理。
provider
Nest 实现了 IOC 容器,会从入口模块开始扫描,分析 Module 之间的引用关系,对象之间的依赖关系,自动把 provider 注入到目标对象。
provider 一般都是用 @Injectable 修饰的 class:
import { Injectable } from '@nestjs/common'
@Injectable()
export class PersonService {}
import { Injectable } from '@nestjs/common'
@Injectable()
export class PersonService {}
在 module 里面的 provide 声明
// app.modules.ts
import { Module } from '@nestjs/common'
import { AppController } from './app.controller'
import { AppService } from './app.service'
import { PersonModule } from './person/person.module'
@Module({
imports: [PersonModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
// app.modules.ts
import { Module } from '@nestjs/common'
import { AppController } from './app.controller'
import { AppService } from './app.service'
import { PersonModule } from './person/person.module'
@Module({
imports: [PersonModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
此外 provide 还可以自定义名字以及自定义值
@Module({
controllers: [UserController],
providers: [{
provide: "Xiaoman", // inject参数
useClass: UserService // 值
}, {
provide: "JD",
useValue: ['TB', 'PDD', 'JD'] // 自定义值
}]
@Module({
controllers: [UserController],
providers: [{
provide: "Xiaoman", // inject参数
useClass: UserService // 值
}, {
provide: "JD",
useValue: ['TB', 'PDD', 'JD'] // 自定义值
}]
使用 provide 内注入的值
// User.controller.ts
@Controller()
export class AppController {
constructor(
private readonly appService: AppService
@Inject('Xiaoman') private readonly userService: UserService
@Inject('Xiaoman') private readonly JD:string[]
) {}
}
// User.controller.ts
@Controller()
export class AppController {
constructor(
private readonly appService: AppService
@Inject('Xiaoman') private readonly userService: UserService
@Inject('Xiaoman') private readonly JD:string[]
) {}
}
在此使用@inject 修饰器,会自动帮我们把 provide 的值设为 controller 的实例属性/方法,因此我们也可以不设置 constructor,直接使用修饰器即可,对上面代码做简单修改
// User.controller.ts
@Controller()
export class AppController {
@Inject(AppService) private readonly appService: AppService
@Inject('Xiaoman') private readonly userService: UserService
@Inject('Xiaoman') private readonly JD: string[]
}
// 这样也是完全相同的,可以正常使用注入的方法
// User.controller.ts
@Controller()
export class AppController {
@Inject(AppService) private readonly appService: AppService
@Inject('Xiaoman') private readonly userService: UserService
@Inject('Xiaoman') private readonly JD: string[]
}
// 这样也是完全相同的,可以正常使用注入的方法
总结 一般情况下,provider 是通过 @Injectable 声明,然后在 @Module 的 providers 数组里注册的 class。
默认的 token 就是 class,这样不用使用 @Inject 来指定注入的 token。
但也可以用字符串类型的 token,不过注入的时候要用 @Inject 单独指定。
除了可以用 useClass 指定注入的 class,还可以用 useValue 直接指定注入的对象。
如果想动态生成对象,可以使用 useFactory,它的参数也注入 IOC 容器中的对象,然后动态返回 provider 的对象。
如果想起别名,可以用 useExisting 给已有的 token,指定一个新 token。
灵活运用这些 provider 类型,就可以利用 Nest 的 IOC 容器中注入任何对象。
Module
每个 Nest 应用程序至少有一个模块,即根模块。根模块是 Nest 开始安排应用程序树的地方。事实上,根模块可能是应用程序中唯一的模块,特别是当应用程序很小时,但是对于大型程序来说这是没有意义的。在大多数情况下,您将拥有多个模块,每个模块都有一组紧密相关的功能
1.基本模块
@Module({
imports: [PersonModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
@Module({
imports: [PersonModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
可以看到一个模块基本由 imports controllers 以及 providers 组成
2.共享模板
假如 app 模块想使用他子集下面的 user 模块的方法
第一步将 userService 暴露出去
@Module({
controllers: [UserController],
providers: [UserService],
exports: [UserService],
})
export class AppModule {}
@Module({
controllers: [UserController],
providers: [UserService],
exports: [UserService],
})
export class AppModule {}
由于 user 模块在创建的时候已经注入到 app 模块了,因此可以直接在 app 模块中使用 user 模块的方法, 只需要使用 inject 声明引用便可使用
@Inject(PersonService)
private readonly personService: PersonService;
//这个时候personServie已经成为app.controller类中的实例方法了
@Inject(PersonService)
private readonly personService: PersonService;
//这个时候personServie已经成为app.controller类中的实例方法了
那如果是平级的 user 模块想要使用 person 模块呢?
由于 2 则并没有任何关联,因此需要显示将 person 模块注入进 user 模块
// User.modules.ts
import { PersonService } from 'src/person/person.service';
@Module({
controllers: [UserController],
providers: [UserService, PersonService],
})
// User.modules.ts
import { PersonService } from 'src/person/person.service';
@Module({
controllers: [UserController],
providers: [UserService, PersonService],
})
3.全局模块
假如想让一个模块变为全局模块,那只需在模块前添加 global 装饰器即可
@global
@module({
controller:[...]
providers:[...]
exports:[...] // 不能忘记哦还是需要暴露出去
})
@global
@module({
controller:[...]
providers:[...]
exports:[...] // 不能忘记哦还是需要暴露出去
})
4.动态模块
动态模块主要就是为了给模块传递参数 可以给该模块添加一个静态方法 用来接受参数
@Global()
@Module({})
export class ConfigModule {
static forRoot(options: Options): DynamicModule {
return {
module: ConfigModule,
providers: [
{
provide: 'Config',
useValue: { baseApi: '/api' + options.path },
},
],
exports: [
{
provide: 'Config',
useValue: { baseApi: '/api' + options.path },
},
],
}
}
}
@Global()
@Module({})
export class ConfigModule {
static forRoot(options: Options): DynamicModule {
return {
module: ConfigModule,
providers: [
{
provide: 'Config',
useValue: { baseApi: '/api' + options.path },
},
],
exports: [
{
provide: 'Config',
useValue: { baseApi: '/api' + options.path },
},
],
}
}
}
然后使用 import 注入传参即可
中间件
中间件是在路由处理程序之前调用的函数。中间件函数可以访问请求和响应对象,以及应用程序请求-响应周期中的 next() 中间件函数。下一个中间件函数通常由名为 的 next 变量表示。
创建一个中间件
import { Injectable, NestMiddleware } from '@nestjs/common'
import { Request, Response, NextFunction } from 'express'
@Injectable()
export class Logger implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log(req)
next()
}
}
import { Injectable, NestMiddleware } from '@nestjs/common'
import { Request, Response, NextFunction } from 'express'
@Injectable()
export class Logger implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log(req)
next()
}
}
注入中间件
//Person.module.ts
...
interface forRoutes {
path: string;
method: RequestMethod;
version?: VersionValue;
}
export declare enum RequestMethod {
GET = 0,
POST = 1,
PUT = 2,
DELETE = 3,
PATCH = 4,
ALL = 5,
OPTIONS = 6,
HEAD = 7,
SEARCH = 8
}
export class PersonModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggerWare).forRoutes('person') // forRoutes:string | forRoutes
}
}
//Person.module.ts
...
interface forRoutes {
path: string;
method: RequestMethod;
version?: VersionValue;
}
export declare enum RequestMethod {
GET = 0,
POST = 1,
PUT = 2,
DELETE = 3,
PATCH = 4,
ALL = 5,
OPTIONS = 6,
HEAD = 7,
SEARCH = 8
}
export class PersonModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggerWare).forRoutes('person') // forRoutes:string | forRoutes
}
}
forRoutes 还支持通配符
forRoutes({ path: 'ab*cd', method: RequestMethod.ALL })
forRoutes({ path: 'ab*cd', method: RequestMethod.ALL })
'ab*cd' 路由路径将匹配 abcd 、 ab_cd abecd 、 等。字符 ? 、 + 、 * 和 () 可以在路由路径中使用,并且是其正则表达式对应项的子集。连字符 ( - ) 和点 ( . ) 由基于字符串的路径逐字解释。
异常筛选器
Nest 带有一个内置的异常层,负责处理整个应用程序中的所有未经处理的异常。当应用程序代码未处理异常时,该层会捕获该异常,然后自动发送适当的用户友好响应。
虽然基本(内置)异常筛选器可以自动为您处理许多情况,但我们可能希望完全控制异常层。例如,您可能希望添加日志记录或根据某些动态因素使用不同的 JSON 架构。异常筛选器正是为此目的而设计的。它们允许您控制确切的控制流以及发送回客户端的响应内容。
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,
})
}
}
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,
})
}
}
使用方式
1. 全局使用
// main.ts 全局使用 app.useGlobalFilters(new HttpExceptionFilter())
async function bootstrap() {
const app = await NestFactory.create(AppModule)
app.useGlobalFilters(new HttpExceptionFilter())
await app.listen(3000)
}
bootstrap()
// main.ts 全局使用 app.useGlobalFilters(new HttpExceptionFilter())
async function bootstrap() {
const app = await NestFactory.create(AppModule)
app.useGlobalFilters(new HttpExceptionFilter())
await app.listen(3000)
}
bootstrap()
2. 局部使用
// person.controller.ts 局部使用
@Controller('person')
@UseFilters(new HttpExceptionFilter())
export class PersonController {}
// person.controller.ts 局部使用
@Controller('person')
@UseFilters(new HttpExceptionFilter())
export class PersonController {}
3. 动态使用
// person.controller.ts 动态使用
@Controller('person')
export class PersonController {
@Get()
@UseFilters(new HttpExceptionFilter()) // 最好使用类,这样可以有缓存 HttpExceptionFilter
findAll() {
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN)
}
}
// person.controller.ts 动态使用
@Controller('person')
export class PersonController {
@Get()
@UseFilters(new HttpExceptionFilter()) // 最好使用类,这样可以有缓存 HttpExceptionFilter
findAll() {
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN)
}
}
管道
管道有两个典型的用例:
转换:将输入数据转换为所需的形式(例如,从字符串转换为整数)
验证:评估输入数据,如果有效,只需原封不动地传递它;否则,引发异常
管道验证
@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number) {
return this.catsService.findOne(id);
}
@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number) {
return this.catsService.findOne(id);
}
假如路由调用方式如下
GET localhost:3000/abc
GET localhost:3000/abc
那么会抛出异常
{
"statusCode": 400,
"message": "Validation failed (numeric string is expected)",
"error": "Bad Request"
}
{
"statusCode": 400,
"message": "Validation failed (numeric string is expected)",
"error": "Bad Request"
}
管道验证-类验证器
Nest 与类验证器库配合得很好。这个强大的库允许您使用基于装饰器的验证。基于装饰器的验证非常强大,特别是当与 Nest 的管道功能结合使用时,因为我们可以访问 metatype 已处理的属性
npm i class-validator class-transformer
npm i class-validator class-transformer
// person.create.dto.ts
import { IsString, IsEmail, IsNotEmpty } from 'class-validator'
export class CreatePersonDto {
@IsNotEmpty({ message: '姓名不能为空' }) // 验证是否为空
@IsString({ message: '参数类型错误' }) // 验证是否为字符串
name: string
@IsString({ message: 'age参数需为数字类型' }) // 验证是否为字符串
age: string
@IsEmail({}, { message: '邮箱格式不正确' }) // 验证是否为字符串
email: string
}
// person.create.dto.ts
import { IsString, IsEmail, IsNotEmpty } from 'class-validator'
export class CreatePersonDto {
@IsNotEmpty({ message: '姓名不能为空' }) // 验证是否为空
@IsString({ message: '参数类型错误' }) // 验证是否为字符串
name: string
@IsString({ message: 'age参数需为数字类型' }) // 验证是否为字符串
age: string
@IsEmail({}, { message: '邮箱格式不正确' }) // 验证是否为字符串
email: string
}
让我们先写一个内置的 Pipes
// validation.pipe.ts
import {
PipeTransform,
Injectable,
ArgumentMetadata,
BadRequestException,
HttpStatus,
} from '@nestjs/common'
import { validate } from 'class-validator'
import { plainToInstance } from 'class-transformer'
import { HttpException } from '@nestjs/common'
@Injectable()
export class ValidationPipe implements PipeTransform<any> {
async transform(value: any, { metatype }: ArgumentMetadata) {
// value: { name: '', age: '20', email: '2382839439@qq.com' }前端传入的参数
// metatype: [class CreatePersonDto] // 参数的类型
if (!metatype || !this.toValidate(metatype)) {
return value
}
const object = plainToInstance(metatype, value)
const errors = await validate(object)
// console.log(errors);
if (errors.length > 0) {
// 抛出错误对象,利用异常筛选器来接收然后返回给前端错误对象
// HttpStatus.BAD_REQUEST规定为参数错误
throw new HttpException(errors, HttpStatus.BAD_REQUEST, {
description: '参数异常',
})
}
return value
}
private toValidate(metatype: Function): boolean {
const types: Function[] = [String, Boolean, Number, Array, Object]
return !types.includes(metatype)
}
}
// validation.pipe.ts
import {
PipeTransform,
Injectable,
ArgumentMetadata,
BadRequestException,
HttpStatus,
} from '@nestjs/common'
import { validate } from 'class-validator'
import { plainToInstance } from 'class-transformer'
import { HttpException } from '@nestjs/common'
@Injectable()
export class ValidationPipe implements PipeTransform<any> {
async transform(value: any, { metatype }: ArgumentMetadata) {
// value: { name: '', age: '20', email: '2382839439@qq.com' }前端传入的参数
// metatype: [class CreatePersonDto] // 参数的类型
if (!metatype || !this.toValidate(metatype)) {
return value
}
const object = plainToInstance(metatype, value)
const errors = await validate(object)
// console.log(errors);
if (errors.length > 0) {
// 抛出错误对象,利用异常筛选器来接收然后返回给前端错误对象
// HttpStatus.BAD_REQUEST规定为参数错误
throw new HttpException(errors, HttpStatus.BAD_REQUEST, {
description: '参数异常',
})
}
return value
}
private toValidate(metatype: Function): boolean {
const types: Function[] = [String, Boolean, Number, Array, Object]
return !types.includes(metatype)
}
}
使用内置 pipes
// person.controller.ts
import {ValidationPipe} from './common/validation.pipe'
@Post()
@HttpCode(200)
async create(@Body(new ValidationPipe()) CreatepersonDto: CreatePersonDto) {
// console.log(CreatepersonDto); // { name: 'ayu', age: '20', email: '2382839439@qq.com' }
return `received: ${JSON.stringify(CreatepersonDto)}`;
}
// person.controller.ts
import {ValidationPipe} from './common/validation.pipe'
@Post()
@HttpCode(200)
async create(@Body(new ValidationPipe()) CreatepersonDto: CreatePersonDto) {
// console.log(CreatepersonDto); // { name: 'ayu', age: '20', email: '2382839439@qq.com' }
return `received: ${JSON.stringify(CreatepersonDto)}`;
}
全局管道
当然,nestjs 也可以使用全局管道 我们只需要将 create.dto.ts 封装好即可
// main.ts
import { ValidationPipe } from '@nestjs/common'
app.useGlobalPipes(new ValidationPipe())
// main.ts
import { ValidationPipe } from '@nestjs/common'
app.useGlobalPipes(new ValidationPipe())
管道修饰
验证并不是自定义管道的唯一用例。在本章开头,我们使用 nest 内置的 ParseIntPipe 将传入的值进行修饰,我们通过实现 ParseIntPipe 来学习如何做管道修饰
// parse-int.pipe.ts
import {
PipeTransform,
Injectable,
ArgumentMetadata,
BadRequestException,
} from '@nestjs/common'
@Injectable()
export class MyParseIntPipe implements PipeTransform<string, number> {
transform(value: string, metadata: ArgumentMetadata): number {
// console.log('metadata: ', metadata); // metadata: { type: 'param', metatype: [Function: Number], data: 'id' }
// console.log('value: ', value); // value: 前端传入的参数
// you can do something here before return value
const val = parseInt(value, 10)
if (isNaN(val)) {
throw new BadRequestException('参数不符合规范')
}
return val
}
}
// parse-int.pipe.ts
import {
PipeTransform,
Injectable,
ArgumentMetadata,
BadRequestException,
} from '@nestjs/common'
@Injectable()
export class MyParseIntPipe implements PipeTransform<string, number> {
transform(value: string, metadata: ArgumentMetadata): number {
// console.log('metadata: ', metadata); // metadata: { type: 'param', metatype: [Function: Number], data: 'id' }
// console.log('value: ', value); // value: 前端传入的参数
// you can do something here before return value
const val = parseInt(value, 10)
if (isNaN(val)) {
throw new BadRequestException('参数不符合规范')
}
return val
}
}
拦截器
拦截器是用装饰器注释 @Injectable() 并实现接口的 NestInterceptor 类。
拦截器的功能
- 在方法执行之前/之后绑定额外的逻辑
- 转换从处理程序方法返回的结果
- 转换从处理程序抛出的异常
- 扩展基本的处理程序类以执行额外的任务
- 根据特定条件完全覆盖函数(例如,出于缓存目的)
###规范响应返回值
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common'
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'
export interface Response<T> {
data: T
}
@Injectable()
export class TransformInterceptor<T>
implements NestInterceptor<T, Response<T>>
{
intercept(
context: ExecutionContext,
next: CallHandler
): Observable<Response<T>> {
return next
.handle()
.pipe(map((data) => ({ data, status: 0, success: true, msg: '成功' })))
}
}
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common'
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'
export interface Response<T> {
data: T
}
@Injectable()
export class TransformInterceptor<T>
implements NestInterceptor<T, Response<T>>
{
intercept(
context: ExecutionContext,
next: CallHandler
): Observable<Response<T>> {
return next
.handle()
.pipe(map((data) => ({ data, status: 0, success: true, msg: '成功' })))
}
}
使用
- 局部使用
// cats.controller.ts
@UseInterceptors(TransformInterceptor)
export class CatsController {}
// cats.controller.ts
@UseInterceptors(TransformInterceptor)
export class CatsController {}
- 全局使用
// main.ts
app.useGlobalInterceptors(new TransformInterceptor())
// main.ts
app.useGlobalInterceptors(new TransformInterceptor())
警卫
守卫是用 @Injectable() 装饰器注释的类,它实现 CanActivate 接口
警卫只有一个责任。它们确定给定的请求是否将由路由处理程序处理,具体取决于运行时存在的某些条件(如权限、角色、ACL 等)。这通常称为授权。授权(及其表亲身份验证,它通常与之协作)通常由传统 Express 应用程序中的中间件处理。中间件是身份验证的不错选择,因为令牌验证和将属性附加到 request 对象等内容与特定路由上下文(及其元数据)没有很强的联系
通过一个例子来说明警卫的作用
// auth.guard.ts
import {
Injectable,
CanActivate,
ExecutionContext,
UnauthorizedException,
} from '@nestjs/common'
import { Reflector } from '@nestjs/core'
import { Request } from 'express'
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<string[]>('role', context.getHandler())
// 指派的守卫验证
// roles => ['admin']
if (!roles) {
return true
}
const request: Request = context.switchToHttp().getRequest()
// 验证是否存在token
//request.headers.authorization => Bearer 121218218219219219281
const token = request.headers.authorization
console.log(token, roles)
return matchRoles(roles, token)
// 如果守卫验证失败我们可以加上自定义的错误信息
// throw new UnauthorizedException('无权限访问');
}
}
function matchRoles(roles: string[], token: string) {
// verify
// do something
return true
}
// auth.guard.ts
import {
Injectable,
CanActivate,
ExecutionContext,
UnauthorizedException,
} from '@nestjs/common'
import { Reflector } from '@nestjs/core'
import { Request } from 'express'
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<string[]>('role', context.getHandler())
// 指派的守卫验证
// roles => ['admin']
if (!roles) {
return true
}
const request: Request = context.switchToHttp().getRequest()
// 验证是否存在token
//request.headers.authorization => Bearer 121218218219219219281
const token = request.headers.authorization
console.log(token, roles)
return matchRoles(roles, token)
// 如果守卫验证失败我们可以加上自定义的错误信息
// throw new UnauthorizedException('无权限访问');
}
}
function matchRoles(roles: string[], token: string) {
// verify
// do something
return true
}
需要使用警卫的 controller
// person.controller.ts
// @UseGuards(new RolesGuard(new Reflector()))
// @UseGuards(RolesGuard)
@Controller('person')
export class PersonController {
private readonly personService: PersonService
@Post()
@SetMetadata('role', ['admin']) // 设置的警卫验证的角色
async create(@Body() CreatepersonDto: CreatePersonDto) {
// console.log(CreatepersonDto); // { name: 'ayu', age: '20', email: '2382839439@qq.com' }
return `received: ${JSON.stringify(CreatepersonDto)}`
}
}
// person.controller.ts
// @UseGuards(new RolesGuard(new Reflector()))
// @UseGuards(RolesGuard)
@Controller('person')
export class PersonController {
private readonly personService: PersonService
@Post()
@SetMetadata('role', ['admin']) // 设置的警卫验证的角色
async create(@Body() CreatepersonDto: CreatePersonDto) {
// console.log(CreatepersonDto); // { name: 'ayu', age: '20', email: '2382839439@qq.com' }
return `received: ${JSON.stringify(CreatepersonDto)}`
}
}
在 person.controller.ts 中,我们使用了@SetMetadata()装饰器来设置警卫验证的角色,然后在 auth.guard.ts 中使用@Reflect()装饰器来获取这个角色,然后进行验证,如果验证成功则返回 true,否则返回 false,如果返回 false 则会抛出一个异常,我们可以在 auth.guard.ts 中加上自定义的错误信息,如上面的代码所示,如果我们不加上自定义的错误信息,那么会抛出一个默认的错误信息,如下所示:
{
"statusCode": 401,
"message": "Unauthorized"
}
{
"statusCode": 401,
"message": "Unauthorized"
}
并且在 person.controller.ts 中,我们使用了@UseGuards()装饰器来使用警卫,我们可以在@UseGuards()装饰器中传入一个或多个警卫,如上面的代码所示,我们传入了一个 RolesGuard 警卫,这样就可以对 person.controller.ts 中的 create()方法进行验证了,可以通过直接写类的形式,但是如果使用实例的形式就必须传入参数 new Reflector()
全局警卫 我们不仅可以局部对 controller 进行警卫设置,还可以设置全局通用警卫
// main.ts
import { RolesGuard } from './common/auth.guard';
import { Reflector } from '@nestjs/core';
...
app.useGlobalGuards(new RolesGuard(new Reflector()));//我使用的时候必须要携带这个参数
// main.ts
import { RolesGuard } from './common/auth.guard';
import { Reflector } from '@nestjs/core';
...
app.useGlobalGuards(new RolesGuard(new Reflector()));//我使用的时候必须要携带这个参数
自定义装饰器
经过了前面的学习,我们发现 nestjs 的装饰器是非常强大的,但是有时候我们需要自定义装饰器,来实现一些特殊的功能
我们先简单实现一个装饰器
import { createParamDecorator, ExecutionContext } from '@nestjs/common'
import { Request } from 'express'
// 自定义装饰器
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
// data是传入的参数,ctx是上下文
const request: Request = ctx.switchToHttp().getRequest() // 请求对象
// 我们可以通过传入的data来当返回的对象的key,然后返回特定的值
return request.originalUrl // get something to return
}
)
import { createParamDecorator, ExecutionContext } from '@nestjs/common'
import { Request } from 'express'
// 自定义装饰器
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
// data是传入的参数,ctx是上下文
const request: Request = ctx.switchToHttp().getRequest() // 请求对象
// 我们可以通过传入的data来当返回的对象的key,然后返回特定的值
return request.originalUrl // get something to return
}
)
然后就可以直接使用它
@Get()
async findAll(@User() user: string) {
console.log(user)
return this.catsService.findAll()
}
@Get()
async findAll(@User() user: string) {
console.log(user)
return this.catsService.findAll()
}
详细内容可以参考官方文档:自定义装饰器
TypeOrm
Nest 与数据库无关,允许您轻松地与任何 SQL 或 NoSQL 数据库集成。根据您的喜好,您有许多选项可供您使用。在最一般的层面上,将 Nest 连接到数据库只是为数据库加载适当的 Node.js 驱动程序,就像使用 Express 或 Fastify 一样。在此我们使用 TypeORM。以下是关于 TypeORM 的一些基本信息:
- TypeORM 是一个 ORM 框架,它可以运行在 NodeJS、浏览器、Cordova、PhoneGap、Ionic、React Native、Expo 和 Electron 平台上,可以与 TypeScript 和 JavaScript (ES5, ES6, ES7, ES8) 一起使用。
安装
npm install --save @nestjs/typeorm typeorm mysql
npm install --save @nestjs/typeorm typeorm mysql
安装完成后在 app.module.ts 中引入
import { Module } from '@nestjs/common'
import { AppController } from './app.controller'
import { AppService } from './app.service'
import { TypeOrmModule } from '@nestjs/typeorm'
@Module({
imports: [
PersonModule,
UserModule,
TypeOrmModule.forRoot({
// 配置信息
type: 'mysql', //数据库类型
username: 'root', //账号
password: 'xiangjiayi53822', //密码
host: 'localhost', //host
port: 3306, //
database: 'db', //库名
// entities: [__dirname + '/**/*.entity{.ts,.js}'], //实体文件
synchronize: true, //synchronize字段代表是否自动将实体类同步到数据库 生产环境不建议使用
retryDelay: 500, //重试连接数据库间隔
retryAttempts: 10, //重试连接数据库的次数
autoLoadEntities: true, //如果为true,将自动加载实体 forFeature()方法注册的每个实体都将自动添加到配置对象的实体数组中
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
import { Module } from '@nestjs/common'
import { AppController } from './app.controller'
import { AppService } from './app.service'
import { TypeOrmModule } from '@nestjs/typeorm'
@Module({
imports: [
PersonModule,
UserModule,
TypeOrmModule.forRoot({
// 配置信息
type: 'mysql', //数据库类型
username: 'root', //账号
password: 'xiangjiayi53822', //密码
host: 'localhost', //host
port: 3306, //
database: 'db', //库名
// entities: [__dirname + '/**/*.entity{.ts,.js}'], //实体文件
synchronize: true, //synchronize字段代表是否自动将实体类同步到数据库 生产环境不建议使用
retryDelay: 500, //重试连接数据库间隔
retryAttempts: 10, //重试连接数据库的次数
autoLoadEntities: true, //如果为true,将自动加载实体 forFeature()方法注册的每个实体都将自动添加到配置对象的实体数组中
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
存储库
TypeORM 支持存储库设计模式,因此每个实体都有自己的存储库。可以从数据库数据源获取这些存储库。
// user.entity.ts
// 创建一个实体,并且将其映射到数据库表
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column()
firstName: string
@Column()
lastName: string
@Column({ default: true })
isActive: boolean
}
// user.entity.ts
// 创建一个实体,并且将其映射到数据库表
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column()
firstName: string
@Column()
lastName: string
@Column({ default: true })
isActive: boolean
}
使用实体
import { Module } from '@nestjs/common'
import { TestService } from './test.service'
import { TestController } from './test.controller'
import { TypeOrmModule } from '@nestjs/typeorm'
import { Test } from './entities/test.entity'
@Module({
imports: [TypeOrmModule.forFeature([Test])],
controllers: [TestController],
providers: [TestService],
})
export class TestModule {}
import { Module } from '@nestjs/common'
import { TestService } from './test.service'
import { TestController } from './test.controller'
import { TypeOrmModule } from '@nestjs/typeorm'
import { Test } from './entities/test.entity'
@Module({
imports: [TypeOrmModule.forFeature([Test])],
controllers: [TestController],
providers: [TestService],
})
export class TestModule {}
此模块使用 forFeature() 该方法定义在当前范围内注册的存储库。有了这个,我们可以将它 TestRepository 注入到使用装饰器中 TestService @InjectRepository()
import { Injectable } from '@nestjs/common'
import { CreateTestDto } from './dto/create-test.dto'
import { UpdateTestDto } from './dto/update-test.dto'
import { Test } from './entities/test.entity'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
@Injectable()
export class TestService {
@InjectRepository(Test) private usersRepository: Repository<Test>
create(createTestDto: CreateTestDto) {
const test = new Test()
test.firstName = createTestDto.firstName
test.lastName = createTestDto.lastName
test.isActive = createTestDto.isActive
return this.usersRepository.save(test) // 添加数据
}
findAll(): Promise<Test[]> {
return this.usersRepository.find()
}
findOne(firstName: string): Promise<Test | null> {
return this.usersRepository.findOneBy({ firstName })
}
update(id: number, updateTestDto: UpdateTestDto) {
return `This action updates a #${id} test`
}
async remove(id: number): Promise<void> {
// await this.usersRepository.delete(id);
await this.usersRepository
.createQueryBuilder('Test')
.delete()
.where('id = :id', { id })
.execute()
}
}
import { Injectable } from '@nestjs/common'
import { CreateTestDto } from './dto/create-test.dto'
import { UpdateTestDto } from './dto/update-test.dto'
import { Test } from './entities/test.entity'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
@Injectable()
export class TestService {
@InjectRepository(Test) private usersRepository: Repository<Test>
create(createTestDto: CreateTestDto) {
const test = new Test()
test.firstName = createTestDto.firstName
test.lastName = createTestDto.lastName
test.isActive = createTestDto.isActive
return this.usersRepository.save(test) // 添加数据
}
findAll(): Promise<Test[]> {
return this.usersRepository.find()
}
findOne(firstName: string): Promise<Test | null> {
return this.usersRepository.findOneBy({ firstName })
}
update(id: number, updateTestDto: UpdateTestDto) {
return `This action updates a #${id} test`
}
async remove(id: number): Promise<void> {
// await this.usersRepository.delete(id);
await this.usersRepository
.createQueryBuilder('Test')
.delete()
.where('id = :id', { id })
.execute()
}
}
Repository
使用 EntityManager 您可以管理(插入、更新、删除、加载等)任何实体。实体管理器就像一个地方所有实体存储库的集合,但是我们大部分都是在使用存储库 Repository 去操作对应的实体,
在 typeorm 中存在很多操作实体来操作数据库的方法,这些方法都是通过 Repository 来实现的,而内部很多查找方法都依据 findOptions 来实现的,所以我们可以通过 findOptions 来实现更多的操作。
所有存储库和管理器 .find* 方法都接受特殊选项,可用于查询所需的数据
userRepository.find(findoptions: FindManyOptions<Test>): Promise<Test[]>
// 在此列出常用findoptions的查找选项
{
select:['id','firstName'],
// SELECT "firstName", "lastName" FROM "user
relations:['test'],
// LEFT JOIN "test" "test" ON "test"."id"="user"."testId"
where:{firstName:'xiang',}, // {expression} | [{expression}]
// WHERE "user"."firstName" = 'xiang'
/*
where: [
{ firstName: "Timber", lastName: "Saw" },
{ firstName: "Stan", lastName: "Lee" },
],
SELECT * FROM "user" WHERE ("firstName" = 'Timber' AND "lastName" = 'Saw') OR ("firstName" = 'Stan' AND "lastName" = 'Lee')
*/
order:{firstName:'ASC',lastName:'DESC'},
// SELECT * FROM "user"ORDER BY "name" ASC, "id" DESC
// 类似于limit
skip: 5, // 跳过前5条数据
take: 10, // 取10条数据
// skip 并 take 应一起使用
cache :true // 启用或禁用查询结果缓存。
// 运算符
title: Not("About #1"),
// SELECT * FROM "post" WHERE "title" != 'About #1'
likes: LessThan(10)
// SELECT * FROM "post" WHERE "likes" < 10
}
userRepository.find(findoptions: FindManyOptions<Test>): Promise<Test[]>
// 在此列出常用findoptions的查找选项
{
select:['id','firstName'],
// SELECT "firstName", "lastName" FROM "user
relations:['test'],
// LEFT JOIN "test" "test" ON "test"."id"="user"."testId"
where:{firstName:'xiang',}, // {expression} | [{expression}]
// WHERE "user"."firstName" = 'xiang'
/*
where: [
{ firstName: "Timber", lastName: "Saw" },
{ firstName: "Stan", lastName: "Lee" },
],
SELECT * FROM "user" WHERE ("firstName" = 'Timber' AND "lastName" = 'Saw') OR ("firstName" = 'Stan' AND "lastName" = 'Lee')
*/
order:{firstName:'ASC',lastName:'DESC'},
// SELECT * FROM "user"ORDER BY "name" ASC, "id" DESC
// 类似于limit
skip: 5, // 跳过前5条数据
take: 10, // 取10条数据
// skip 并 take 应一起使用
cache :true // 启用或禁用查询结果缓存。
// 运算符
title: Not("About #1"),
// SELECT * FROM "post" WHERE "title" != 'About #1'
likes: LessThan(10)
// SELECT * FROM "post" WHERE "likes" < 10
}
更多请访问https://typeorm.io/find-options
Repository API
使用 Repository 可以实现对数据库的增删改查,里面有很多方法 我观察发现带有 by 的方法是没有 by 的简写,不带 by 的需要加入 where 条件,而带 by 的不需要加入 where 条件
具体访问 https://typeorm.io/repository-api
Nestjs 技巧
虽然 nestjs 的框架已经很完善了,但是我们还是可以通过一些技巧来提高我们的开发效率,以下是我收集以及个人的总结
1.使用 applyDecorators 减少对装饰器的依赖
对于一个业务模块,写接口以及接口文档如果不做封装,那么代码中会有大量的装饰器出现,导致代码难以阅读,所以我们可以对装饰器进行封装,减少对装饰器的依赖。
import { applyDecorators, SetMetadata, Type } from '@nestjs/common'
import {
ApiOperation,
ApiOkResponse,
ApiBadRequestResponse,
} from '@nestjs/swagger'
export function SwaggerDocumentation(
summary: string,
okDescription: string,
badRequestDescription: string,
type: Type
) {
return applyDecorators(
ApiOperation({ summary }),
ApiOkResponse({ description: okDescription, type }),
ApiBadRequestResponse({ description: badRequestDescription })
)
}
import { applyDecorators, SetMetadata, Type } from '@nestjs/common'
import {
ApiOperation,
ApiOkResponse,
ApiBadRequestResponse,
} from '@nestjs/swagger'
export function SwaggerDocumentation(
summary: string,
okDescription: string,
badRequestDescription: string,
type: Type
) {
return applyDecorators(
ApiOperation({ summary }),
ApiOkResponse({ description: okDescription, type }),
ApiBadRequestResponse({ description: badRequestDescription })
)
}
当然,这只是一个简单的例子,我们可以根据自己的业务需求来封装,这样我们就可以在接口中直接使用这个装饰器,而不用再引入大量的装饰器了。
2.使用缓存
缓存是提高应用程序性能的有效技术。NestJS 使用流行的缓存库(如 Redis 和 Memcached)为缓存提供内置支持。下面是如何在 NestJS 中使用缓存的示例:
@Injectable()
export class UserService {
constructor(private readonly cacheManager: CacheManager) {}
async getUser(id: number): Promise<User> {
const user = await this.cacheManager.get(`user-${id}`)
if (user) {
return user
}
const newUser = await userRepository.findOne(id)
await this.cacheManager.set(`user-${id}`, newUser)
return newUser
}
}
@Injectable()
export class UserService {
constructor(private readonly cacheManager: CacheManager) {}
async getUser(id: number): Promise<User> {
const user = await this.cacheManager.get(`user-${id}`)
if (user) {
return user
}
const newUser = await userRepository.findOne(id)
await this.cacheManager.set(`user-${id}`, newUser)
return newUser
}
}
在本例中,UserService 的 getUser 方法使用 CacheManager 实例缓存用户数据。如果用户数据已经在缓存中,该方法将立即返回该数据。如果没有,该方法将从数据库中检索用户数据,使用 CacheManager 的 set 方法对其进行缓存,并将其返回。
3.使用压缩
压缩是提高应用程序性能的另一种技术。NestJS 使用流行的压缩库(如 zlib)为压缩提供内置支持。以下是如何在 NestJS 中使用压缩的示例:
const app = await NestFactory.create(AppModule)
app.use(compression())
await app.listen(3000)
const app = await NestFactory.create(AppModule)
app.use(compression())
await app.listen(3000)
在本例中,使用应用实例的使用方法将 compression 中间件添加到 NestJS 应用程序中。该中间件在将响应数据发送到客户端之前对其进行压缩,这有助于减少数据大小并缩短响应时间。
4.使用速率限制
为了避免用户不要频繁请求数据,以及防止网站恶意攻击,我们可以使用@nestjs/throttler 来限制用户的请求速率。
npm install --save @nestjs/throttler
npm install --save @nestjs/throttler
然后在 app.module.ts 中引入 ThrottlerModule
imports: [
ThrottlerModule.forRoot([
{
ttl: 6000, // time-to-live 每个请求计数记录的生存时间(秒)
limit: 3, // 给定时间段内允许的最大请求数
},
]),
],
providers: [
{
provide: APP_GUARD,
useClass: ThrottlerGuard, //这里是全局使用,实际开发需要视情况而定
},
],
imports: [
ThrottlerModule.forRoot([
{
ttl: 6000, // time-to-live 每个请求计数记录的生存时间(秒)
limit: 3, // 给定时间段内允许的最大请求数
},
]),
],
providers: [
{
provide: APP_GUARD,
useClass: ThrottlerGuard, //这里是全局使用,实际开发需要视情况而定
},
],
更多信息请访问https://docs.nestjs.com/security/rate-limiting#rate-limiting
5.使用相对路径而非绝对路径
您可以使用绝对路径或者相对路径来导入一个 es6 模块。在开发阶段他们会工作的很顺利,但是当尝试去构建它的时候这会崩溃。当您使用 Nest.js 的时候请始终使用相对路径。你稍后会感谢我的。(npm 运行 build 时,预计会出现绝对路径错误)
// 相对
import { SecurityService } from '../security/security.service'
import { CommentService } from '../comment/comment.service'
// 绝对
import { SecurityService } from 'src/security/security.service'
import { CommentService } from 'src/comment/comment.service'
// 相对
import { SecurityService } from '../security/security.service'
import { CommentService } from '../comment/comment.service'
// 绝对
import { SecurityService } from 'src/security/security.service'
import { CommentService } from 'src/comment/comment.service'
6.使用 Exclude()来隐藏数据
当您从数据库取得数据时,通常要通过 transformers 来筛选数据。主要的目的是为了删除或格式化从数据库过来的数据。这会产生很多垃圾逻辑。
import { Exclude } from 'class-transformer'
export class UserEntity {
id: number
firstName: string
lastName: string
@Exclude()
password: string
// password不会出现在返回的数据中
constructor(partial: Partial<UserEntity>) {
Object.assign(this, partial)
}
}
import { Exclude } from 'class-transformer'
export class UserEntity {
id: number
firstName: string
lastName: string
@Exclude()
password: string
// password不会出现在返回的数据中
constructor(partial: Partial<UserEntity>) {
Object.assign(this, partial)
}
}
7.使用实体 getter
有些逻辑会直接访问您的实体属性。最常见的用例与密码哈希和获取全名有关。但是要注意不要用大量的逻辑来重载实体。为此使用自定义存储库
import { Exclude } from 'class-transformer'
export class UserEntity {
id: number
firstName: string
lastName: string
get fullName() {
return this.firstName + ' ' + this.lastName
}
}
import { Exclude } from 'class-transformer'
export class UserEntity {
id: number
firstName: string
lastName: string
get fullName() {
return this.firstName + ' ' + this.lastName
}
}
8.使用集中式命名导出
假设我们一个 dto 文件内部有很多的 dto,我们可以使用集中式命名导出来导出这些 dto,这样我们就可以在其他文件中直接使用这些 dto 了。只需要在 dto 文件中添加 index.dto.ts 文件,然后在 index.dto.ts 文件中导出所有的 dto 即可。
// index.ts内部
export * from './createPost.dto'
export * from './editPost.dto'
export * from './editPostCategory.dto'
export * from './editPostStatus.dto'
// 从一个文件夹导入
import { CreatePostDto, EditPostDto } from './dto'
// 代替多个文件夹导入
import { CreatePostDto } from './dto/createPost.dto'
import { EditPostDto } from './dto/editPost.dto'
// index.ts内部
export * from './createPost.dto'
export * from './editPost.dto'
export * from './editPostCategory.dto'
export * from './editPostStatus.dto'
// 从一个文件夹导入
import { CreatePostDto, EditPostDto } from './dto'
// 代替多个文件夹导入
import { CreatePostDto } from './dto/createPost.dto'
import { EditPostDto } from './dto/editPost.dto'
nest-cli.json 配置
nest-cli.json 配置
{
"collection": "@nestjs/schematics",
"generateOptions": {
"spec": false
}, // 生成文件时不生成spec文件
"sourceRoot": "src",
"compilerOptions": {
"assets": ["**/*.graphql", "**/*.hbs"]
}
}
{
"collection": "@nestjs/schematics",
"generateOptions": {
"spec": false
}, // 生成文件时不生成spec文件
"sourceRoot": "src",
"compilerOptions": {
"assets": ["**/*.graphql", "**/*.hbs"]
}
}