Nestjs是一个基于Nodejs的服务器框架,吸收和借鉴了前端框架Angular的风格,完全支持TypeScript语言,因此,对于熟悉Angular以及TypeScript的用户来说,是服务器端编程的不二选择。在底层,Nestjs支持流行的Express(默认)框架,但也可以选择Fastify框架进行开发。

1.Nestjs安装与快速启动

1.1 nestjs安装

Nestjs项目可以通过命令行,git或者手动创建三种方法开始。一般来说,更推荐使用命令行指令进行开发。使用命令行指令时,需要先安装nestjs-cli命令行模块。按照下述指令开始一个新的项目。

  1. #npm
  2. npm i -g @nestjs/cli
  3. nest new project-name
  4. #yarn
  5. yarn global add @nestjs/cli

使用git安装时,可通过以下命令从github上复制示例项目并在此基础上开始新项目

  1. git clone https://github.com/nestjs/typescript-starter.git project

对于高级用户来说,可能仅需要使用一部分nestjs的核心功能,在这种情况下,通过下列指令可以在项目中安装需要的模块并手动开始一个项目。

  1. $ npm i --save @nestjs/core @nestjs/common rxjs reflect-metadata

1.2 脚手架及项目启动

安装完nestjs的命令行工具后,可通过下列命令快速搭建项目脚手架以开始新项目

  1. nest new project-name
  2. cd project-name
  3. npm run start
  4. #也可以以开发模式启动项目,这样项目中任何修改和变化都可以实时编译
  5. #在脚手架项目的package.json文件中可以看到更多的启动项目及测试的脚本命令
  6. #npm run start:dev

通过git命令下载的项目,只要进入项目,并按照常规的nodejs项目安装模块并启动即可

  1. cd project
  2. npm install
  3. npm run start

运行上述命令后,打开http://localhost:3000,可以看到hello world页面。

1.3 Visual Studio Code配置Nestjs项目插件

在VS Code中进行Nestjs项目开发时,除了node环境和上述nestjs命令行工具外,可以安装以下插件来提高项目开发效率和速度。

  • nestjs Files,该插件可以实现nestjs部分生成命令的右键快捷操作。例如,如果要在一个名为cats的文件夹下新建Controller,可以在cats文件夹上点击右键,选择Generate Controller并输入Controller名称cats,就可以生成cats.controller.ts文件并在其中生成并导出CatsController类。这和Angular插件的用法非常相似,实际上相当于执行nest g controller cats或者nest g service cats命令。
  • NestJs Snippets,提供了大量的nestjs语法片段可快速使用。在命令栏Ctrl+Shift+P输入n-可以看到插件支持的大部分语法和命令。
  • REST Client。用于测试API的非常高效的VS Code插件,基本可以完全取代Postman。

2. nestjs脚手架项目

除了主入口文件main.ts外,nestjs项目一般模块化组成。默认的脚手架项目包括了app.module.ts,app.controller.ts,app.service.ts几个文件。和angular项目结构类似,在nestjs项目中,controller作为控制器,一般用于处理请求并返回响应,service用来实现后台逻辑。

在controller中,使用装饰器来区分不同的路径和请求。在脚手架项目中,controller默认路径的get方法返回appService.getHello()方法,appService.getHello()方法,后者返回一个Hello World字符串。在实际项目中,这样使用同步方法来返回的情况非常少见,更多的时候都是使用异步的方法返回用户请求。以下对默认脚手架项目做一些修改,以异步的方式返回用户请求。默认脚手架项目返回如下内容:

  1. //Controller
  2. @Get()
  3. getHello(): string {
  4. return this.appService.getHello();}
  5. //Service
  6. getHello(): string {
  7. return 'Hello World!';
  8. }

在app.controller.ts文件中分别做如下修改:

  1. //Controller
  2. import { Observable,of } from 'rxjs';
  3. getHello():Observable<string>{
  4. return of(this.appService.getHello());
  5. }

这里使用到了异步编程中最常用的rxjs模块中Observable和of,将字符串Hello World转换为异步对象,并返回到controller中通过async标识的异步函数中再响应给用户。虽然看到的同样是Hello World字符串,但在处理更复杂任务的时候,异步编程的优势就可以更好地显现出来。

