Nest实战使用总结

174 阅读4分钟

Nest使用指南

nest是用于构建高效可扩展的一个基于node服务端应用开发框架,完全支持typescript,结合AOP。

nest还是一个springMVC风格,依赖注入、控制反转都借鉴了Angular,底层运用了express和fastify。以下都是在express框架上进行总结。

0、核心概念

控制反转IOC和依赖注入DI主要是解决类与类之间的耦合问题,通过一个Container容器解决问题,在容器里注入类,然后在其他类中使用。

class A {
    name:string
    constructor(name:string){
        this.name = name
    }
}
class C{
    name:string
    constructor(name:string){
        this.name = name
    }
}
class Container{
    mo:any
    constructor(name:string){
        this.mo = {}
    }
    provide(key:string,mo:any){
        this.mo[key] = mo
    }
    get(key:string){
        return this.mo[key]
    }
}

const mo = new Container()
mo.provide("a", new A("xxxx"))
mo.provide("b", new C("yyyy"))

class B {
    a:any
    c:any
    constructor(mo:Container){
    	this.a = mo.get("a")
        this.c = mo.get("c")
    }
}

1、基础命令

  • nest --help
命令目录位置文件名称
nest g co demo/demo/demo.controller.tscontroller.ts
nest g mo demo/demo/demo.module.tsmodule.ts
nest g s demo/demo/demo.service.tsservice.ts
nest g resource user/user/user.controller.ts /user/user.module.ts /user/user.service.tscontroller.ts module.ts service.ts
  • 版本控制

    • 启用全局版本控制
    // main.ts
    async function bootstrap() {
      const app = await NestFactory.create(AppModule);
      app.enableVersioning({
        type: VersioningType.URI,
      }); 
      await app.listen(3000);
    }
    
    //controller.ts
    @Controller({
      version: '2',
    })
    
    • 启用模块版本单个控制
    //controller.ts
     @Get()
     @Version('3')
     getHello(): string {
        return this.appService.getHello();
     }
    

2、控制器Controller

return@Res()的区别:当使用@Res的装饰器的时候,其实是调用了底层express框架的能力,这个时候不能使用return返回值了。return@Res()没有本质的区别,但是后期如果转fastify框架时,使用原生的return代码改动量会小。

  • Get
@Get()
@function(@Request() req){
    req.query //前端参数
}

//Get参数语法糖
@Get()
@function(@Query() query){
    query //前端参数
}
  • Post
@Post()
@function(@Request() req){
  req.body //前端参数
}

//Post参数语法糖
@Post()
@function(@Body() body){
  body //前端参数
}

//可以直接指定读取的返回参数
@Post()
@function(@Body("name") body){
  body //前端参数,这个body就是name这个值,原来是个对象{name:"xxx"}
}
  • Get动态参数
@Get(":id")
@function(@Request() req){
    req.params //前端参数
}

//Get动态参数语法糖
@Get(":id")
@function(@Param() params){
    params //前端参数
}

3、Session和Cookie

使用session的前提是要有cookie。

//main.ts
import * as session from 'express-session'
app.use(session({secret:"YTH",rolling:true,name:"yth.sid",cookie:{maxAge:999999}}))
@Get()
@function(@Request() req,@Session session){
    session.code = "xxx" //设置session的值
    console.log(session.code) //读取session的值
}

4、Providers

Providers本质上就是提供出一堆service.ts服务,实现全局或者共享模块。

  • Providers自定义名称

    1.在app.modules.ts中,providers参照以下写法

    2.在app.controller.ts中,用@Inject修饰名称

// app.modules.ts
@Module({
  providers: [AppService],
})

@Module({
  providers: [{
      provide:"RandomName",
      useClass:AppService
  }],
  exports: ['RandomName'], 
})

//app.controller.ts
@Controller()
export class AppController {
  constructor(@Inject('RandomName') private readonly appService: AppService) {}
}

注意:providers里的provide如果是字符串"RandomName",exports的时候也是字符串"RandomName",对应在controller里使用Inject("RandomName")。但是还有一种情况,这样就可以直接使用,但要小心在module里的exports和imports的形式。

// app.modules.ts
@Module({
  providers: [{
      provide:AppService,
      useClass:AppService
  }],
  exports: [AppService], 
})

