Express 项目集成 routing-controllers,快速搞定 controller 层

904 阅读10分钟

前言

同学,你好!我是 嘟老板。之前写 java 的时候经常会用到各种各样的注解(通常是 SpringBoot 框架提供),如 @RestController@GetMapping等,极大的提升了代码的整洁度和开发效率。那 express 能否使用类似的方式开发呢?答案是肯定的,实现的关键就是接下来要聊的 routing-controllers

什么是 routing-controllers

routing-controllers 是基于 TypeScript 的库,可以使用装饰器来创建结构化、声明性的控制器,可帮助我们更好的组织代码结构。

routing-controllers 适用于 ExpressKoa,本文重点讲解如何在 Express 项目中使用。

基本使用

安装依赖

  1. 安装 routing-controllers 相关依赖

使用时要在使用 routing-controllers 前引入。

import 'reflect-metadata';

终端输入以下命令,回车执行:

npm install routing-controllers reflect-metadata class-transformer class-validator -S
  1. 安装 express 相关依赖

终端输入以下命令,回车执行:

npm install express body-parser -S

安装依赖对应的 ts 类型:

npm install -D @types/express @types/body-parser
  1. 启用 tsconfig.json 装饰器相关配置
{
  "emitDecoratorMetadata": true,
  "experimentalDecorators": true
}

示例 Controller

工程搭建步骤就不细说了,不清楚的可以参考 《我用 express 实现会话控制之 - Cookie》,有详细的搭建过程。

编写入口代码

入口代码(通常 src/app.ts)主要包含以下内容:

  • 引入必要依赖,如 reflect-metadata
  • 创建 express 应用实例。
  • 安装相关中间件,如 body-parser
  • express 实例注册到 routing-controllers
  • 启动服务,监听指定端口(通常 3000)。

具体代码如下:

import 'reflect-metadata'
import { useExpressServer } from 'routing-controllers'
import { json, urlencoded } from 'body-parser'
import express from 'express'
import { UserController } from './controllers/user.controller'

// 创建 express 应用实例
const app = express()

// body 解析相关中间件
// 解析 json 格式
app.use(json())
// 解析 urlencoded body
// 会在 request 对象上挂载 body 属性,包含解析后的数据。
// 这个新的 body 对象包含 key-value 键值对,若设置 extended 为 true,则键值可以是任意类型,否则只能是字符串或数组。
app.use(urlencoded({ extended: true }))

// 在 routing-controllers 中注册已有的 express 实例
useExpressServer({
  controllers: [UserController]
})

// 启动服务,监听 3000 端口
const PORT = 3000
app.listen(PORT, () => {
  console.log(`  App is running at http://localhost:${PORT}\n`)
  console.log('  Press CTRL-C to stop\n')
})

创建 UserController

细心的同学可能发现,入口文件中引入了 user.controller,我们来创建一下。

import { Controller, Param, Body, Get, Post, Put, Delete } from 'routing-controllers';

@Controller()
export class UserController {
  @Get('/users')
  getAll() {
    return '查询所有用户';
  }

  @Get('/users/:id')
  getOne(@Param('id') id: number) {
    return `查询 id 为 ${id} 的用户`;
  }

  @Post('/users')
  post(@Body() user: any) {
    return '保存用户';
  }

  @Put('/users/:id')
  put(@Param('id') id: number, @Body() user: any) {
    return `更新 id 为 ${id} 的用户信息`;
  }

  @Delete('/users/:id')
  remove(@Param('id') id: number) {
    return `删除 id 为 ${id} 的用户`;
  }
}

测试一下

启动服务,package.json 中添加 npm script: "start": "nodemon ./src/app.ts",需要安装 ts-nodenodemon 依赖。

终端输入以下命令,回车执行,安装依赖:

npm i -D nodemon ts-node

安装成功后,终端输入 npm run start,回车执行,出现以下内容即为启动成功:

image.png

打开浏览器输入地址:http://localhost:3000/users/getAll:

image.png

