Nestjs成长足迹(一):Nest介绍、swagger的使用

2,759 阅读7分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情

前言

本文章记录自己学习Nest的过程,适于前端及对后端没有基础但对Nest感兴趣的同学,如有错误,欢迎各位大佬指正

Nest成长足迹系列:

Nestjs介绍

Nest 是一个用于构建高效,可扩展的 Node.js 服务器端应用程序的框架。它使用渐进式 JavaScript,内置并完全支持 TypeScript(但仍然允许开发人员使用纯 JavaScript 编写代码)并结合了 OOP(面向对象编程),FP(函数式编程)和 FRP(函数式响应编程)的元素。

在底层,Nest使用强大的 HTTP Server 框架,如 Express(默认)和 Fastify。Nest 在这些框架之上提供了一定程度的抽象,同时也将其 API 直接暴露给开发人员。这样可以轻松使用每个平台的无数第三方模块。

Nest安装构建项目

// 先全局安装nest脚手架
npm install -g @nestjs/cli
// 创建项目
nest new xxx

image.png

  • 在这我选择更为熟悉且速度更快的yarn

  • 如下图是项目构建完后打开的目录结构

image.png

Nest的快捷命令

  • nest g mo kylin 创建一个kylin模块,文件目录不写,默认创建和文件名一样的kylin目录,在kylin目录下创建一个kylin.module.ts
//语法
nest g [文件类型] [文件名] [文件目录]
nest g mo
nest g co
nest g service
nest g res user
  • 注意创建顺序: 先创建Module, 再创建ControllerService, 这样创建出来的文件在Module中自动注册,反之,后创建Module, ControllerService,会被注册到外层的app.module.ts
  • 如果忘记命令可以使用nest --help进行查看 nest --help

image.png

  • 示例:nest g res user

image.png

  • 在此我选择REST API风格的
  • nest g res user就是创建完整的user模块,这个命令执行完后会生成一个user文件夹,在这个文件夹中包含如下图所示的文件
    • dto用于定义系统内的接口或者输入和输出
    • entitiesmysql数据库中的一张表的实体文件,但要在文件中使用@Entity装饰器,这个在下文中介绍数据库连接、增删改查时会使用到
    • 这个后缀为spec的文件是测试文件,在快速开发的情况下,比较少用到这个文件,如果不需要每次都生成这个文件,可以在nest-cli.json文件中添加如下配置,禁用测试用例生成
"generateOptions": { "spec": false }

image.png

文件解释
app.controller.spec.ts对于基本控制器的单元测试样例
app.controller.ts带有单个路由的基本控制器示例。
app.module.ts应用程序的根模块。
app.service.ts带有单个方法的基本服务。
main.ts应用程序入口文件。它使用 NestFactory 用来创建 Nest 应用实例。

image.png

运行Nest

  • 看到package.json文件
    • yarn start:运行项目(未启用热更新)
    • yarn start:dev:运行项目(启用热更新) image.png
  • 一般运行项目都会启用热更新,所以我将yarn startyarn start:dev进行合并,简洁点使用yarn start

image.png

  • 运行项目后,访问本地3000端口即可看见经典的hello world

image.png

  • hello world是如何显示出来的呢, 让我们看看它的结构
// 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();
  }
}
// app.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}
  • app.controller.ts文件中引入了app.service.ts中的AppSerice类,在这个类中有一个getHello的方法返回了hello world
  • 而在app.controller.ts中使用@Get装饰器装饰了getHello方法,表示这是一个get请求
  • 仅有app.controller.tsapp.controller.ts是不够的,还有app.module.ts,在这个文件中引入了app.controller.tsapp.controller.ts,而在main.ts中引入了app.module.ts
// app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';

