NestJS入门和实战

Ready

安装(使用脚手架): $ npm i -g @nestjs/cli
新建一个项目 $ nest new project-name
或者,使用 Git 安装 TypeScript 起始项目
$ git clone https://github.com/nestjs/typescript-starter.git project-name
$ cd project-name
$ npm install
$ npm run start

如果不使用脚手架,使用 npm/yarn/pnpn 手动从头开始创建一个新项目,安装核心和支持包。
$ npm i --save-dev @nestjs/core @nestjs/common rxjs reflect-metadata

  1. @nestjs/core: Nest.js框架的核心包,提供了构建Nest.js应用程序所需的基本功能,包括应用程序实例化、模块加载、路由处理、中间件管理。Controllers、Modules、Dependency Injection、Middleware、Exception Filters、Guards、Interceptors、Pipes、Websocks。
  2. **@nestjs/common: **Nest.js框架的通用模块包,包含了一些常用的装饰器、异常类和工具函数,用于简化开发过程。如, **@Controller装饰器用于定义控制器类,@Injectable装饰器用于定义可注入的服务类,HttpException**异常类用于抛出HTTP异常,等等。
  3. @rxjs:响应式编程库,提供了丰富的操作符和工具函数,用于处理异步数据流。在 Nest.js中,rxjs通常用于处理异步操作,如触发HTTP请求、处理数据库查询等。它提供了方便的方式来处理异步数据流和事件,并且与Nest.js的异步编程模型紧密集成。
  4. reflect-metadata: 用于获取和定义元数据的库。在Nest.js中,reflect-metadata用于支持基于装饰器的元编程。它允许开发人员在类、方法、参数等级别上添加元数据,以便在运行时进行反射和元数据的访问。这在实现依赖注入、路由处理和其他装饰器相关的功能时非常有用。

CRUD

快捷命令: $ nest g resource [name]

  • 生成一个module: $ nest g mo [name]
  • 生成一个controller$ nest g co [name]
  • 生成一个service$ nest g s [name]

基础知识

Nest CLI

数据传输

url param
query
form-urlencoded
form-data
json

IOC(Inverse of Control)

实现思路: 在class上声明依赖后,让工具去分析声明的依赖关系,根据先后顺序自动把对象创建好后组装起来。

它有一个放对象的容器,程序初始化的时候,会扫描class上声明的依赖关系,然后把这些class都给new一个实例放到容器里。创建对象的时候,还会把它们依赖的对象也注入进去。这种依赖注入的方式叫做 Dependency Injection 。

常用装饰器

  • @Module: 声明 Nest 模块
  • @Controller:声明模块里的 controller
  • @Injectable:声明模块里可以注入的 provider
  • @Inject:通过 token 手动指定注入的 provider,token 可以是 class 或者 string
  • @Optional:声明注入的 provider 是可选的,可以为空
  • @Global:声明全局模块
  • @Catch:声明 exception filter 处理的 exception 类型
  • @UseFilters:路由级别使用 exception filter
  • @UsePipes:路由级别使用 pipe
  • @UseInterceptors:路由级别使用 interceptor
  • @SetMetadata:在 class 或者 handler 上添加 metadata
  • @Get、@Post、@Put、@Delete、@Patch、@Options、@Head:声明 get、post、put、delete、patch、options、head 的请求方式
  • @Param:取出 url 中的参数,比如 /aaa/:id 中的 id
  • @Query: 取出 query 部分的参数,比如 /aaa?name=xx 中的 name
  • @Body:取出请求 body,通过 dto class 来接收
  • @Headers:取出某个或全部请求头
  • @Session:取出 session 对象,需要启用 express-session 中间件
  • @HostParm: 取出 host 里的参数
  • @Req、@Request:注入 request 对象
  • @Res、@Response:注入 response 对象,一旦注入了这个 Nest 就不会把返回值作为响应了,除非指定 passthrough 为true
  • @Next:注入调用下一个 handler 的 next 方法
  • @HttpCode: 修改响应的状态码
  • @Header:修改响应头
  • @Redirect:指定重定向的 url
  • @Render:指定渲染用的模版引擎

