1、前期准備工作
環境配置參考node里的nodemon或者webpack
在學習nest.js前需要了解它的反映機制 Reflect.defineMetadata, 學習這個需要安裝和引入庫‘reflect-metadata’這個庫
npm i reflect-metadata --save
import 'reflect-metadata' console.log(Reflect.defineMetadata); //可以打印出對應的方法說明安裝成功
Reflect-Metadata的方法介紹
import 'reflect-metadata' let Info = { name: 'aaa', getName() { return this.name }, } //定義在對象上的元數據
Reflect.defineMetadata('info', '這是個對象元數據信息', Info) let msg = Reflect.getMetadata('info', Info) console.log(msg) //定義在對象屬性上的元數據
Reflect.defineMetadata('paramInfo', '這是個對象屬性元數據', Info, 'name') let paramMsg = Reflect.getMetadata('paramInfo', Info, 'name') console.log(paramMsg) //判斷元數據上是否有指定的數據
console.log(Reflect.hasMetadata('info', Info)) // true
console.log(Reflect.hasMetadata('paramInfo', Info, 'getName')) // false
//如果子類繼承了父類原型鏈上的方法,那么同時也會繼承原型鏈上的metadata,這個時候就可以使用hasOwnMetadata來判斷是否是自有的屬性
console.log(Reflect.hasOwnMetadata('info', Info))
注意:使用defineMetadata不會影響原數據本身,只會增加元數據
Reflect中類中的使用
import 'reflect-metadata' @Reflect.metadata('name', 'metaData_className') //這個是直接定義,相當於defineMetadata
class Test { @Reflect.metadata('name', 'metaData_className_state') public state?: string @Reflect.metadata('name', 'metaData_className_method') public check(param: string): void {} } console.log(Reflect.getMetadata('name', Test)) console.log(Reflect.getMetadata('name', Test.prototype, 'state')) console.log(Reflect.getMetadata('name', Test.prototype, 'check')) //獲取所有的原型上的key值
console.log(Reflect.getMetadataKeys(Test.prototype)) //獲取原型上指定屬性的key值
console.log(Reflect.getMetadataKeys(Test.prototype, 'state')) // 注意: 特殊類型 // design:type 表示屬性的聲明的類型 // design:paramtypes 表示函數參數的聲明類型,返回的是一個數組 // design:returntype 表示函數的返回類型
注意:Reflect.metadata如何修復的是屬性或者類里的方法,那么相當對應的屬性值是掛載在prototype原型上
可以通過自己寫裝飾器,將這個東西封裝入自己封裝的裝飾器中
import 'reflect-metadata' const classMetadata = <T>(name: string, value: string|number|boolean) => { return (classT: T) => { Reflect.defineMetadata(name, value, classT) } } const methodMetadata = <T>(name: string, value: string|number|boolean) => { return (classT: T, methodT: string|symbol) => { Reflect.defineMetadata(name, value, classT, methodT) } } @classMetadata('name', 'ven') class Test { @methodMetadata('name', 'state') public state: string @methodMetadata('name', 'method') public check(param: string): void { } } console.log(Reflect.getMetadata('name', Test)) console.log(Reflect.getMetadata('name', Test.prototype, 'state')) console.log(Reflect.getMetadata('name', Test.prototype, 'check'))
依賴注入小試
import 'reflect-metadata' class Logger { public constructor() { console.log('this is logger') } public getLog() {} } // 使用依賴注入,實例化聲明屬性的類 function Inject(injectId: string): PropertyDecorator { return function (targetClassPrototype, propName) { let PropClass = Reflect.getMetadata('design:type', targetClassPrototype, propName) let PropClassObj = new PropClass() console.log(PropClassObj) } } class Active { @Inject('Logger') public log?: Logger public getPeo() { return this.log } } //使用依賴注冊實例化構造器參數中聲明的類 function InjectContructor(injectId?: string): ParameterDecorator { return function (targetClassPrototype, propertyKey, parameterIndex) { let propClass = Reflect.getMetadata('design:paramtypes', targetClassPrototype) let loggerClass = new propClass[parameterIndex]() } } class Person { public constructor(@InjectContructor('Logger') private logger: Logger, private count: number) {} }
注意:以上方法的使用需要建立在tsconfig.json中target:es5的基礎上,否則獲取不到相關的值
2、nest框架的安裝
$ npm i -g @nestjs/cli $ nest new project-name
3、nest結構介紹
在nest.js中路徑是指向根目錄下,如 src/controllers表示的是根目錄下的controllers文件夾
4、controller的使用
controller的創建,可以使用命令:
nest g controller 模塊名稱
就會獨立生成指定文件的模塊,里面有 模塊名稱.controller.ts 模塊名稱.controller.spec.ts
controller的入門案例
import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() //前綴是/
export class AppController { // 只需要在構造函數里聲明依賴,IOC窗容器會自動幫你注入實例,你直接調用就可以了
constructor(private readonly appService: AppService) {} @Get() //表示的是/的響應, 控制器里面一般只用於接收參數,返回響應,並不會真正處理業務
getHello(): string { return this.appService.getHello(); } @Get('api') // 訪問的/api的路徑進行響應的
getApi(): Array<string> { return ['aa', 'bbb', 'cc'] } }
注意:在使用controller的時候 @controller('prefix')是表示一個控制器的修飾符,prefix表示訪問路徑的前綴,如@controller('user'), 那么@get('/api')時則表示訪問/user/api路徑時的響應, 這里的@get表示發送請求的類型是get
controller方法的響應方式
通常來講,controller里的響應會自動返回200的狀態碼,通常來講,有兩種響應方式
注意:通常來講安裝完整項目,內部已經安裝了@types/express這個模塊了
import { Controller, Get, Req, Res, Query, Ip, HttpStatus, Param, Header, Post, Body, UploadedFile } from '@nestjs/common'; import { AppService } from './app.service'; import { Request, Response } from 'express' @Controller('user') //前綴是/user
export class AppController { // 只需要在構造函數里聲明依賴,IOC窗容器會自動幫你注入實例,你直接調用就可以了
constructor(private readonly appService: AppService) {} @Get() //表示的是/user的響應, 控制器里面一般只用於接收參數,返回響應,並不會真正處理業務
getHello(): string { return this.appService.getHello(); } @Get('api') // 訪問的/user/api的路徑進行響應的
getApi(@Req() request: Request, @Res() response: Response, @Query('age') query: string): any { response.status(HttpStatus.OK).json({ ...request.query, query }) } @Get('ip') // 獲取ip
getIp(@Req() request: Request, @Ip() ip ) { return [request.ip, ip] } @Get('header') // 獲取host相關的信息
@Header('token', '123') getHeader(@Req() request: Request) { return [request.headers, request.header('token')] // 這個是一個對象,如果是request,header則是一個方法
} @Get('param/:id') // 或者使用:id
getParams(@Param('id') id: string) { return id; } @Post('param') getPost(@Req() request: Request, @Body() body, @UploadedFile() files) { return [request.method, request.body, body, files] } }
注意:如果引入了@Res() 那么return 將不起作用,需要用response.json或response.send進行返回,但是對於模板的渲染還是按原有方法操作
注意:路由跳轉在response里面, response.redirect('/test')表示跳轉到路由下
5、配置靜態資源
文檔地址: https://docs.nestjs.com/techniques/mvc
import { NestFactory } from '@nestjs/core'; import { NestExpressApplication } from '@nestjs/platform-express'; import { AppModule } from './app.module'; import {join} from 'path' async function bootstrap() { const app = await NestFactory.create<NestExpressApplication>(AppModule); //寫法一 // app.useStaticAssets('public') //沒有配置虛擬路徑 訪問目錄 http://localhost:3000/資源名稱 //寫法二 app.useStaticAssets('public', { //設置虛擬路徑 那么訪問的時候就需要用 http://localhost:3000/static/資源名稱 prefix: '/static/' }) //寫法三 // app.useStaticAssets(join(__dirname, '..', 'public'),{ // 注意該寫法與上面寫法是一樣的效果 // prefix: '/static/', // });
await app.listen(3000); } bootstrap();
注意:以上是main.ts入口文件,注意__dirname是指在項目下的dist文件,所以在指定路徑的時候,需要注意指向
6、配置模板引擎
在nest.js中使用的是hbs模板引擎,但是這邊使用的是ejs那么需要配置這個模板引擎
npm i ejs --save
把模板引擎配置到入口文件中
app.setBaseViewsDir(join(__dirname, '..', 'views')) // 放視圖的文件
app.setViewEngine('ejs'); // 指定模板引擎
注意:上述代碼指定了視圖的根目錄為views文件夾下,引擎是ejs, 這就需要在根目錄下創建views,渲染的時候會自動讀取views文件夾下的文件
import { Controller, Get, Render } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { ... @Get('test') @Render('test/index') //指定的是views下test文件下的index.ejs文件
public testInfo(): {[key: string]: string|number} { return {name: 'ven', age: 20, img: '/static/6.jpg'} //返回需要渲染的數據
} ... }
具體的ejs的使用可以參看ejs的官方文檔
7、nest.js中的服務
Nestjs 中的服務可以是 service 也可以是 provider。他們都可以通過 constructor 注 入依賴關系。服務本質上就是通過@Injectable() 裝飾器注解的類。在 Nestjs 中服務相 當於 MVC 的 Model。
服務的創建
nest g service 服務名 (推薦) nest g provider 服務名 幫助信息 nest g --help
import { Injectable } from '@nestjs/common'; @Injectable() export class YfService { getAll() { return ['aa', 'bb', 'cc'] } }
注意:系統自動生成服務后,會在app.module.ts中的providers中自動引入 ,如果沒有自動引用,則需要手動引入
import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { YfController } from './yf/yf.controller'; import { AppService } from './app.service'; import { YfService } from './yf/yf.service'; @Module({ imports: [], controllers: [AppController, YfController], providers: [AppService, YfService], }) export class AppModule {}
那么如果在需要在controller中使用,也需要進行聲明
import { Controller, Get } from '@nestjs/common'; import { YfService } from './yf.service'; @Controller('yf') export class YfController { public constructor(private yfservice: YfService) {} @Get() public index() { return this.yfservice.getAll() } }
8、nest中cookie的使用
NestJs 中使用 Cookie 的話我們可以用 cookie-parser來實現 文檔位置: https://docs.nestjs.com/techniques/cookies
安裝
npm i cookie-parser npm i -D @types/cookie-parser
引入到main.ts中
import * as cookieParser from 'cookie-parser'; app.use(cookieParser('加密字符串')); //如果需要加密,則需要配置加密字符串,建議配置,配置后可使用加密狀態也可以不使用加密
controller中的使用
import { Controller, Get, Req, Res } from '@nestjs/common'; import { YfService } from './yf.service'; import { Response, Request } from 'express'; @Controller('yf') export class YfController { public constructor(private yfservice: YfService) {} @Get() public index(@Res() res: Response) { // res.cookie('name', 'this is test name', {maxAge: 90000, httpOnly: true}) =》不加密 //這里設置了過期時間,以及是否只有http訪問 res.cookie('name', 'this is test name', {maxAge: 90000, httpOnly: true, signed: true}) =》 加密 res.send('this is test') } @Get('cookie') public getCookie(@Req() req: Request): string { // return req.cookies.name =》 不加密訪問 // 加密后的cookie訪問 return req.signedCookies.name =》 加密訪問 } }
cookie-parser的參數說明
domain: 域名
expires : 過 期 時 間 ( 秒 ) , 在 設 置 的 某 個 時 間 點 后 該 Cookie 就 會 失 效 , 如 expires=Wednesday, 09-Nov-99 23:12:40 GMT maxAge: 最大失效時間(毫秒),設置在多少后失效
secure: 當 secure 值為 true 時,cookie 在 HTTP 中是無效,在 HTTPS 中才有效
path: 表示 cookie 影響到的路,如 path=/。如果路徑不能匹配時,瀏覽器則不發送這 個 Cookie
httpOnly:是微軟對 COOKIE 做的擴展。如果在 COOKIE 中設置了“httpOnly”屬性,則通 過程序(JS 腳本、applet 等)將無法讀取到 COOKIE 信息,防止 XSS 攻擊產生
signed : 表 示 是 否 簽 名 cookie, 設 為 true 會 對 這 個 cookie 簽 名 , 這 樣 就 需 要 用 res.signedCookies 而不是 res.cookies 訪問它。被篡改的簽名 cookie 會被服務器拒絕,並且 cookie 值會重置為它的原始值
設置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});
9、nest.js debugger調試
https://github.com/nestjs/docs.nestjs.com/issues/217
打開界面
在.vscode下的launch.json里配置如下:
{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "type": "node", "request": "attach", "name": "Attach NestJS WS", "port": 9229, "restart": true, "stopOnEntry": false, "protocol": "inspector" } ] }
在項目路徑下支行npm run start:debug
在vscode中啟動調試界面
這個時候調用chrome里的inspect監聽9229端口
當代碼運行那指定的debugger的地點時就會啟用斷點,打印指定的變量了
同時也可以點擊下面的界面進行調試
10、自定義裝飾器
以設置cookie為例封裝一個獲取cookie的裝飾器
import { createParamDecorator, ExecutionContext } from "@nestjs/common"; import { Request } from "express"; export const Cookies = createParamDecorator((data: string, ctx: ExecutionContext) => { // ctx可以獲取到上下文的request和response const request: Request = ctx.switchToHttp().getRequest(); return data? request.signedCookies?.[data]: request.signedCookies; })
這樣就可以在controller中進行使用了
import { Controller, Get, Res } from '@nestjs/common'; import { Response } from 'express'; import { Cookies } from 'src/utils/decorators'; import { YfService } from './yf.service'; @Controller('yf') export class YfController { public constructor(private yfservice: YfService) {} @Get() public index(@Res() res: Response) { res.cookie('name', 'this is test name', {maxAge: 90000, httpOnly: true, signed: true}) res.send('this is test') } @Get('cookie') public getCookie(@Cookies('name') data): string { return data; } }
裝飾器嵌套
import { applyDecorators } from '@nestjs/common'; export function Auth(...roles: Role[]) { return applyDecorators( SetMetadata('roles', roles), UseGuards(AuthGuard, RolesGuard), ApiBearerAuth(), ApiUnauthorizedResponse({ description: 'Unauthorized' }), ); }
使用
@Get('users') @Auth('admin') findAllUsers() {}