@Module({
  imports: [UserModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

  • module中的模块介绍如下 | 模块 | 介绍 | | --- | --- | | imports | 导入模块的列表,这些模块导出了此模块中所需提供者,一般是module | | controllers | 必须创建的一组控制器,导入controlltr | | providers | 由 Nest 注入器实例化的提供者,并且可以至少在整个模块中共享,导入service | | exports | 由本模块提供并应在其他模块中可用的提供者的子集。一般导出service |

路由

  • 熟悉koa2的人看到这可能想问,它的router在哪定义了呢,其实就是这个@Get,在最初的情况下,这个路由就是localhost:3000,如果稍作改动
// app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller('hello')
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}
  • 如果这个时候再访问localhost:3000,那就会报错

image.png

  • 此时输入localhost:3000/hello按下回车,hello world回来啦

image.png

  • 此时再做个修改
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller('hello')
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get('world')
  getHello(): string {
    return this.appService.getHello();
  }
}
  • 如果这个时候再访问localhost:3000/hello,那就会报错

image.png

  • 此时输入localhost:3000/hello/world按下回车,hello world又回来啦

image.png

  • 其实这个controller.ts文件就相当于了路由,Controller装饰器中的hello,相当于这一整个路由的前缀,而Get装饰器中的world就是get请求的这条路由的路径,整个合起来就是localhost:3000/hello/world,这就是nestjs的路由

全局路由前缀

  • 其实在公司中,一般都有api为开头的接口,那在每个controller.ts文件加这个前缀就太麻烦了,可以选择在main.ts文件中使用setGlobalPrefix设置全局路由前缀
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  // 设置全局路由前缀
  app.setGlobalPrefix('api');
  await app.listen(3000);
}
bootstrap();

swagger

安装

  • 作为一个后端服务,API文档是必不可少的,Nest自带了Swagger文档,集成非常简单,在使用前需要安装依赖
npm install --save @nestjs/swagger

使用

  • src目录下创建swagger.ts(根据自己习惯,我更喜欢将swagger抽离到单独的文件,main.ts越简洁越好,当然你也可以在main.ts中使用swagger
// swagger.ts
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';

import * as packageConfig from '../package.json';

export const generateDocument = (app) => {
  // 创建swagger接口文档
  const options = new DocumentBuilder()
    .setTitle(packageConfig.name) // 标题
    .setDescription(packageConfig.description) // 描述
    .setVersion(packageConfig.version) // 版本
    .addBearerAuth()
    .build();

  const document = SwaggerModule.createDocument(app, options);

  SwaggerModule.setup('/api/swagger', app, document); // 第一个参数是接口文档地址
};

  • 为了节约配置项,Swagger 的配置信息全部取自 package.json,有额外需求的话可以自己维护配置信息的文件
  • 需要注意默认情况下,在 TS 开发的项目中是没办法导入 .json 后缀的模块,在上文中import * as packageConfig from '../package.json',引入了json,需要在tsconfig.json文件中加上"resolveJsonModule": true

image.png

  • 然后在main.ts中进行使用
import { NestFactory } from '@nestjs/core';
import { AppModule } from '@/modules/app.module';

import { generateDocument } from './swagger';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  // 设置全局路由前缀
  app.setGlobalPrefix('api');

  // 创建swagger文档
  generateDocument(app);

  await app.listen(+process.env.SERVICE_PORT, () => {
    console.log(`项目运行在http:localhost:${process.env.SERVICE_PORT}/api`);
  });
}
bootstrap();

  • 打开http://localhost:1231/api/swagger,即可见swagger

image.png

  • 如果要做到如上图所示,我们需要用到几个swagger的装饰器
    • ApiTags:设置swagger文档标签分类
    • ApiOperation:接口描述信息
// user.controller.ts
import {
  Controller,
  Get,
  Post,
  Body,
  Patch,
  Param,
  Delete,
} from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

// 设置swagger文档标签分类
@ApiTags('用户模块')
@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Post('create')
  @ApiOperation({
    summary: '添加用户', // 接口描述信息
  })
  create(@Body() createUserDto: CreateUserDto) {
    return this.userService.create(createUserDto);
  }

  @Get('list')
  @ApiOperation({
    summary: '获取user列表',
  })
  findAll() {
    return this.userService.findAll();
  }

  @Get('list/:id')
  @ApiOperation({
    summary: '根据id获取user',
  })
  findOne(@Param('id') id: string) {
    return this.userService.findOne(+id);
  }

  @Patch('list/:id')
  @ApiOperation({
    summary: '根据id修改user',
  })
  update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
    return this.userService.update(+id, updateUserDto);
  }

  @Delete('list/:id')
  @ApiOperation({
    summary: '根据id删除user',
  })
  remove(@Param('id') id: string) {
    return this.userService.remove(+id);
  }
}

总结

  • 本文对Nest进行介绍其创建项目、快捷命令、目录结构主要文件的作用、如何启动项目、使用swagger的方法
  • 下一篇:TypeORM操作数据库、Pipe校验参数