什么是NestJS
Nest 是一個漸進的 Node.js 框架剧腻,可以在 TypeScript 和 JavaScript (ES6拘央、ES7、ES8)之上構(gòu) 建高效书在、可伸縮的企業(yè)級服務(wù)器端應(yīng)用程序灰伟。
Nest 基于 TypeScript 編寫并且結(jié)合了 OOP(面向?qū)ο缶幊蹋現(xiàn)P(函數(shù)式編程)和 FRP (函數(shù)式響應(yīng)編程)的相關(guān)理念儒旬。在設(shè)計上的很多靈感來自于 Angular栏账,Angular 的很多模 式又來自于 Java 中的 Spring 框架,依賴注入栈源、面向切面編程等挡爵,所以我們也可以認(rèn)為: Nest 是 Node.js 版的 Spring 框架。
Nest 框架底層 HTTP 平臺默認(rèn)是基于 Express 實現(xiàn)的甚垦,所以無需擔(dān)心第三方庫的缺失茶鹃。 Nest 旨在成為一個與平臺無關(guān)的框架。 通過平臺制轰,可以創(chuàng)建可重用的邏輯部件前计,開發(fā)人員可以利用這些部件來跨越多種不同類型的應(yīng)用程序。 從技術(shù)上講垃杖,Nest 可以在創(chuàng)建適配器 后使用任何 Node HTTP 框架男杈。 有兩個支持開箱即用的 HTTP 平臺:express 和 fastify。 您 可以選擇最適合您需求的產(chǎn)品调俘。
NestJs 的核心思想:就是提供了一個層與層直接的耦合度極小,抽象化極高的一個架構(gòu) 體系伶棒。
官網(wǎng):https://nestjs.com/
中文網(wǎng)站:https://docs.nestjs.cn/
GitHub: https://github.com/nestjs/nest
圖一只是說明規(guī)范的模塊方式,實際上彩库,可以只有根模塊肤无,也可以劃分多個模塊,互相依賴骇钦,只要不是循環(huán)引入就行宛渐。
Nestjs 的特性
- 依賴注入容器
- 模塊化封裝
- 可測試性
- 內(nèi)置支持 TypeScript
- 可基于 Express 或者 fastify
腳手架nest-cli
安裝
npm i -g @nestjs/cli 或者 cnpm i -g @nestjs/cli 或者 yarn global add @nestjs/cli
創(chuàng)建
nest new nestdemo
相關(guān)指令
- nest new 名稱 創(chuàng)建項目
- nest -h/--help 幫助
- nest g co 名稱 創(chuàng)建控制器
- nest g s 名稱 創(chuàng)建服務(wù)
- nest g mi 名稱 創(chuàng)建中間件
- nest g pi 名稱 創(chuàng)建管道
- nest g mo 名稱 創(chuàng)建模塊
- nest g gu 名稱 創(chuàng)建守衛(wèi)
創(chuàng)建類型指令都可以指定文件路徑,而且路徑全部是再src目錄下面,例如:
nest g co /aaa/bbb/user 則在src下面就會存在一個三級目錄窥翩,user的目錄下
有一個以user命名大寫的控制器 UserController.ts文件
注意:凡是以腳手架創(chuàng)建的模塊业岁,控制器等等都會自動添加到對應(yīng)配置位置,不需要手動配置
控制器
Nest 中的控制器層負(fù)責(zé)處理傳入的請求, 并返回對客戶端的響應(yīng)寇蚊。
import { Controller, Get } from '@nestjs/common';
@Controller('article')
export class ArticleController {
@Get()
index(): string {
return '這是 article 里面的 index';
}
@Get('add')
add(): string {
return '這是 article 里面的 index';
}
}
關(guān)于 nest 的 return: 當(dāng)請求處理程序返回 JavaScript 對象或數(shù)組時笔时,它將自動序列化為 JSON。但是仗岸,當(dāng)它返回一個字符串時允耿,Nest 將只發(fā)送一個字符串而不是序列化它。這使響應(yīng)處理變得簡單:只需要返回值扒怖,Nest 負(fù)責(zé)其余部分较锡。
Get Post通過方法參數(shù)裝飾器獲取傳值
- 基本栗子
nestjs 內(nèi)置裝飾器的時候必須得在@nestjs/common 模塊下面引入對應(yīng)的裝飾器
import { Controller, Get, Post } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Post()
create(): string {
return 'This action adds a new cat';
}
@Get()
findAll(): string {
return 'This action returns all cats';
}
}
Nestjs 也提供了其他 HTTP 請求方法的裝飾器 @Put() 、@Delete()姚垃、@Patch()念链、 @Options()、 @Head()和 @All()
- Nest中獲取請求參數(shù)
在 Nestjs 中獲取 Get 傳值或者 Post 提交的數(shù)據(jù)的話我們可以使用 Nestjs 中的裝飾器來獲取
@Request() req
@Response() 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]
import { Controller, Get, Post,Query,Body } from '@nestjs/common';
@Controller('news')
export class NewsController {
@Get()
getAbout(@Query() query): string {
console.log(query);
//這里獲取的就是所有的 Get 傳值
return '這是 about'
}
//針對參數(shù)是 localhost:3000/news/list?id=zq&age=12
@Get('list')
getNews(@Query('id') id):string {
console.log(id);
//這里獲取的就是 Get 傳值里面的 Id 的值
//如果@Query()則是整個id=zq&age=12的對象
return '這是新聞'
}
@Post('doAdd')
async addNews(@Body() newsData){
console.log(newsData);
return '增加新聞’'
}
}
- 動態(tài)路由
// 針對的參數(shù)是 /name/id這種類型积糯,例如/name/1
@Get(':id')
findOne(@Param() params): string {
console.log(params.id);
return `This action returns a #${params.id} cat`;
}
補充: @Param() 裝飾器訪問以這種方式聲明的路由參數(shù)掂墓,該裝飾器應(yīng)添 加到函數(shù)簽名中。@Param可以用在get或者post看成,但是都是針對 localhost:3000/news/list?id=zq&age=12這種才可以獲取君编,而針對body內(nèi)部都是獲取不到的,可以使用@Body
- 綜合案例
@Controller('news')
export class NewsController {
//依賴注入
constructor(private readonly newsService:NewsService){}
@Get('pip')
@UsePipes(new NewsPipe(useSchema))
indexPip(@Query() info){
// console.log(info);
return info;
}
@Get()
@Render('default/news')
index(){
return {
newsList:this.newsService.findAll()
}
}
/**
* 路由順序:如果此時訪問http://localhost:3000/news/add
* 則會正確執(zhí)行川慌,如果把add移動到:id下面吃嘿,則只會執(zhí)行:id的
*/
@Get('add')
addData(@Query('id') id){
return id+'------';
}
//同理,這個模糊匹配如果移動到:id下面梦重,訪問http://localhost:3000/news/aaa
//也會只匹配:id的路由
@Get('a*a')
indexA(){
return '模糊匹配';
}
//動態(tài)路由 /add/1
@Get(':id')
indexB(@Param('id') id){
return id;
}
}
Swagger集成
中文文檔地址:https://docs.nestjs.cn/6/recipes?id=openapi-swagger
- 安裝
npm install --save @nestjs/swagger swagger-ui-express
如果你正在使用fastify兑燥,你必須安裝 fastify-swagger 而不是 swagger-ui-express
npm install --save @nestjs/swagger fastify-swagger
- 引導(dǎo)
- main.ts
- 引入:import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
- 編碼
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; async function bootstrap() { const app = await NestFactory.create(AppModule); const options = new DocumentBuilder() .setTitle('Cats example') .setDescription('The cats API description') .setVersion('1.0') //下面兩者結(jié)合組成請求基本路徑 .setHost('http://www.baidu.com') .setBasePath('/api') // .addTag('cats') 分類 .build(); const document = SwaggerModule.createDocument(app, options); //指定文檔路徑 SwaggerModule.setup('api-docs', app, document); await app.listen(3000); } bootstrap();
- 打開http://localhost:3000/api-docs/#/,如下圖二
圖二.png
swagger基本使用
- 創(chuàng)建Dto
import { ApiModelProperty } from '@nestjs/swagger';
export class CreatePostDto{
@ApiModelProperty({description:"應(yīng)用名稱",example:'示例值'})
title:string
@ApiModelProperty({description:"應(yīng)用內(nèi)容"})
content:string
}
- @ApiModelProperty() 裝飾器接受選項對象
export const ApiModelProperty: (metadata?: {
description?: string;
required?: boolean; //代表是否必須存在該參數(shù)
type?: any;
isArray?: boolean;
collectionFormat?: string;
default?: any;
enum?: SwaggerEnumType;
format?: string;
multipleOf?: number;
maximum?: number;
exclusiveMaximum?: number;
minimum?: number;
exclusiveMinimum?: number;
maxLength?: number;
minLength?: number;
pattern?: string;
maxItems?: number;
minItems?: number;
uniqueItems?: boolean;
maxProperties?: number;
minProperties?: number;
readOnly?: boolean;
xml?: any;
example?: any;
}) => PropertyDecorator;
- 完整例子
只是入門琴拧,更多例子是使用規(guī)則查看文檔
import { Controller, Get, Post, Body, Query, Param } from '@nestjs/common';
import { AppService } from './app.service';
import { ApiUseTags, ApiOperation, ApiModelProperty } from '@nestjs/swagger';
class CreatePostDto{
//默認(rèn)required都是true降瞳,Model上面會有個紅色星號,代表必須填寫蚓胸,
//但是實際上swagger本身不會限制挣饥,只是告知作用,swagger本身請求正常
@ApiModelProperty({description:"應(yīng)用名稱",example:'示例值',maxLength:1,required:false})
title:string
@ApiModelProperty({description:"應(yīng)用內(nèi)容"})
content:string
}
@Controller('app')
@ApiUseTags('默認(rèn)標(biāo)簽')//其實是大分類
export class AppController {
constructor(private readonly appService: AppService) {}
@Get('hello')
@ApiOperation({title:"顯示hello"}) //api的title描述/注釋
getHello(@Query() query,@Param() params): any[] {
return this.appService.getHello();
}
@Post('create')
@ApiOperation({title:"創(chuàng)建應(yīng)用"})
createApp(@Body() body:CreatePostDto): CreatePostDto{
return body;
}
@Get(':id')
@ApiOperation({title:'應(yīng)用詳情'})
detail(@Param('id') id:number){
return{
id
}
}
}
總結(jié):swagger本身注解只是告知作用沛膳,不同于graphql扔枫,例如required本身是沒什么作用只是告知使用者需要填寫,但是實際上需要與否還是程序控制锹安;同理短荐,不論填寫不填寫swagger都會進行請求倚舀,最終結(jié)果以邏輯控制為準(zhǔn)。
參數(shù)驗證
- 安裝
npm i class-validator class-transformer --save
- 啟用全局管道
- main.ts中
app.useGlobalPipes(new ValidationPipe())
- 導(dǎo)包
- 在需要使用參數(shù)校驗的文件內(nèi)導(dǎo)入
import { IsNotEmpty } from 'class-validator'
class CreatePostDto{
@ApiModelProperty({description:"應(yīng)用名稱",example:'示例值'})
//**此處就是**
@IsNotEmpty({message:'我是沒填寫title屬性的時候搓侄,返回的給前端的錯誤信息'})
title:string
@ApiModelProperty({description:"應(yīng)用內(nèi)容"})
content:string
}
說明: Nest 自帶兩個開箱即用的管道瞄桨,即 ==ValidationPipe== 和 ==ParseIntPipe==
補充:ParseIntPipe簡單使用
可把id自動轉(zhuǎn)換成Int類型
@Get(':id')
async findOne(@Param('id', new ParseIntPipe()) id) {
return await this.catsService.findOne(id);
}
總結(jié): 內(nèi)置管道都支持全局话速,方法讶踪,參數(shù)三種級別的使用
文檔連接
靜態(tài)資源
官方文檔:https://docs.nestjs.com/techniques/mvc
app.useStaticAssets('public');
- 栗子
async function bootstrap() {
//指定平臺
const app = await NestFactory.create<NestExpressApplication>(AppModule);
//配置靜態(tài)資源
//app.useStaticAssets(join(__dirname,'..','public'))
//上面是直接訪問http://localhost:3000/a.png
//下面可以設(shè)置虛擬目錄http://localhost:3000/public/a.png
// app.useStaticAssets(join(__dirname,'..','public'),{
// prefix:'/public/'
// })
//如下方式也可以,因為默認(rèn)nest會去尋找根目錄下面的參數(shù)一文件夾
app.useStaticAssets('public',{
prefix:'/public/'
})
await app.listen(3000);
}
注意:NestFactory.create<NestExpressApplication>(AppModule);指定了范型其實就是Nest的平臺泊交,默認(rèn)使用的是express的平臺乳讥,因為靜態(tài)資源涉及平臺的選擇所以必須指定了。
模板引擎
官方文檔:https://docs.nestjs.com/techniques/mvc
- 安裝
cnpm i ejs --save
- 配置
app.setBaseViewsDir(join(__dirname, '..', 'views')) // 放視圖的文件
app.setViewEngine('ejs');
- 完整代碼
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
//靜態(tài)資源中間件依賴于具體平臺廓俭,所以可以先引入express
import { NestExpressApplication } from '@nestjs/platform-express';
// import { join } from 'path';
//此時下面如果使用path則就是path.join
// import * as path from 'path';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.useStaticAssets('public',{
prefix:'/public/'
})
//配置模板引擎,需要先安裝模板引擎
// app.setBaseViewsDir(join(__dirname,'..','views'))
app.setBaseViewsDir('views');
app.setViewEngine('ejs');
await app.listen(3000);
}
bootstrap();
==注意此處引入path的方式==
- 渲染頁面
@Controller('user')
export class UserController {
@Get()
@Render('default/user')
index(){
//注意一般有render的路由則return值都是給模板引擎使用的
//所以nest會判斷云石,一般都是對象,返回字符串會報錯
// return '用戶中心';
//此處是字符串key還是直接命名都可以被ejs搜索到
// return {"name":"zs",age:12}
}
}
說明:default指的是views下面的default文件夾內(nèi)部的user模板
重定向
import { Controller, Get, Post, Body,Response, Render} from '@nestjs/common';
@Controller('user')
export class UserController {
@Get()
@Render('default/user')
index(){
return {"name":"張三"};
}
@Post('doAdd')
doAdd(@Body() body,@Response() res){
console.log(body);
res.redirect('/user'); //路由跳轉(zhuǎn)
}
}
提供者
幾乎所有的東西都可以被認(rèn)為是提供者 - service, repository, factory, helper 等等研乒。他們都可以通過 constructor注入依賴關(guān)系汹忠,也就是說,他們可以創(chuàng)建各種關(guān)系雹熬。但事實上宽菜,提供者不過是一個用@Injectable() 裝飾器注解的類。
export interface Cat {
name: string;
age: number;
breed: string;
}
import { Controller, Get, Post, Body } from '@nestjs/common';
import { CreateCatDto } from './dto/create-cat.dto';
import { CatsService } from './cats.service';
@Controller('cats')
export class CatsController {
//依賴注入
constructor(private readonly catsService: CatsService) {}
@Post()
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
@Get()
async findAll(): Promise<Cat[]> {
return this.catsService.findAll();
}
}
cookie
- 安裝
cnpm instlal cookie-parser --save
- 在 main.ts 中引入 cookie-parser
import * as cookieParser from 'cookie-parser'
- 在 main.ts 配置中間件
app.use(cookieParser());
- 設(shè)置 cookie
res.cookie("name",'zhangsan',{maxAge: 900000, httpOnly: true});
- 獲取 Cookies
@Get('getCookies')
getCookies(@Request() req){
return req.cookies.name;
}
cookie參數(shù)說明
屬性 | 說明 |
---|---|
domain | 域名 |
expires | 過 期 時 間 ( 秒 ) 竿报, 在 設(shè) 置 的 某 個 時 間 點 后 該 Cookie 就 會 失 效 铅乡, 如 expires=Wednesday, 09-Nov-99 23:12:40 GMT |
maxAge | 最大失效時間(毫秒),設(shè)置在多少后失效 |
secure | 當(dāng) secure 值為 true 時烈菌,cookie 在 HTTP 中是無效阵幸,在 HTTPS 中才有效 |
path | 表示 cookie 影響到的路,如 path=/芽世。如果路徑不能匹配時挚赊,瀏覽器則不發(fā)送這 個 Cookie |
httpOnly | 是微軟對 COOKIE 做的擴展。如果在 COOKIE 中設(shè)置了“httpOnly”屬性济瓢,則通 過程序(JS 腳本荠割、applet 等)將無法讀取到 COOKIE 信息,防止 XSS 攻擊產(chǎn)生 |
signed | 表 示 是 否 簽 名 cookie, 設(shè) 為 true 會 對 這 個 cookie 簽 名 葬荷, 這 樣 就 需 要 用 res.signedCookies 而不是 res.cookies 訪問它涨共。被篡改的簽名 cookie 會被服務(wù)器拒絕,并且 cookie 值會重置為它的原始值宠漩,說白了==加密== |
相關(guān)代碼
- 設(shè)置 cookie
res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })
res.cookie('name', 'tobi', { domain: '.example.com', path: '/admin', secure: true });
res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
- 獲取cookie
req.cookies.name
- 刪除 cookie
res.cookie('rememberme', '', { expires: new Date(0)});
res.cookie('username','zhangsan',{domain:'.ccc.com',maxAge:0,httpOnly:true});
加密cookie
- 配置中間件的時候需要傳參
app.use(cookieParser('123456'));
- 設(shè)置 cookie 的時候配置 signed 屬性
res.cookie('userinfo','hahaha',{domain:'.ccc.com',maxAge:900000,httpOnly:true,signed:true});
- signedCookies 調(diào)用設(shè)置的 cookie
console.log(req.signedCookies);
說明:加密的cookie使用3這種方式獲取
session
- 安裝
cnpm install express-session --save
- 導(dǎo)入
import * as session from 'express-session';
- 設(shè)置中間價
app.use(session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }}))
- 使用
設(shè)置值 req.session.username = "張三";
獲取值 req.session.username
- 常用參數(shù)
app.use(session({
secret: '12345',
name: 'name',
cookie: {maxAge: 60000},
resave: false,
saveUninitialized: true
}));
- 參數(shù)說明
屬性 | 說明 |
---|---|
secret | 一個 String 類型的字符串举反,作為服務(wù)器端生成 session 的簽名 |
name | 返回客戶端的 key 的名稱,默認(rèn)為 connect.sid,也可以自己設(shè)置 |
resave | 強制保存 session 即使它并沒有變化,扒吁。默認(rèn)為 true火鼻。建議設(shè)置成 |
saveUninitialized | 強制將未初始化的 session 存儲室囊。當(dāng)新建了一個 session 且未設(shè)定屬性或值時,它就處于 未初始化狀態(tài)魁索。在設(shè)定一個 cookie 前融撞,這對于登陸驗證,減輕服務(wù)端存儲壓力粗蔚,權(quán)限控制是有幫助的尝偎。(默 認(rèn):true)悲柱。建議手動添加洽沟。 |
cookie | 設(shè)置返回到前端 key 的屬性罗珍,默認(rèn)值為{ path: ‘/’, httpOnly: true, secure: false, maxAge: null }慨亲。 |
rolling | 在每次請求時強行設(shè)置 cookie瞻赶,這將重置 cookie 過期時間(默認(rèn):false) |
- 常用方法
req.session.destroy(function(err) { /*銷毀 session*/ })
req.session.username='張三'; //設(shè)置
session req.session.username //獲取
session req.session.cookie.maxAge=0; //重新設(shè)置 cookie 的過期時間
上傳
官方文檔:https://docs.nestjs.com/techniques/file-upload
單文件上傳
import { Controller, Get, Render, Post, Body, UseInterceptors, UploadedFile } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { createWriteStream } from 'fs';
import { join } from 'path';
@Controller('upload')
export class UploadController {
@Post('doAdd')
@UseInterceptors(FileInterceptor('pic')) //pic對應(yīng) <input type="file" name="pic" id="">
doAdd(@Body() body,@UploadedFile() file){
// console.log(body);
// console.log(file);
let cws=createWriteStream(join(__dirname,'../../public/upload/',`${Date.now()}---${file.originalname}`))
cws.write(file.buffer);
return '上傳圖片成功';
}
}
<form action="upload/doAdd" method="post" enctype="multipart/form-data">
<input type="text" name="title" placeholder="新聞標(biāo)題">
<br>
<br>
<input type="file" name="pic" id="">
<br>
<input type="submit" value="提交">
</form>
說明:注意enctype="multipart/form-data"屬性必須添加
多文件上傳
import { Controller, UseInterceptors, Get, Post, Render, Body, UploadedFiles } from '@nestjs/common';
import { createWriteStream } from 'fs';
import { FilesInterceptor } from '@nestjs/platform-express';
import { join } from 'path';
@Controller('uploadmany')
export class UploadmanyController {
@Post('doAdd')
//注意此處是FileFieldsInterceptor代表多文件的name不同的攔截器
// @UseInterceptors(FileFieldsInterceptor([
// { name: 'pic1', maxCount: 1 },
// { name: 'pic2', maxCount: 1 }
// ]))
//注意此處是FilesInterceptor而上面是FileFieldsInterceptor
@UseInterceptors(FilesInterceptor('pic')) //多個文件name屬性相同的情況下
doAdd(@Body() body, @UploadedFiles() files) {
for (const file of files) {
let cws = createWriteStream(join(__dirname, '../../public/upload/', `${Date.now()}---${file.originalname}`))
cws.write(file.buffer);
}
return '上傳多個文件成功';
}
}
注意:此處html中的name相同和不同使用的處理方式不同
<!-- 注意此處上傳文件的enctype -->
<form action="uploadmany/doAdd" method="post" enctype="multipart/form-data">
<input type="text" name="title" placeholder="新聞標(biāo)題">
<br>
<br>
<input type="file" name="pic" id="">
<input type="file" name="pic" id="">
<br>
<input type="submit" value="提交">
</form>
中間價
中間件就是匹配路由之前或者匹配路由完成做的一系列的操作鼻种。中間件中如果想往下 匹配的話椎木,需要寫 next()
中間件任務(wù)
- 執(zhí)行任何代碼镰绎。
- 對請求和響應(yīng)對象進行更改缘揪。
- 結(jié)束請求-響應(yīng)周期耍群。
- 調(diào)用堆棧中的下一個中間件函數(shù)。
- 如果當(dāng)前的中間件函數(shù)沒有結(jié)束請求-響應(yīng)周期, 它必須調(diào)用 next() 將控制傳遞給下一個中間 件函數(shù)找筝。否則, 請求將被掛起蹈垢。
Nest 中間件可以是一個函數(shù),也可以是一個帶有@Injectable()裝飾器的類
使用中間件
- 創(chuàng)建
nest g middleware init
import { Injectable, NestMiddleware } from '@nestjs/common';
@Injectable()
export class InitMiddleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
console.log(Date());
next();
}
}
- 配置中間件
在 app.module.ts 中繼承 NestModule 然后配置中間件
export class AppModule implements NestModule{
configure(consumer:MiddlewareConsumer) {
/**
* 中間件相關(guān)
*/
//寫*表示匹配所有路由
// consumer.apply(InitMiddleware).forRoutes('*');
//匹配指定路由
// consumer.apply(InitMiddleware).forRoutes('news');
//直接傳入控制器:不推薦
// consumer.apply(InitMiddleware).forRoutes(NewsController);
// consumer.apply(InitMiddleware).forRoutes({path:'ab*cd',method:RequestMethod.ALL});
//所有路由都匹配InitMiddleware但是UserMiddleware不光匹配InitMiddleware
//還匹配UserMiddleware
// consumer.apply(InitMiddleware,logger).forRoutes('*')
// .apply(UserMiddleware).forRoutes('user')
//都可以添加多個:代表user/news都可以匹配InitMiddleware,UserMiddleware
// consumer.apply(InitMiddleware,UserMiddleware).
// forRoutes({path:'user',method:RequestMethod.ALL},{path:'news',method:RequestMethod.ALL})
}
}
- 多個中間件
consumer.apply(cors(), helmet(), logger).forRoutes(CatsController);
- 函數(shù)式中間件
export function logger(req, res, next) {
console.log(`Request...`);
next();
};
- 全局中間件
//全局中間件只能引入函數(shù)式中間件呻征,引入類中間件會報錯
import { logger} from './middleware/logger.middleware'
const app = await NestFactory.create(ApplicationModule);
app.use(logger);
await app.listen(3000);
全局中間件只能使用函數(shù)式中間件
管道
Nestjs 中的管道可以將輸入數(shù)據(jù)轉(zhuǎn)換為所需的輸出耘婚。此外,它也可以處理驗證陆赋, 當(dāng)數(shù)據(jù)不正確時可能會拋出異常沐祷。
- 創(chuàng)建
nest g pipe news
import { ArgumentMetadata, Injectable, PipeTransform, BadRequestException, HttpStatus } from '@nestjs/common';
import * as Joi from '@hapi/joi';
@Injectable()
export class NewsPipe implements PipeTransform {
// constructor(private readonly schema:Joi.Schema){
// }
constructor(private readonly schema:Joi.Schema){}
transform(value: any, metadata: ArgumentMetadata) {
// console.log(value); //value一般都是get/post等請求傳遞過來的值
// value.age=30; 如果這樣修改之后,控制器里面的數(shù)據(jù)就變了
const {error}=this.schema.validate(value);
if (error) {
// throw new BadRequestException('Validate failed')
return error;
}
return value;
}
}
- 使用
import { Controller,Get, Param, Query, Render, UsePipes } from '@nestjs/common';
import { NewsService } from './news.service';
import { NewsPipe } from '../pipe/news.pipe';
import * as Joi from '@hapi/joi';
let useSchema:Joi.Schema=Joi.object().keys({
name:Joi.string().required(),
age:Joi.number().integer().min(6).max(66).required()
});
@Controller('news')
export class NewsController {
//依賴注入
constructor(private readonly newsService:NewsService){}
@Get('pip')
@UsePipes(new NewsPipe(useSchema))
indexPip(@Query() info){
// console.log(info);
return info;
}
@Get()
@Render('default/news')
index(){
return {
newsList:this.newsService.findAll()
}
}
/**
* 路由順序:如果此時訪問http://localhost:3000/news/add
* 則會正確執(zhí)行攒岛,如果把add移動到:id下面赖临,則只會執(zhí)行:id的
*/
@Get('add')
addData(@Query('id') id){
return id+'------';
}
//同理,這個模糊匹配如果移動到:id下面灾锯,訪問http://localhost:3000/news/aaa
//也會只匹配:id的路由
@Get('a*a')
indexA(){
return '模糊匹配';
}
//動態(tài)路由 /add/1
@Get(':id')
indexB(@Param('id') id){
return id;
}
}
安裝joi: cnpm i @hapi/joi --save
模塊
模塊是具有 @Module() 裝飾器的類兢榨。 @Module() 裝飾器提供了元數(shù)據(jù),Nest 用它來組織應(yīng)用 程序結(jié)構(gòu)顺饮。
每個 Nest 應(yīng)用程序至少有一個模塊吵聪,即根模塊。根模塊是 Nest 開始安排應(yīng)用程序樹的地方兼雄。事實上吟逝,根模塊可能是應(yīng)用程序中唯一的模塊,特別是當(dāng)應(yīng)用程序很小時赦肋,但是對于大型程序來說這是沒有意義的块攒。在大多數(shù)情況下励稳,您將擁有多個模塊,每個模塊都有一組緊密 相關(guān)的功能囱井。
@module() 裝飾器接受一個描述模塊屬性的對象
屬性 | 描述 |
---|---|
providers | 由 Nest 注入器實例化的提供者驹尼,并且可以至少在整個模塊中共享 |
controllers | 必須創(chuàng)建的一組控制器 |
imports | 導(dǎo)入模塊的列表,這些模塊導(dǎo)出了此模塊中 所需提供者庞呕,注意導(dǎo)入的是模塊 |
exports | 由本模塊提供并應(yīng)在其他模塊中可用的提供 者的子集 |
模塊共享
- 共享模塊:只是一個普通模塊
import { Module } from '@nestjs/common';
import { BaseService } from './service/base/base.service';
@Module({
providers: [BaseService],
exports:[BaseService]
})
export class ShareModule {}
總結(jié):其實就是module內(nèi)部exports的東西新翎,此時是一個基礎(chǔ)服務(wù)類(可以是很多),只要導(dǎo)出了千扶,其他模塊只要導(dǎo)入了該模塊料祠,則該模塊的所有exports的都可以通過依賴注入方式直接使用,而不需要一個個導(dǎo)入service或者provider澎羞,然后放到想使用的模塊的provider中。
- 使用共享模塊
import { Module } from '@nestjs/common';
import { UserController } from './controller/user/user.controller';
import { NewsService } from './service/news/news.service';
import { ShareModule } from '../share/share.module';
@Module({
imports:[ShareModule],
controllers: [UserController],
providers: [NewsService],
})
export class AdminModule {
}
此時新板塊導(dǎo)入了module敛苇,沒有導(dǎo)入具體外部service例如上面的BaseService妆绞,但是可以依賴注入在現(xiàn)在的模塊中直接使用。
- 使用共享模塊導(dǎo)出的service
import { Controller, Get } from '@nestjs/common';
import {BaseService} from '../../../share/service/base/base.service'
@Controller('admin/user')
export class UserController {
constructor(private readonly baseService:BaseService){}
@Get()
index(){
console.log(this.baseService.getData());
return "哈哈"
}
}
==模塊總結(jié):==由上面可知枫攀,其實所有模塊都可以導(dǎo)入括饶,只要不出現(xiàn)循環(huán)導(dǎo)入就都可以,而模塊的概念来涨,加強了約束图焰,即使import導(dǎo)入了對應(yīng)service,只要沒有在對應(yīng)module導(dǎo)入module蹦掐,則運行就報錯技羔。
守衛(wèi)
文檔: https://docs.nestjs.com/pipes
守衛(wèi)是一個使用 @Injectable() 裝飾器的類。守衛(wèi)應(yīng)該實現(xiàn) CanActivate 接口卧抗。
守衛(wèi)有一個單獨的責(zé)任藤滥。它們確定請求是否應(yīng)該由路由處理程序處理。到目前為止社裆,訪問限 制邏輯大多在中間件內(nèi)拙绊。這樣很好,因為諸如 token 驗證或?qū)?request 對象附加屬性與 特定路由沒有強關(guān)聯(lián)泳秀。但中間件是非常笨的标沪。它不知道調(diào)用 next() 函數(shù)后會執(zhí)行哪個處 理程序。另一方面嗜傅,守衛(wèi)可以訪問 ExecutionContext 對象金句,所以我們確切知道將要執(zhí)行 什么。
==簡單說:==
在 Nextjs 中如果我們想做權(quán)限判斷的話可以在守衛(wèi)中完成磺陡,也可以在中間件中完成趴梢,但是一般通過守衛(wèi)最好漠畜。
- 創(chuàng)建
nest g gu auth
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
// let n=Math.random();
// if (n>0.3) {
// return false;
// }
//守衛(wèi)返回false則無法訪問路由,true可以
//判斷cookie或者session
//其實context.switchToHttp().getRequest();就相當(dāng)于express的req坞靶,所以什么path屬性等等都有
// const {cookies,session}=context.switchToHttp().getRequest();
// console.log(cookies,session);
return true;
}
}
- 使用守衛(wèi)
@Get('guard')
@UseGuards(AuthGuard)
guard(@Query() info){
console.log(info);
return `this is guard`;
}
### 也可以直接加在控制器上面
@Controller('cats')
@UseGuards(RolesGuard)
export class CatsController {}
### 全局使用守衛(wèi)
app.useGlobalGuards(new AuthGuard());
- 模塊上使用守衛(wèi)
import {Module} from '@nestjs/common';
import {APP_GUARD} from '@nestjs/core';
@Module({
providers:[
{
provide:APP_GUARD,
useClass:RolesGuard
}
]
})
export class AppModule{}
攔截器
-
作用
- 在函數(shù)執(zhí)行之前/之后綁定額外的邏輯
- 轉(zhuǎn)換從函數(shù)返回的結(jié)果
- 轉(zhuǎn)換從函數(shù)拋出的異常
- 擴展基本函數(shù)行為
- 根據(jù)所選條件完全重寫函數(shù) (例如, 緩存目的)
創(chuàng)建
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('Before...');
const now = Date.now();
return next
.handle()
.pipe(
tap(() => console.log(`After... ${Date.now() - now}ms`)),
);
}
}
由于 handle() 返回一個RxJS Observable
憔狞,我們有很多種操作符可以用來操作流。在上面的例子中彰阴,我們使用了 tap() 運算符瘾敢,該運算符在可觀察序列的正常或異常終止時調(diào)用函數(shù)尿这。
- 使用
@UseInterceptors(LoggingInterceptor)
export class CatsController {}
此處使用LoggingInterceptor簇抵,初始化控制反轉(zhuǎn)交給框架本身,雖然可以傳入new的實例射众,但是不建議
- 全局綁定
const app = await NestFactory.create(ApplicationModule);
app.useGlobalInterceptors(new LoggingInterceptor());
- 模塊綁定
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_INTERCEPTOR,
useClass: LoggingInterceptor,
},
],
})
export class ApplicationModule {}
執(zhí)行順序
過濾器詳情其實就是異常過濾器碟摆,整個流程不見得一定會指定,但是出現(xiàn)異常時候會進入叨橱,詳情查看官方文檔
graph LR
客戶端請求-->中間件
中間件-->守衛(wèi)
守衛(wèi) -->攔截器之前
攔截器之前 -->管道
管道--> 控制器處理并響應(yīng)
控制器處理并響應(yīng) -->攔截器之后
攔截器之后 -->過濾器
- 接收客戶端發(fā)起請求
- 中間件去做請求處理典蜕,比如helmet,csrf罗洗,rate limiting愉舔,compression等等常用的處理請求的中間件。
- 守衛(wèi)就驗證該用戶的身份伙菜,如果沒有權(quán)限或者沒有登錄轩缤,就直接拋出異常,最適合做權(quán)限管理贩绕。
- 攔截器根據(jù)作者解釋火的,攔截器之前不能修改請求信息。只能獲取請求信息丧叽。
- 管道做請求的數(shù)據(jù)驗證和轉(zhuǎn)化卫玖,如果驗證失敗拋出異常。
- 這里處理響應(yīng)請求的業(yè)務(wù)踊淳,俗稱controller假瞬,處理請求和服務(wù)橋梁,直接響應(yīng)服務(wù)處理結(jié)果迂尝。
- 攔截器之后只能修改響應(yīng)body數(shù)據(jù)脱茉。
- 最后走過濾器:如果前面任何位置發(fā)生拋出異常操作,都會直接走它垄开。
總結(jié)
模塊是按業(yè)務(wù)邏輯劃分基本單元琴许,包含控制器和服務(wù)「榷悖控制器是處理請求和響應(yīng)數(shù)據(jù)的部件榜田,服務(wù)處理實際業(yè)務(wù)邏輯的部件益兄。
中間件是路由處理Handler前的數(shù)據(jù)處理層,只能在模塊或者全局注冊箭券,可以做日志處理中間件净捅、用戶認(rèn)證中間件等處理,中間件和express的中間件一樣辩块,所以可以訪問整個request蛔六、response的上下文,模塊作用域可以依賴注入服務(wù)废亭。全局注冊只能是一個純函數(shù)或者一個高階函數(shù)国章。
管道是數(shù)據(jù)流處理,在中間件后路由處理前做數(shù)據(jù)處理豆村,可以控制器中的類液兽、方法、方法參數(shù)你画、全局注冊使用抵碟,只能是一個純函數(shù)』捣耍可以做數(shù)據(jù)驗證,數(shù)據(jù)轉(zhuǎn)換等數(shù)據(jù)處理撬统。
守衛(wèi)是決定請求是否可以到達對應(yīng)的路由處理器适滓,能夠知道當(dāng)前路由的執(zhí)行上下文,可以控制器中的類恋追、方法凭迹、全局注冊使用,可以做角色守衛(wèi)苦囱。
攔截器是進入控制器之前和之后處理相關(guān)邏輯嗅绸,能夠知道當(dāng)前路由的執(zhí)行上下文,可以控制器中的類撕彤、方法鱼鸠、全局注冊使用,可以做日志羹铅、事務(wù)處理蚀狰、異常處理、響應(yīng)數(shù)據(jù)格式等职员。
過濾器是捕獲錯誤信息麻蹋,返回響應(yīng)給客戶端『盖校可以控制器中的類扮授、方法芳室、全局注冊使用,可以做自定義響應(yīng)異常格式刹勃。
中間件堪侯、過濾器、管道深夯、守衛(wèi)抖格、攔截器,這是幾個比較容易混淆的東西咕晋。他們有個共同點都是和控制器掛鉤的中間抽象處理層雹拄,但是他們的職責(zé)卻不一樣。
NestJS很多提供的內(nèi)部功能掌呜,看似功能重復(fù)滓玖,但是實際上有明確的職責(zé)劃分,按照約定來质蕉,能使結(jié)構(gòu)清晰势篡,代碼更好維護