//app.controller.ts
export class AppController {
  constructor(private readonly appService: AppService) {}
}
  • Providers自定义值
// app.modules.ts
@Module({
  providers: [AppService],
})

@Module({
  providers: [{
      provide:"RandomName",
      useValue:["a","b","c"]
  }],
})

//app.controller.ts
@Controller()
export class AppController {
  constructor(@Inject('RandomName') private readonly abc: string[]) {}
  @Get()
  getHello(): string {
    return this.abc; // ["a","b","c"]
  }
}

5、模块Module

  • user.service 作为共享模块
转存失败,建议直接上传图片文件

1、在user.module exports[user.service]

// user.module.ts
@Module({
  controllers: [UserController],
  providers: [UserService],
  exports: [UserService],
})

2、在list.module imports[user.module]

//list.module.ts
@Module({
  imports: [UserModule],
  controllers: [ListController],
  providers: [ListService],
})

3、在list.controller 依赖注入 user.service

//list.controller.ts
export class ListController {
  constructor(
    private readonly listService: ListService,
    private readonly userService: UserService,
  ) {}
}
  • user.service 作为全局模块
转存失败,建议直接上传图片文件

1、在user.module exports[user.service]

2、在user.module 加上@Global装饰器

// user.module.ts
@Global()
@Module({
  controllers: [UserController],
  providers: [UserService],
  exports: [UserService],
})

3、在list.controller 依赖注入 user.service

//list.controller.ts
export class ListController {
  constructor(
    private readonly listService: ListService,
    private readonly userService: UserService,
  ) {}
}
  • user.service 作为动态模块,支持参数传递

下面这个例子就是一个全局动态模块,支持传参数,参数可以在controller和service中使用。核心就是@Inject('UserServiceOptions') private readonly options: UserServiceOptions

//user.service.ts
@Injectable()
export class UserService {
  constructor(
    @Inject('UserServiceOptions') private readonly options: UserServiceOptions,
  ) {}
  findAll() {
    return `${this.options.apiURL} This action returns all list`;  //options 就是动态参数
  }
}

//user.controller.ts
@Controller('user')
export class UserController {
  constructor(
    private readonly userService: UserService,
    @Inject('UserServiceOptions') private readonly options: UserServiceOptions,
  ) {}
  @Get('list')
  async getUsers() {
    console.log('options', this.options);  //options 就是动态参数
    return this.userService.findAll();
  }
}

//user.module.ts
@Global()
@Module({})
export class UserModule {
  static forRoot(options: UserServiceOptions): DynamicModule {
    return {
      module: UserModule,
      controllers: [UserController],
      providers: [
        UserService,
        {
          provide: 'UserServiceOptions',
          useValue: options,
        },
      ],
      exports: [UserService],
    };
  }
}

//app.module.ts
@Module({
  controllers: [AppController],
  providers: [AppService],
  imports: [UserModule.forRoot(userServiceOptions), ListModule],
})

6、中间件MiddleWare

  • 全局

定义一个全局中间件,在main.ts里进行注册,全局中间件不同于局部中间件是一个class,全局中间件是一个函数。

1.写中间件函数

2.使用app.use(“xxxx”) 注册

//main.ts
function MiddleWareAll(req: Request, res: Response, next: NextFunction) {
  next();
}
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.use(MiddleWareAll);
  await app.listen(3000);
}
  • 局部

定义一个中间件,在需要使用这个中间件的module.ts文件里进行注册,比如logger中间件我要用在list里,对使用/list的路由进行AOP。

1.注册中间件,最好使用express的类型声明覆盖原来的any

2.在module.ts中将logger引用进来,然后实现configure函数

// logger.middleware.ts
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: any, res: any, next: () => void) {
    next();
  }
}

// list.module.ts
import { LoggerMiddleware } from '../logger.middleware';
export class ListModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggerMiddleware).forRoutes('list'); //1.会拦截所有/list的路由
    consumer.apply(LoggerMiddleware).forRoutes({  //2.会拦截/list的路由的GET请求
        path:"list",
        method:RequestMethod.GET
    });
    consumer.apply(LoggerMiddleware).forRoutes(ListController); //3.会拦截所有/list的路由
  }
}
  • 图片上传
  • 日志

7、其他

  • 静态目录
  • 管道