OK,接口已通。

进阶用法

常用注解及用法

routing-controllers 的核心优势就是提供了大量的注解,我们在开发中可以直接按需使用,免去自己实现的复杂过程。

按照注解用途,可以分为以下几类:

  • 控制器类
  • 函数类
  • 参数类
  • 中间件/拦截器类
  • 其他

我们挨个看看,每类都包含哪些优秀的注解。

控制器类

控制器类注解在 controller 类 上使用。

@Controller

  • 用途

将类注册为 controller

  • 语法

@Controller(baseRoute: string)

baseRoute 参数作为当前 controller 下所有接口的统一前缀。

  • 示例
@Controller('/user')
class UserController {}

@JsonController

  • 用途

将类注册为 controller。与 @Controller 不同之处在于,@JsonController 会自动将 controller 返回的结果转换为 JSON 对象,并将响应的内容类型 Content-Type 表头设置为 application/json

  • 语法

@JsonController(baseRoute: string)

baseRoute 参数作为当前 controller 下所有接口的统一前缀。

  • 示例
@JsonController('/user')
class UserController {}

函数类

函数类注解在 类函数 上使用。

@Get

  • 用途

将函数声明为 Get 方法请求路由。

  • 语法

@Get(route: string|RegExp)

route 参数是路由的地址,拼接在 controllerbaseRoute 后。

  • 示例
@Get('/get')
get() {}

相当于 expressapp.get('/get', get)

@Post

  • 用途

将函数声明为 Post 方法请求路由。

  • 语法

@Post(route: string|RegExp)

route 参数是路由的地址,拼接在 controllerbaseRoute 后。

  • 示例
@Post('/post')
post() {}

相当于 expressapp.post('/post', post)

@Put

  • 用途

将函数声明为 Put 方法请求路由。

  • 语法

@Put(route: string|RegExp)

route 参数是路由的地址,拼接在 controllerbaseRoute 后。

  • 示例
@Put('/put/:id')
put() {}

相当于 expressapp.put('/put/:id', put)

@Patch

  • 用途

将函数声明为 Patch 方法请求路由。

  • 语法

@Patch(route: string|RegExp)

route 参数是路由的地址,拼接在 controllerbaseRoute 后。

  • 示例
@Patch('/patch/:id')
patch() {}

相当于 expressapp.patch('/patch', patch)

@Delete

  • 用途

将函数声明为 Delete 方法请求路由。

  • 语法

@Delete(route: string|RegExp)

route 参数是路由的地址,拼接在 controllerbaseRoute 后。

  • 示例
@Delete('/delete/:id')
delete() {}

相当于 expressapp.delete('/delete', delete)

@Head

  • 用途

将函数声明为 Head 方法请求路由。

  • 语法

@Head(route: string|RegExp)

route 参数是路由的地址,拼接在 controllerbaseRoute 后。

  • 示例
@Head('/head')
head() {}

相当于 expressapp.head('/head', head)

参数类

@Req()

  • 用途

注入 Request 请求对象。

  • 示例
getAll(@Req() request: Request) {}

相当于 express(req, res) => {}

@Res()

  • 用途

注入 Response 响应对象。

  • 示例
getAll(@Res() response: Response) {}

相当于 express(req, res) => {}

@Param、@Params

  • 用途

注入路由请求参数。

  • 语法

@Param(name: string, options?: ParamOptions)

@Params()

name:获取指定名称的参数。
options参数配置项,如 required 是否必需。

  • 示例
get(@Param("id") id: number) {}

相当于 expressrequest.params.id

get(@Params() params: any)

相当于 expressrequest.params

@QueryParam、@QueryParams

  • 用途

注入 Query 参数,常用于 get 请求。

  • 语法

@QueryParam(name: string, options?: ParamOptions)

@QueryParams()

name:获取指定名称的参数。
options参数配置项,如 required 是否必需。

  • 示例
get(@QueryParam("id") id: number) {}

相当于 expressrequest.query.id

