Controller设计理念,传参和回应方式你都知道吗?

746 阅读5分钟

Controller概念

每一个 Controller 都可以依照需求来设计不同 Http Method 的资源,就好像外场服务生负责带位、协助客人点餐一样,并根据客户的需求做出相对应的回应:Controller 负责路由的配置并处理来自客户端的请求,而每一个 Controller 都可以依照需求来设计不同 Http Method 的资源, 就好像外场服务生负责带位、协助客人点餐一样,并根据客户的需求做出相对应的回应: image.png

NestCLI 快速生成 Controller:

NestCLI 快速生成 Controller:
$ nest generate controller <CONTROLLER_NAME>

#**注意**:`<CONTROLLER_NAME>`可以含有路径,如:`features/todo`,这样就会在 `src` 资料夹下建立该路径并含有 Controller。

`src` 底下会看见一个名为 `todo` 的资料夹
nest generate controller todo

Controller发起HTTP请求详解

路由前缀,路由符号(regexp)

  • 添加路由前缀的好处是可以使相同路由的资源都归纳在同一个 Controller 里面,其中包含了前缀底下的所有子路由:

  • 设计路由时,可能会提供些许的容错空间, 比如说:原本是GET /todos/examples``/todos/exammmmmmmmples``/todos/exam_ples``/todos/examples``* image.png 路由前缀和路由参数演示为一个动图,在下面~~

路由参数@Param,查询参数 @Query

传什么参数就填什么,没什么难的。看个范例

路由前缀和路由参数演示: nest-route.gif 查询参数演示: nest-query.gif

//路由参数@Param
import { Controller, Get, Param } from '@nestjs/common';
//路由前缀:todos
@Controller('todos')
export class TodoController {
    //路由参数:id
    @Get(':id')
    get(@Param('id') id: string) {
        return {
            id,
            title: `Title ${id}`,
            description: ''
        };
    }
}
//查询参数 @Query
import { Controller, Get, Query } from '@nestjs/common';

@Controller('todos')
export class TodoController {
    @Get()
    getList(
        // 查询参数:limit或skip
        @Query('limit') limit: number = 30,
        @Query('skip') skip: number = 0
    ) {
        const list = [
            {
                id: 1,
                title: 'Title 1',
                description: ''
            },
            {
                id: 2,
                title: 'Title 2',
                description: ''
            }
        ];

        return list.slice(skip, limit);
    }
}

主体资料 (@Body),资料传输物件 (Data Transfer Object), 标头 (@Header)

  • 在传输资料时经常会使用到主体资料,比如说:POSTPUTPATCH等操作,Nest 有提供 @Body 装饰器来取得主体资料。范例程式码如下: nest-body.gif
import { Body, Controller, Post } from '@nestjs/common';

@Controller('todos')
export class TodoController {
    @Post()
    create(
        // 创建body的title,description
        @Body('title') title: string,
        @Body('description') description?: string
    ) {
        const id = 1;
        return { id, title, description };
    }
}
  • 资料传输物件 (Data Transfer Object)通常用于****过滤、格式化资料,它只负责存放要传递的资讯,故 只有唯读属性,没有任何方法 既然是定义格式,那么就有两种选择:
  1. TypeScript 的interface
  2. 标准 JavaScript 支援的class

基本上会建议大家采用 class 的形式来建立 DTO,原因是 interface 在编译成 JavaScript 就会被删除,而 class 会保留,这对部分功能是有影响的,所以 官方也推荐大家采用 class

那么就来建立一个范例的 DTO 吧,在要调整的 Controller 目录下,新增一个名为 dto 的资料夹,并建立create-<CONTROLLER_NAME>.dto.ts,我这边的档案名称为create-todo.dto.ts

nest-dto.gif

export class CreateTodoDto {
    public readonly title: string;
    public readonly description?: string;
}
// dto格式为只读格式


import { Body, Controller, Post } from '@nestjs/common';
import { CreateTodoDto } from './dto/create-todo.dto';
// 将带有@body装饰器的参数类型指定为该DTO格式
@Controller('todos')
export class TodoController {
    @Post()
    create(@Body() dto: CreateTodoDto) {
        const id = 1;
        return { id, ...dto };
    }
}
  • 有时候可能需要设置标头来回传给用户端,这时候就可以用 @Header 装饰器来配置:

nest-returnHeader.gif

import { Controller, Get, Header } from '@nestjs/common';

@Controller('todos')
export class TodoController {
    @Get()
    // 设置标头回传客户端
    @Header('X-Hao-headers', '1')
    getAll() {
        return {
            id: 1,
            title: 'Title 1',
            description: ''
        };
    }
}

其他提供许多参数装饰器来提供开发人员取得更多资讯查阅表

  • @Request():请求的装饰器,带有此装饰器的参数会赋予底层框架的 请求物件 (Request Object) ,该装饰器有别称,通常将参数名称取为。@Req()``req
  • @Response():回应的装饰器,带有此装饰器的参数会赋予底层框架的 回应物件 (Response Object) ,该装饰器有别称,通常将参数名称取为。@Res()``res
  • @Next():Next 函式的装饰器,带有此装饰器的参数会赋予底层框架的 Next 函式,用途为呼叫下一个 中介软体 (Middleware)
  • @Param(key?: string)路由参数的装饰器,相当于 req.params 或req.params[key]
  • @Query(key?: string)查询参数的装饰器,相当于 req.query 或req.query[key]
  • @Body(key?: string)主体资料的装饰器,相当于 req.body 或req.body[key]
  • @Headers(name?: string)请求标头的装饰器,相当于 req.headers 或req.headers[name]
  • @Session()session的装饰器,相当于req.session
  • @Ip():IP 的装饰器,相当于req.ip
  • @HostParam():host 的装饰器,相当于req.hosts

Http Methods & Http Code

添加装饰器在* class *的方法上,来指定不同 Http Method 所呼叫的方法

常见方法查阅表:

  • @Get:表示接收对应路由且为 GET 请求时触发。
  • @Post:表示接收对应路由且为 POST 请求时触发。
  • @Put:表示接收对应路由且为 PUT 请求时触发。
  • @Patch:表示接收对应路由且为 PATCH 请求时触发。
  • @Delete:表示接收对应路由且为 DELETE 请求时触发。
  • @Options:表示接收对应路由且为 OPTIONS 请求时触发。
  • @Head:表示接收对应路由且为 HEAD 请求时触发。
  • @All:表示接收对应路由且为以上任何方式的请求时触发。

image.png

import { Controller, Patch, HttpCode, HttpStatus } from '@nestjs/common';

@Controller('todos')
export class TodoController {
    @Patch()
    @HttpCode(HttpStatus.NO_CONTENT)
    get() {
        return [];
    }
}

Controller处理回应的方式

标准模式 return

import { Controller, Get } from '@nestjs/common';

@Controller('todos')
export class TodoController {
    @Get()
    getAll() {
        return [];
    }
}

非同步ES7 的async/await

import { Controller, Get } from '@nestjs/common';

@Controller('todos')
export class TodoController {
    @Get()
    async getAll() {
        return new Promise((resolve, reject) => setTimeout(() => resolve([]), 1000));
    }
}

RxJS函数库

Nest 会自动订阅 / 取消订阅对象,无须手动取消订阅,

import { Controller, Get } from '@nestjs/common';
import { of } from 'rxjs';

@Controller('todos')
export class TodoController {
    @Get()
    getAll() {
        return of([]);
    }
}

底层Express框架的回应物件

import { Controller, Get, Res } from '@nestjs/common';
import { Response } from 'express';

@Controller('todos')
export class TodoController {
    @Get()
    getAll(@Res() res: Response) {
        res.send([]);
    }
}

模式的限制

Nest 会去侦测是否有带@Res@Response@Next装饰器的参数,如果有的话,该资源就会启用函式库模式,而标准模式会被关闭,这是什么意思呢?简单来说 值的方式会失去作用

那如果真的要从回应物件中取得资讯,但又想采用标准模式的话,有什么方法可以突破此限制吗?答案是有的,只需要在装饰器中添加 passthrough: true 即可:

import { Controller, Get, Res } from '@nestjs/common';
import { Response } from 'express';

@Controller('todos')
export class TodoController {
    @Get()
    getAll(@Res({ passthrough: true }) res: Response) {
        return [];
    }
}