NestJS 是一个渐进式 Node.js 框架,使用 TypeScript 编写,并且结合了 OOP(面向对象编程),FP(函数式编程)和 FRP(函数式响应编程)的相关理念。NestJS 在设计上与前端框架 Angular 非常类似,与后端框架 Spring 也非常类似,学起来非常容易,五分钟即可上手。
创建项目
安装 NestJS 命令行工具:
npm install -g @nestjs/cli
# 或者
yarn global add @nestjs/cli @nestjs/schematics
创建新项目并启动:
nest new xxx
yarn run start:dev # 启动项目,打开 http://localhost:3000/ 会看到 hello world
主要代码生成在 src 目录下:
├── app.controller.ts # 控制器
├── app.module.ts # 根模块
├── app.service.ts # 服务
└── main.ts # 项目入口,可以选择平台、配置中间件等
它们之间的依赖关系是:
入口文件 main.ts
main.js 通过内置的 NestFactory 来创建应用:
async function bootstrap() {
const app = await NestFactory.create(AppModule)
await app.listen(3000)
}
bootstrap()
根模块 app.module.ts
模块就是被 @Module() 装饰的类。 NestJS 用它来组织应用,每个 NestJS 应用程序至少有一个模块,即根模块:
@Module({
imports: [],
controllers: [AppController, ArticleController, FileController],
providers: [AppService, ArticleService]
})
export class AppModule {}
控制器 app.controller.ts
控制器负责响应客户端的请求,并返回数据。
import { Controller, Get } from '@nestjs/common'
import { AppService } from './app.service'
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello()
}
}
可以看到,控制器就是一个被 @Controller() 装饰器装饰的类。在 NestJS 中新增一个 controller 很简单,例如创建一个 article 控制器:
nest g controller article # 会生成 article/article.controller.ts 文件
控制器内部可以添加方法,例如:
list()方法表示文章列表detail()方法表示文章详情
import { Controller, Get } from '@nestjs/common'
@Controller('article')
export class ArticleController {
@Get('list')
list() {
return ['文章1', '文章2']
}
@Get('detail')
detail() {
return '文章详情'
}
}
这里面的 @Get('list') 和 @Get('detail') 叫做方法装饰器,当用户请求 /article/list 的时候会调用 list() 方法,请求 /article/detail 的时候会调用 detail() 方法。
服务 app.service.ts
NestJS 中服务在 MVC 架构下扮演 Model 的角色,即负责与数据库打交道,供 controller 来调用。
import { Injectable } from '@nestjs/common'
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!'
}
}
可以看到服务本质上就是用 @Injectable() 装饰的类,可以在被注入到任何的控制器中。在 NestJS 中新增服务的命令为:
nest g service article
连接数据库
API 接口已经就绪,就差连接数据这一环节了,这里使用 MongoDB 作为示例,当然也可以使用其他数据库例如 MySQL 等,使用方法可参考官方文档。首先安装相关依赖:
yarn add @nestjs/mongoose mongoose
然后创建一个 schemas 目录用于放置数据库模型:
import { Schema } from 'mongoose'
const ArticleSchema = new Schema(
{
title: { type: String }, // 文章标题
content: { type: String }, // 文章内容
cover: { type: String }, // 文章封面
},
{ timestamps: true }
)
export { ArticleSchema }
然后在 app.module.ts 中引入模型并建立连接:
import { MongooseModule } from '@nestjs/mongoose'
import { ArticleSchema } from './schemas/article.schema'
@Module({
imports: [
MongooseModule.forRoot('mongodb://localhost:27017/api'),
MongooseModule.forFeature([{ name: 'article', schema: ArticleSchema }])
],
controllers: [AppController, ArticleController],
providers: [AppService, ArticleService]
})
export class AppModule {}
然后就可以在 controller 或 service 中注入文章模型,从而实现增删改查逻辑:
@Injectable()
export class ArticleService {
constructor(@InjectModel('article') private articleModel) {}
find() {
return this.articleModel.find({}).lean()
}
}
高级特性
掌握上面的 module、controller、service 这三个概念,以及数据库连接方式之后,就能着手开发项目了,当然还有一些高级的用法,感兴趣的可以继续阅读。
拦截器
拦截器顾名思义是用作拦截,也就是说可以在进入 controller 之前以及 controller 处理之后,加入一些额外的逻辑,例如这里定义一个前置拦截器:
@Injectable()
export class MyInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const req = context.switchToHttp().getRequest()
const query = req.query
const data = req.body
// 对 query 或者 body 进行验证,如果不合法就抛错
return next.handle().pipe(
map(data => {
// 对返回数据进行加工,如果不想影响返回值可以用 tap
return newData
})
)
}
}
文件上传
在 NestJS 中处理文件上传非常容易,下面的示例代码能够让后端拿到前端传过来的文件:
import { Controller, Post, UseInterceptors, UploadedFile, UploadedFiles, Body } from '@nestjs/common'
import { FileInterceptor,FilesInterceptor } from '@nestjs/platform-express'
@Controller('files')
export class FileController {
@Post('single')
@UseInterceptors(FileInterceptor('file'))
single(@UploadedFile() file, @Body() body) {
console.log(file)
console.log(body)
return { ok: 1 }
}
@Post('multiple')
@UseInterceptors(FilesInterceptor('files'))
multiple(@UploadedFiles() files, @Body() body) {
console.log(files)
console.log(body)
return { ok: 1 }
}
}
其中 single 接口用于接收单文件,multiple 用于接收多文件,这里的文件是前端是用 FormData 提交给后端的。