开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情
前言
本文章记录自己学习
Nest
的过程,适于前端及对后端没有基础但对Nest
感兴趣的同学,如有错误,欢迎各位大佬指正
Nest
成长足迹系列:
- 篇幅一: Nest介绍、swagger的使用(本篇)
- 篇幅二:TypeORM操作数据库、Pipe校验参数
- 篇幅三:用户登录颁发jwt
- 篇幅四:使用nodemailer发送html/ejs文件模版的邮箱验证码
- 篇幅五:如何上传文件
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
-
在这我选择更为熟悉且速度更快的
yarn
-
如下图是项目构建完后打开的目录结构
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
, 再创建Controller
和Service
, 这样创建出来的文件在Module
中自动注册,反之,后创建Module
,Controller
和Service
,会被注册到外层的app.module.ts
- 如果忘记命令可以使用
nest --help
进行查看
- 示例:
nest g res user
- 在此我选择
REST API
风格的 nest g res user
就是创建完整的user
模块,这个命令执行完后会生成一个user
文件夹,在这个文件夹中包含如下图所示的文件dto
用于定义系统内的接口或者输入和输出entities
是mysql
数据库中的一张表的实体文件,但要在文件中使用@Entity
装饰器,这个在下文中介绍数据库连接、增删改查时会使用到- 这个后缀为
spec
的文件是测试文件,在快速开发的情况下,比较少用到这个文件,如果不需要每次都生成这个文件,可以在nest-cli.json
文件中添加如下配置,禁用测试用例生成
"generateOptions": { "spec": false }
文件 | 解释 |
---|---|
app.controller.spec.ts | 对于基本控制器的单元测试样例 |
app.controller.ts | 带有单个路由的基本控制器示例。 |
app.module.ts | 应用程序的根模块。 |
app.service.ts | 带有单个方法的基本服务。 |
main.ts | 应用程序入口文件。它使用 NestFactory 用来创建 Nest 应用实例。 |
运行Nest
- 看到
package.json
文件yarn start
:运行项目(未启用热更新)yarn start:dev
:运行项目(启用热更新)
- 一般运行项目都会启用热更新,所以我将
yarn start
和yarn start:dev
进行合并,简洁点使用yarn start
- 运行项目后,访问本地
3000
端口即可看见经典的hello world
- 那
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.ts
、app.controller.ts
是不够的,还有app.module.ts
,在这个文件中引入了app.controller.ts
、app.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
,那就会报错
- 此时输入
localhost:3000/hello
按下回车,hello world
回来啦
- 此时再做个修改
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
,那就会报错
- 此时输入
localhost:3000/hello/world
按下回车,hello world
又回来啦
- 其实这个
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
- 然后在
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
,
- 如果要做到如上图所示,我们需要用到几个
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
校验参数