get(@QueryParams() params: any)

相当于 expressrequest.query

@Body、@BodyParam

  • 用途

注入 Body 参数,常用于 post 请求。

  • 语法

@BodyParam(name: string, options?: ParamOptions)

@Body()

name:获取指定名称的参数。
options参数配置项,如 required 是否必需。

  • 示例
get(@BodyParam("id") id: number) {}

相当于 expressrequest.body.id

get(@Body() body: any) {}

相当于 expressrequest.body

中间件/拦截器类

@Middleware

  • 用途

注册全局中间件。

  • 语法

@Middleware({ type: "before"|"after" })

type:中间件执行时机,指定在 controller 处理函数前或后调用。

  • 示例
@Middleware({ type: "before" })
class GlobalMiddleware {}

@UseBefore、@UseAfter

  • 用途

用于使用 局部中间件,可修饰 controller函数

@UseBefore:在请求函数执行前调用中间件。

@UseAfter:在请求函数执行后调用中间件。

  • 示例
@UseBefore(CustomMiddleware1)
@UseAfter(CustomMiddleware2)
export class UserController {

    @Get("/users/:id")
    @UseBefore(CustomMiddleware3)
    @UseAfter(CustomMiddleware4)
    get(@QueryParam('id') id: number) {}
}

@Interceptor

  • 用途

注册全局拦截器。

  • 示例
@Interceptor()
class GlobalInterceptor {}

@UseInterceptor

  • 用途

用于使用 局部拦截器,可修饰 controller函数,可用于拦截其所修饰的操作执行结果,更改或替换结果数据。

  • 示例
@UseInterceptor(CustomInterceptor)
export class UserController {

    @Get("/users/:id")
    @UseInterceptor(CustomInterceptor1)
    get(@QueryParam('id') id: number) {}
}

其他

  • @Header:设置响应头。
  • @ContentType:设置响应头 ContentType 标头。
  • @Location:设置响应头 Location 标头。
  • @Redirect:设置响应头 Redirect 标头。
  • ......

中间件

中间件属于 express 核心知识点,使用 express,就离不开中间件。我们详细说说 routing-controllers 是如何支持中间件的。

局部中间件

创建

可以通过以下两种方式创建局部中间件:

  1. 函数方式

函数方式比较简单,定义一个中间件函数即可。

参数与 express 中间件的参数一致,分别为 请求对象 request响应对象 response 以及可选的 next 函数。

export function CustomMiddleware(request: any, response: any, next?: (err?: any) => any): any {
  console.log('我是自定义局部中间件...');
  next();
}
  1. 类方式

此方式需要实现 routing-controllers 提供的 ExpressMiddlewareInterface 类,并重写 use 方法,加入自定义的处理逻辑。

use 方法的参数与函数定义的参数一致。

import { ExpressMiddlewareInterface } from 'routing-controllers';

export class CustomMiddleware implements ExpressMiddlewareInterface {
  use(request: any, response: any, next?: (err?: any) => any): any {
    console.log('我是自定义局部中间件...');
    next();
  }
}

使用

局部中间件使用需要借助 @UseBefore@UseAfter 注解。

若中间件在请求操作前执行,使用 @UseBefore;在请求操作后执行,使用 @UseAfter

中间件可以应用于 controller具体的操作函数

用于 controller
import { Controller, UseBefore } from 'routing-controllers';
import { MyMiddleware } from './MyMiddleware';
import { loggingMiddleware } from './loggingMiddleware';

@Controller()
@UseBefore(CustomMiddleware)
export class UserController {}
用于函数
@Get("/users/:id")
@UseBefore(CustomMiddleware)
get(@Param("id") id: number) {
    // 请求操作具体逻辑...
}

全局中间件

全局中间件默认在每个请求操作前执行,可以通过设置 type 配置项,指定执行的时机。

type 配置有两个可选值:

  • before: 请求操作执行。
  • after: 请求操作执行。

创建

创建全局中间件需要借助 @Middleware 注解。

