nest.js的服务端的仓库地址: https://github.com/yufengctbu/nest-service.git (目前还在整理中)
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
注意:安装完@nestjs/cli后,创建的模板在一定时间内nest的版本是固定的,如果需要升级nest核心库的版本,就需要升级一下模板的版本使用命令
yarn global add @nestjs/schematics
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'); // 指定模板引擎
//注意: 配置模板的时候, 需要声明create的类型,否则会提示不存在该方法
注意:上述代码指定了视图的根目录为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()
}
}
nestjs中其他三种的服务注入
import { UseFactoryService } from './../../providers/common.service';
import {
UseClassService,
UseValueService,
} from '@app/providers/common.service';
import { Module } from '@nestjs/common';
import { CourseController } from './course.controller';
import { CourseService } from './course.service';
@Module({
controllers: [CourseController],
providers: [
CourseService,
// 方式一, provide对应controller里inject()括号的值,表示的是类的标识,也可以取其他名称, 使用useClass那么就直接传入类名,系统内部进行实例化
{ provide: UseClassService, useClass: UseClassService },
// 方式二 使用useValue, 那么就需要自己进行实例化
{ provide: UseValueService, useValue: new UseValueService() },
// 方式三 使用useFactory,那么传入的是一个回调函数,返回该实例的对象,这时这个回调里的值则是inject里的provider
{
provide: UseFactoryService,
useFactory: (useClassService: UseClassService) => {
useClassService.check();
return new UseFactoryService();
},
inject: [UseClassService],
},
],
})
export class CourseModule {}
对应的服务类
export class UseClassService {
public check() {
console.log('this is use class');
}
}
export class UseValueService {
public check() {
console.log('this is use value');
}
}
export class UseFactoryService {
public check() {
console.log('this is use factory');
}
}
controller的调用
import { UseFactoryService } from './../../providers/common.service';
import {
UseClassService,
UseValueService,
} from '@app/providers/common.service';
import { Controller, Get, Inject } from '@nestjs/common';
@Controller('course')
export class CourseController {
public constructor(
// 注意这里括号时面的标识对应的是provider里的标识,也可以取其他名字,如果名字与类名一样,那么也可以进行省略
@Inject(UseClassService) private commonService: UseClassService,
@Inject(UseValueService) private useValueService: UseValueService,
@Inject(UseFactoryService) private useFactoryService: UseFactoryService,
) {}
@Get()
public index() {
this.commonService.check();
this.useValueService.check();
this.useFactoryService.check();
return 'this is index';
}
}
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的地点时就会启用断点,打印指定的变量了
同时也可以点击下面的界面进行调试
使用vscode进行debug
生成新的launch.json的配置文件
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "node debug", // 可自定义debugger的名字
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}\\dist\\main.js" // 这个需要替换成自己目录下的项目入口文件
}
]
}
通过以上配置就可以进行愉快的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() {}
11、typescript的路径配置
项目的路径配置,可以参看typescript知识点拾遗