五分钟快速上手NestJS

3,348 阅读4分钟

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 # 项目入口,可以选择平台、配置中间件等

它们之间的依赖关系是:

NestJS依赖关系

入口文件 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 文件

控制器内部可以添加方法,例如:

  1. list() 方法表示文章列表
  2. 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 提交给后端的。