import { Middleware, ExpressMiddlewareInterface } from 'routing-controllers';

@Middleware({ type: 'before' })
export class CustomGlobalMiddleware implements ExpressMiddlewareInterface {
  use(request: any, response: any, next: (err: any) => any): void {
    console.log('我是全局中间件...');
    next();
  }
}

使用

若要启用全局中间件,要在调用 createExpressServer 函数创建应用实例时,参数对象添加 middlewares 属性,并将需要启用的中间件数组赋值给它。

import { createExpressServer } from 'routing-controllers';
import { UserController } from './UserController';
import { CustomGlobalMiddleware } from './CustomGlobalMiddleware';

const app = createExpressServer({
  controllers: [UserController],
  middlewares: [CustomGlobalMiddleware],
})

app.listen(3000);

拦截器

拦截器属于 routing-controllers 提供的特性,可以帮助我们拦截请求结果并进行修改或替换。

其工作原理和中间件基本相同。

同样的,拦截器也分为 局部拦截器全局拦截器

局部拦截器

创建

和中间件一样,拦截器也分别支持 函数方式类方式 创建。

函数方式

拦截器函数接收两个参数:

  • action:包含请求函数涉及的相关对象,如 请求对象 Request响应对象 Response 以及 next 函数 等。
  • content:请求操作返回的结果,拦截器可以对其进行修改或替换,返回新的结果。
export function MyInterceptor(action: Action, content: any) {
    // ...拦截器处理逻辑
    return content.replace(/World/gi, "Dulaoban");
}
类方式

此方式需要实现 routing-controllers 提供的 InterceptorInterface 类,并重写 intercept 方法,写入处理逻辑。

intercept 方法的参数与函数定义的参数一致。

import { Interceptor, InterceptorInterface, Action } from 'routing-controllers';

export class MyInterceptor implements InterceptorInterface {
  intercept(action: Action, content: any) {
    return content.replace(/World/gi, 'Dulaoban');
  }
}

使用

局部拦截器使用需要借助 @UseInterceptor 注解。

拦截器可以应用于 controller具体的操作函数

用于 controller

拦截器用于 controller 时,作用于该 controller 下的所有请求,每个请求的结果都会被拦截。

import { Controller, UseBefore } from 'routing-controllers';
import { MyMiddleware } from './MyMiddleware';
import { MyInterceptor } from './MyInterceptor';

@Controller()
@UseInterceptor(MyInterceptor)
export class UserController {}
用于函数
@Get("/users/:id")
@UseInterceptor(MyInterceptor)
get(@Param("id") id: number) {
    // 请求操作具体逻辑...
    return 'Hello World'
}

全局拦截器

全局拦截器会作用于所有的 controller,对所有的请求结果进行统一拦截。

创建

创建全局拦截器需要借助 @Interceptor 注解。

import { Interceptor, InterceptorInterface, Action } from 'routing-controllers';

@Interceptor()
export class MyGlobalInterceptor implements InterceptorInterface {
  intercept(action: Action, content: any) {
    return content.replace(/World/gi, 'Dulaoban');
  }
}

使用

若要启用全局拦截器,要在调用 createExpressServer 函数创建应用实例时,参数对象添加 interceptors 属性,并将需要启用的拦截器数组赋值给它。

import { createExpressServer } from 'routing-controllers';
import { UserController } from './UserController';
import { MyGlobalInterceptor } from './MyGlobalInterceptor';

const app = createExpressServer({
  controllers: [UserController],
  interceptors: [MyGlobalInterceptor],
})

app.listen(3000);

结语

本文重点介绍了 express 框架集成 routing-controllers 库, routing-controllers 核心注解介绍及使用方法,旨在帮助同学们加深对于 routing-controllers 的应用理解,希望对您有所帮助。

如您对文章内容有任何疑问或想深入讨论,欢迎评论区留下您的问题和见解。

技术简而不凡,创新生生不息。我是 嘟老板,咱们下期再会。


往期推荐