3.通过nestjs响应网络请求

3.1 网络请求装饰器

nestjs可以通过装饰器响应不同类型的网络请求。nestjs默认启用express框架,系统提供的装饰器可以满足大部分网络请求,但也支持自定义装饰器来实现更多功能。常用的装饰器与express的对应关系列表如下:

装饰器 请求
@Request() req
@Response(),@Res res
@Next() next
@Session() req.session
@Param(key?: string) req.params / req.params[key]
@Body(key?: string) req.body / req.body[key]
@Query(key?: string) req.query / req.query[key]
@Headers(name?: string) req.headers / req.headers[name]
@HttpStatus 自定义http状态

3.2 nestjs支持的一些http请求和方法:

  • nestjs支持@Put() 、 @Delete()、 @Patch()、 @Options()、 @Head()和 @All()装饰器以这些表示各自的 HTTP请求方法
  • 支持通过@HttpCode()装饰器来返回指定的状态码如201、404等(如@HttpCode(201)),需要从@nestjs/common导入。
  • 支持@Header装饰器来相应特定的header请求如@Header('Cache-Control', 'none'),同样需要从@nestjs/common导入。
  • 支持@Redirect装饰器来重定向资源例如@Redirect(http://www.weizhiyong.com`,301)。
  • 支持模式匹配通配符,例如星号(*)可以被匹配任何字符组合

3.3 路由参数

nestjs可以使用装饰器取得路由参数或http请求中的内容,一般来说,@Param参数用来读取路由参数,如https://www.weizhiyong.com/archives/:id格式的路由,在读取id参数时可以采用以下两种方式:

  1. @Controller('archives')
  2. @Get(':id')
  3. getId(@Param() params):string{
  4. let result=params.id;
  5. return result;
  6. }
  7. //或者
  8. @Controller('archives')
  9. @Get(':id')
  10. getId(@Param('id')id):string{
  11. return id;
  12. }

类似地,如果要从JSON格式的Body请求中取得参数,也可以使用诸如@Body()reqbody 之类的参数进行获取。
在Controller中,也可以通过host参数添加特定的域名来访问指定值(仅适用于Express),例如.Controller({ host: 'admin.example.com' })

4.nestjs入门示例

在脚手架项目的基础上,参考官方文档,新建一个cats模块,来实现对cat的添加和基本查找功能。
在项目根目录下新建cats目录,并在其中新建cats.service.ts,cats.controller.ts和cats.module.ts文件(可以通过前节的nestjs File完成),为了实现数据接口,还需要在cats文件夹下新建dto/create-cat.dto.ts文件和interfaces/cat.interface.ts文件。

4.1 cat.interface.ts接口文件

该文件导出一个cat的接口,用于实现不同文件之间的数据交互。在实际项目中,接口文件不仅仅用于项目中不同模块之间的数据交互,也为实现前后端的统一开发提供了条件。

  1. export interface Cat{
  2. name:string;
  3. age:number;
  4. breed:string;
  5. }

4.2 create-cat.dto.ts数据传输对象(DTO)文件

在处理数据时,通常使用dto来针对不同操作指定数据接口,和interface不同,dto往往用类的方式进行声明。本文件的内容如下:

  1. export class CreateCatDto{
  2. readonly name:string;
  3. readonly age:number;
  4. readonly breed:string;
  5. }

4.3 cats.service.ts 服务器文件

服务在nestjs中被称为提供者(provider),与angular类似,provider通过注入的方式注入在controller或者module文件中以创建各种关系并执行不同功能,除了service外,其他被称为provider的类型还包括repository,factory和helper,所有的provider在nestjs中都通过@Injectable()装饰器来标识。除了@Injectable()装饰器外,提供者还支持@Optional()装饰器,来表示该provider是可选的。除了诸如某个类之外,提供者也可仅针对类中的某个属性进行诸如。本示例的文件内容如下:

  1. import { Injectable } from '@nestjs/common';
  2. import {Cat} from './interfaces/cat.interface';
  3. @Injectable()
  4. export class CatsService {
  5. private readonly cats:Cat[]=[];
  6. create(cat:Cat){
  7. this.cats.push(cat);
  8. return {'status':'ok'};
  9. }
  10. findAll():Cat[]{
  11. return this.cats;
  12. }
  13. }

4.4 cats.controller.ts 控制器文件

控制器通过@Controller('cats')装饰器标识路径cats。通过@Post,@Get等装饰器来实现不同的http请求方法。这里使用了异步函数的实现方式,这也是nestjs中主要使用的方式。需要注意的是,异步函数可以通过Promise或者Observable两种不同方法来实现。当使用Promise时,需要用async前缀,需要Observable时,需要从rxjs中引入Observable和of。在本例中,通过@Body() createCatDto:CreateCatDto装饰器与数据传输对象从用户请求中读取数据,并保证数据格式符合要求。

  1. import { Controller,Get,Post,Body } from '@nestjs/common';
  2. import {CatsService} from './cats.service';
  3. import {Cat} from './interfaces/cat.interface';
  4. import {CreateCatDto} from './dto/create-cat.dto';
  5. import {Observable,of} from 'rxjs';
  6. @Controller('cats')
  7. export class CatsController {
  8. constructor(private readonly catsService:CatsService){}
  9. @Post()
  10. create(@Body() createCatDto:CreateCatDto):Observable<any>{
  11. return of(this.catsService.create(createCatDto));
  12. }
  13. @Get()
  14. findAll():Observable<Cat[]>{
  15. return of(this.catsService.findAll());
  16. }
  17. // async findAll():Promise<Cat[]>{
  18. // return this.catsService.findAll();
  19. // }
  20. }

4.5 cats.module.ts模块文件

模块文件内容如下,在模块文件中列出了本模块下的Controller和Service,如果需要引入其他模块和内容,也需要在imports中列出。如果要在模块间共享服务或者实例,也可以通过exports数组列出,例如要在其他模块中使用CatsService,可参见下文(注释掉)的exports部分代码。如果要在模块中注入提供者(比如出于配置参数的目的),也可以在模块的constructor中实现(见下列代码中注释部分)。
在nestjs中,全局模块通过@Global()装饰器标识,动态模块可以使用forRoot来同步或异步(使用Promise)返回。

  1. import { CatsService } from './cats.service';
  2. import { CatsController } from './cats.controller';
  3. import { Module } from '@nestjs/common';
  4. @Module({
  5. imports: [],
  6. controllers: [
  7. CatsController, ],
  8. providers: [
  9. CatsService, ],
  10. //exports:[CatsService]
  11. })
  12. export class CatsModule {
  13. //constructor(private readonly someService:SomeService){}
  14. }

最后,修改app.module.ts文件以导入并使用cats模块。

  1. import { CatsModule } from './cats/cats.module';
  2. import { Module } from '@nestjs/common';
  3. import { AppController } from './app.controller';
  4. import { AppService } from './app.service';
  5. @Module({
  6. imports: [
  7. CatsModule, ],
  8. controllers: [AppController],
  9. providers: [AppService],
  10. })
  11. export class AppModule {}

4.6 使用REST Client客户端测试

编写一个后缀为.http的文件,就可以在nest项目运行时通过REST Client插件进行测试。在REST Client中,@用于标识变量,如以下示例中的 host 变量。 ### 用来隔开不同的命令,以保证每次执行指定的变量。可以在指令行点击指令上方的send request或者使用快捷键Ctrl+Alt+R来发送指令,并验证返回情况。

  1. @host=http://127.0.0.1:3000
  2. ###
  3. {{host}}
  4. ###
  5. Get {{host}}/cats
  6. content-type: application/json
  7. ###
  8. Post {{host}}/cats
  9. content-type: application/json
  10. {
  11. "name":"Kitten1",
  12. "age":1
  13. }
  14. ###
  15. Post {{host}}/cats
  16. content-type: application/json
  17. {
  18. "name":"Kitten2",
  19. "age":2
  20. }

nestjs资源