NestJS 学习笔记
简介
Nest 是一个用于构建 Node.js 服务器端应用程序的框架。内置 TypeScript(也允许用纯 JavaScript编写代码)并结合了 OOP(面向对象编程),FP(函数式编程)和 FRP(函数式响应编程)的元素及思想,设计灵感来自于Angular。
Angular 的很多模式又来自于 Java 中的 Spring 框架,依赖注入、面向切面编程等,所以你可以认为: Nest 是 Node.js 版的 Spring 框架。
同类型框架比较
- Midway
- Egg
- Express
起步
相关命令
#安装脚手架
$ npm i -g @nestjs/cli
#使用脚手架创建一个项目
$ nest new [project-name]
#默认启动项目命令
$ npm run start
#打包项目
$ nest run build
脚手架初始化项目 - 选项
- Which package manager would you to use?(选择你想使用的包管理器)
yarn | npm(默认) | pnpm
初始目录结构
src
├── app.controller.spec.ts 对于基本控制器的单元测试样例
├── app.controller.ts 带有单个路由的基本控制器示例
├── app.module.ts 应用程序的根模块
├── app.service.ts 带有单个方法的基本服务提供者
└── main.ts 应用程序入口文件
其他指令
#查看帮助选项
$ nest --help|-h
#自动在目录中创建一个名为[module-name]的控制器模块文件
$ nest generate|g co [module-name]
#显示项目信息
$ nest info
#更新依赖包
$ nest update|u
#添加依赖包
$ nest add [library-name]
Generate命令说明(点击查看):
| 名称 | 别名 | 说明 |
|---|---|---|
| application | application | 在当前目录创建新的应用(同new指令) |
| class | cl | 生成一个空class文件 |
| configuration | config | 生成CLI配置文件 |
| controller | co | 生成并声明一个控制器模块 |
| decorator | d | 生成一个自定义装饰器文件 |
| filter | f | 生成并声明一个过滤器模块 |
| gateway | ga | 生成并声明一个网关模块 |
| guard | gu | 生成并声明一个权限守卫模块 |
| interceptor | in | 生成并声明一个拦截器模块 |
| interface | interface | 生成并声明一个接口定义模块 |
| middleware | mi | 生成并声明一个中间件模块 |
| module | mo | 生成并声明一个模块管理文件 |
| pipe | pi | 生成并声明一个管道模提供者 |
| resolver | r | 生成并声明一个GraphQL解析器模块 |
| service | s | 生成并声明一个服务模块 |
| library | lib | 在monorepo中生成新库 |
| sub-app | app | 在monorepo中生成新应用程序 |
| resource | res | 生成一个完整的CRUD资源目录 |
定义
Controller(控制器)
通俗来说就是路由Router,负责处理客户端传入的请求参数并向客户端返回响应数据,也可以理解是HTTP请求的逻辑处理。
注:要使用 CLI 创建控制器类,只需执行 $ nest g co [name] 命令。
示例:
/* dto/create-cat.dto.ts */
//定义一个Dto类,规定请求的入参格式。
export class CreateCatDto {
readonly name: string;
readonly age: number;
readonly breed: string;
}
------------------------------------
/* cats.controller.ts */
import { Controller, Get, Query, Post, Body, Put, Param, Delete } from '@nestjs/common';
import { CreateCatDto, UpdateCatDto, ListAllEntities } from './dto';
@Controller('cats') //定义了一个前缀为'cats'的路由模块
export class CatsController {
@Post() //通过Post方法访问到"/cats/"路由
create(@Body() createCatDto: CreateCatDto) {
return '此路由将添加一条新数据';
}
@Get() //通过Get方法访问到"/cats/"路由
findAll(@Query() query: ListAllEntities) {
return `此路由将返回所有数据 (limit: ${query.limit} items)`;
}
@Get(':id') //通过Get方法访问到"/cats/${id}"路由
findOne(@Param('id') id: string) {
return `此路由将根据${id}返回指定数据`;
}
@Put(':id') //通过Put方法访问到"/cats/${id}"路由
update(@Param('id') id: string, @Body() updateCatDto: UpdateCatDto) {
return `此路由将根据${id}更新指定数据`;
}
@Delete(':id') //通过Delete方法访问到"/cats/${id}"路由
remove(@Param('id') id: string) { //delete为js保留关键字,可以使用remove命名
return `此路由将根据${id}删除指定条目`;
}
}
Providers(提供者)
Providers 是一个用@Injectable()装饰器注释的类。许多基本的 Nest 类可能被视为 provider,如service, repository, factory, helper等等,通过 constructor(构造器) 注入依赖关系。
注:要使用 CLI 创建服务类,只需执行 $ nest g s [name] 命令。
最常见的是使用@Injectable()装饰器创建一个提供数据操作服务的类,即service模块。
示例:
/* interfaces/cat.interface.ts */
//声明一个接口,规定请求的入参格式。
export interface Cat {
name: string;
age: number;
breed: string;
}
------------------------------
/* cats.service.ts */
import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';
@Injectable()
export class CatsService {
private readonly cats: Cat[] = [];
create(cat: Cat) {
this.cats.push(cat);
}
findAll(): Cat[] {
return this.cats;
}
}
然后在CatsController里使用它:
/* dto/create-cat.dto.ts */
export class CreateCatDto {
readonly name: string;
readonly age: number;
readonly breed: string;
}
----------------------------------
/* cats.controller.ts */
import { Controller, Get, Post, Body } from '@nestjs/common';
import { CreateCatDto } from './dto/create-cat.dto';
import { CatsService } from './cats.service';
import { Cat } from './interfaces/cat.interface';
@Controller('cats')
export class CatsController {
constructor(private catsService: CatsService) {}
@Post()
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
@Get()
async findAll(): Promise {
return this.catsService.findAll();
}
}
定义了服务模块(提供者)和控制器路由模块(使用者)之后,还需要在模块管理文件中进行注册。
示例:
//app.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class AppModule {}
然后我们就拥有了现在的目录结构:
src
├── cats
│ ├──dto
│ │ └──create-cat.dto.ts
│ ├── interfaces
│ │ └──cat.interface.ts
│ ├──cats.service.ts
│ └──cats.controller.ts
├──app.module.ts
└──main.ts
Module(模块)
模块是具有 @Module() 装饰器的类。 @Module() 装饰器提供了元数据,Nest 用它来组织应用程序结构。
要使用 CLI 创建模块,只需执行 $ nest g module cats 命令。
每个 Nest 应用程序至少有一个模块,即根模块(app.module)。一般情况下应用程序可以按照功能划分若干个子模块,比如cats模块。
示例:
/* 子模块 cats/cats.module.ts */
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService] //若需要在其它模块中CatsService实例,就需要把它导出去。
})
//Provider也可以注入到模块的导出类中,方便用于其他配置。
export class CatsModule {
constructor(private readonly catsService: CatsService) {}
/// 可以通过注入Provider,在这里做一些其它事情...
}
------------------------------------------
/* 根模块 app.module.ts */
import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule], //在这里导入子模块
})
export class ApplicationModule {}
按照上述的导出模块以共享实例,可能不能够满足某些特殊情况。比如我们在很多地方中都用到了相同的模块,或者想要一些模块即取即用(比如helper,数据库连接等),如果在每个地方都这样导入导出就太麻烦了。所以可以把这类模块,通过 @Global 装饰器注册到全局。
示例:
import { Module, Global } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Global()
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService],
})
export class CatsModule {}
通过按照功能划分模块之后,我们的项目结构现在是这样的:
src
├──cats
│ ├──dto
│ │ └──create-cat.dto.ts
│ ├──interfaces
│ │ └──cat.interface.ts
│ ├─cats.service.ts
│ ├─cats.controller.ts
│ └─cats.module.ts
├──app.module.ts
└──main.ts
Middleware(中间件)
中间件是在路由处理程序(controller)之前调用的函数,使用@Injectable()装饰器定义类。提供三个回调参数,请求对象(request)、响应对象(response)和中间件函数(next())。
注:要使用 CLI 创建中间件函数,只需执行 $ nest g mi [name] 命令。
中间件功能:
- 执行任何代码。
- 对请求和响应对象进行更改。
- 结束请求-响应周期。
- 调用堆栈中的下一个中间件函数。
- 如果当前的中间件函数没有结束请求-响应周期, 它必须调用 next() 将控制传递给下一个中间件函数。否则, 请求将被挂起。
示例:
/* logger.middleware.ts */
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { CatsService } from './cats/cats.service';
//使用类的方式创建中间件
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
//同样支持依赖注入
constructor(private readonly catsService: CatsService) {}
use(req: Request, res: Response, next: NextFunction) {
console.log(this.catsService.findAll());
next();
}
}
//使用函数的方式创建中间件
export function logger(req, res, next) {
console.log(`Request...`);
next();
};
然后在app.module中挂载(中间件并不能在@Module()装饰器中像controller数组一样列出,必须在实现NestModule接口的类上使用configure方法来设置和挂载)。
示例:
/* app.module.ts */
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware, logger } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware, logger) //可以指定一个或多个中间件
.forRoutes('cats'); //通过forRoutes可以将中间件限制在制定路由下面使用。
}
}
MiddlewareConsumer是专门用来管理中间件的帮助类。forRoutes方法支持传入字符串、对象或控制器类,并支持正则表达式的匹配。并且可以通过前置方法exclude来标记需要排除的路由。
示例:
consumer
.apply(LoggerMiddleware)
.exclude(
{ path: 'cats', method: RequestMethod.GET },
{ path: 'cats', method: RequestMethod.POST },
'cats/(.*)',
)
.forRoutes(CatsController);
// forRoutes({ path: 'cats', method: RequestMethod.GET })
// forRoutes({ path: 'ab*cd', method: RequestMethod.ALL });
也可以使用入口文件(main)的app.use()方法,全局挂载中间件到所有路由上:
const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(3000);
Filter(过滤器)
过滤器用于处理整个应用程序中所抛出抛出的异常,当捕获到未处理的异常时,我们可以使用过滤器来做一些操作。
注:要使用 CLI 创建中间件函数,只需执行 $ nest g f [name] 命令。
未完待续。。。