Nest原理

通过装饰器给类或对象添加元数据metadata,并且开启ts的**emitDecoratorMetadata **来自动添加类型相关的metadata,初始化的时候取出这些元数据进行依赖分析,然后创建对应的实例对象即可。所以核心就是 Reflector metadata的api。
Nest的装饰器都是依赖reflect-metadata来实现的,而且还提供了一个 @SetMetadata 的装饰器用于给class、method添加一些元数据metatdata。

Dynamic Module

Module 可以传入 options 动态产生,这叫做动态 Module,你还可以把传入的 options 作为 provider 注入到别的对象里。
建议的动态产生 Module 的方法名有 register、forRoot、forFeature 3种。

  • register:用一次注册一次
  • forRoot:只注册一次,用多次,一般在 AppModule 引入
  • forFeature:用了 forRoot 之后,用 forFeature 传入局部配置,一般在具体模块里 imports

并且这些方法都可以写 xxxAsync 版本,也就是传入 useFactory 等 option,内部注册异步 provider。
这个过程也可以用 ConfigurableModuleBuilder 来生成。通过 setClassMethodName 设置方法名,通过 setExtras 设置额外的 options 处理逻辑。
并且返回的 class 都有 xxxAsync 的版本。
这就是动态模块的定义方式,后面用到 typeorm、mongoose 等模块会大量见到这种模块。

Express

Express是处理请求、响应的库。

Nest核心概念

  • IOC
  • AOP
  • 全局模块
  • 动态模块
  • 自定义provider
  • 生命周期

Docker

FROM node:latest

WORKDIR /app

COPY . .

RUN npm config set registry https://registry.npmmirror.com/

RUN npm install -g http-server

EXPOSE 8080

CMD ["http-server", "-p", "8080"]

解释:

  • FROM: 基于一个基础镜像来修改
  • WORKDIR: 指定当前工作目录
  • COPY: 把容器外的内容复制到容器内
  • EXPOSE: 声明当前容器要访问的网络端口号,比如这里起服务会用到8080
  • RUN: 在容器内执行命令
  • CMD:容器启动的时候执行的命令

我们先通过 FROM 继承了 node 基础镜像,里面就有 npm、node 这些命令了。
通过 WORKDIR 指定当前目录。
然后通过 COPY 把 Dockerfile 同级目录下的内容复制到容器内,这里的 . 也就是 /app 目录
之后通过 RUN 执行 npm install,全局安装 http-server
通过 EXPOSE 指定要暴露的端口
CMD 指定容器跑起来之后执行的命令,这里就是执行 http-server 把服务跑起来。

生成镜像
docker build -t aaa:ccc .
其中,aaa 是镜像名,ccc是镜像的标签。

FROM node:18 # 继承node:18基础镜像

WORKDIR /app # 指定当前目录为 /app

COPY package.json . # 复制宿主机的package.json和lock文件到容器的当前目录,也就是 /app下

COPY *.lock .

RUN npm config set registry https://registry.npmmirror.com/

RUN npm install # 执行 npm install

COPY . . #复制其余的文件到当前容器内

RUN npm run build

EXPOSE 3000 # 指定容器需要暴露的端口是3000

CMD [ "node", "./dist/main.js" ] # 指定容器跑起来时执行的命令是 node ./dist/main.js

两种登录状态保存方式: session+cookie、jwt

  1. 服务端session+cookie

session + cookie 的给 http 添加状态的方案是服务端保存 session 数据,然后把 id 放入 cookie 返回,cookie 是自动携带的,每个请求可以通过 cookie 里的 id 查找到对应的 session,从而实现请求的标识。这种方案能实现需求,但是有 CSRF、分布式 session、跨域等问题,不过都是有解决方案的。

  1. 客户端存储的token

token 的方案常用 json 格式来保存,叫做 json web token,简称 JWT。
JWT 是保存在 request header 里的一段字符串(比如用 header 名可以叫 authorization),它分为三部分: header、payload、verify signature
image.png

header 部分保存当前的加密算法,payload 部分是具体存储的数据,verify signature 部分是把 header 和 payload 还有 salt 做一次加密之后生成的。(salt,盐,就是一段任意的字符串,增加随机性)
这三部分会分别做 Base64,然后连在一起就是 JWT 的 header,放到某个 header 比如 authorization 中:

makefile
复制代码authorization: Bearer xxxxx.xxxxx.xxxx

请求的时候把这个 header 带上,服务端就可以解析出对应的 header、payload、verify signature 这三部分,然后根据 header 里的算法也对 header、payload 加上 salt 做一次加密,如果得出的结果和 verify signature 一样,就接受这个 token。
把状态数据都保存在 payload 部分,这样就实现了有状态的 http:
image.png
JWT 的方案是把状态数据保存在 header 里,每次请求需要手动携带,没有 session + cookie 方案的 CSRF、分布式、跨域的问题,但是也有安全性、性能、没法控制等问题。

实战 session-cookie

npm install express-session
npm install @types/express-session -D

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as session from 'express-session';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.use(session({
    secret: 'guang',
    resave: false,
    saveUninitialized: false
  }));
  await app.listen(3000);
}
bootstrap();
  • secret: 加密的密钥
  • resave: ‘true’ 每次访问都会更新 session,不管有没有修改 session 的内容,而 false 是只有 session 内容变了才会去更新 session。
  • saveUninitalized: 设置为 ‘true’ 是不管是否设置 session,都会初始化一个空的 session 对象。比如你没有登录的时候,也会初始化一个 session 对象,这个设置为 false 就好。
import { Controller, Get, Session } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }

  @Get('sss')
  session(@Session() session) {
    console.log(session);
    // Session {
    //   cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true },
    //   count: 3
    // }
    session.count = session.count ? session.count + 1 : 1;
    return session.count;
  }
}

jwt

npm install @nestjs/jwt
引入

import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [
    // JwtModule.register({
    //   secret: 'wangyibo',
    //   signOptions: {
    //     expiresIn: '7d',
    //   },
    // }),
    JwtModule.registerAsync({
      async useFactory() {
        await 1;
        return {
          secret: 'wangyibo',
          signOptions: { expiresIn: '7d' },
        };
      },
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

在 controller 里注入 JwtModule 里的 JwtService:

import { Controller, Get, Inject, Res } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Response } from 'express';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Inject(JwtService) private jwtService: JwtService;
}

然后在加一个 handler:

@Get('ttt')
ttt(@Res({ passthrough: true }) response: Response) {
  const newToken = this.jwtService.sign({ count: 1 });
  response.setHeader('token', newToken);
  return 'hello';
}

注意: 因为注入 response 对象之后,默认不会把返回值作为 body 了,需要设置 passthrough 为 true 才可以。

import {
  Controller,
  Get,
  Inject,
  Res,
  Headers,
  UnauthorizedException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Response } from 'express';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Inject(JwtService) private jwtService: JwtService;

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }

  @Get('ttt')
  ttt(
    @Headers('authorization') authorization: string,
    @Res({ passthrough: true }) response: Response,
  ) {
    if (authorization) {
      try {
        const token = authorization.split(' ')[1];
        const data = this.jwtService.verify(token);

        const newToken = this.jwtService.sign({ count: data.count + 1 });
        response.setHeader('token', newToken);
        return data.count + 1;
      } catch (error) {
        throw new UnauthorizedException();
      }
    } else {
      const newToken = this.jwtService.sign({ count: 1 });
      response.setHeader('token', newToken);
      return 1;
    }
  }
}

通过 @Headers 装饰器取出 autorization 的 header,然后通过 jwtService.verify 对它做验证。
如果验证失败,那就抛出 UnauthorizedException 异常,让 Nest 内置的 Exception Filter 来处理。
验证成功就重新生成 jwt 放到 header 里返回。
如果没有 autorization 的 header,那就生成一个 jwt 放到 header 里返回。

  • 17
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值