前言
同学,你好!我是 嘟老板。之前写 java 的时候经常会用到各种各样的注解(通常是 SpringBoot 框架提供),如 @RestController、@GetMapping等,极大的提升了代码的整洁度和开发效率。那 express 能否使用类似的方式开发呢?答案是肯定的,实现的关键就是接下来要聊的 routing-controllers。
什么是 routing-controllers
routing-controllers 是基于 TypeScript 的库,可以使用装饰器来创建结构化、声明性的控制器类,可帮助我们更好的组织代码结构。
routing-controllers 适用于 Express 和 Koa,本文重点讲解如何在 Express 项目中使用。
基本使用
安装依赖
- 安装 routing-controllers 相关依赖
- routing-controllers:本文主角。
- reflect-metadata:操作元数据,可以在声明的时候添加和读取,点击查看详细介绍。
使用时要在使用 routing-controllers 前引入。
import 'reflect-metadata';
- class-transformer:用于对象与类实例之间相互转化、序列化和反序列化。
- class-validator:校验类属性。
终端输入以下命令,回车执行:
npm install routing-controllers reflect-metadata class-transformer class-validator -S
- 安装 express 相关依赖
- express:express 框架,不了解的同学可参考 《express 基础入门》:
- body-parser:解析请求体 body 的 express 中间件。
终端输入以下命令,回车执行:
npm install express body-parser -S
安装依赖对应的 ts 类型:
npm install -D @types/express @types/body-parser
- 启用 tsconfig.json 装饰器相关配置
- emitDecoratorMetadata:启用装饰器元数据支持。
- experimentalDecorators:启用装饰器支持。
{
"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-node 和 nodemon 依赖。
终端输入以下命令,回车执行,安装依赖:
npm i -D nodemon ts-node
安装成功后,终端输入 npm run start
,回车执行,出现以下内容即为启动成功:
打开浏览器输入地址:http://localhost:3000/users/getAll
:
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 参数是路由的地址,拼接在 controller 的 baseRoute 后。
- 示例
@Get('/get')
get() {}
相当于 express: app.get('/get', get)。
@Post
- 用途
将函数声明为 Post 方法请求路由。
- 语法
@Post(route: string|RegExp)
route 参数是路由的地址,拼接在 controller 的 baseRoute 后。
- 示例
@Post('/post')
post() {}
相当于 express: app.post('/post', post)。
@Put
- 用途
将函数声明为 Put 方法请求路由。
- 语法
@Put(route: string|RegExp)
route 参数是路由的地址,拼接在 controller 的 baseRoute 后。
- 示例
@Put('/put/:id')
put() {}
相当于 express: app.put('/put/:id', put)。
@Patch
- 用途
将函数声明为 Patch 方法请求路由。
- 语法
@Patch(route: string|RegExp)
route 参数是路由的地址,拼接在 controller 的 baseRoute 后。
- 示例
@Patch('/patch/:id')
patch() {}
相当于 express: app.patch('/patch', patch)。
@Delete
- 用途
将函数声明为 Delete 方法请求路由。
- 语法
@Delete(route: string|RegExp)
route 参数是路由的地址,拼接在 controller 的 baseRoute 后。
- 示例
@Delete('/delete/:id')
delete() {}
相当于 express: app.delete('/delete', delete)。
@Head
- 用途
将函数声明为 Head 方法请求路由。
- 语法
@Head(route: string|RegExp)
route 参数是路由的地址,拼接在 controller 的 baseRoute 后。
- 示例
@Head('/head')
head() {}
相当于 express: app.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) {}
相当于 express: request.params.id。
get(@Params() params: any)
相当于 express: request.params。
@QueryParam、@QueryParams
- 用途
注入 Query 参数,常用于 get 请求。
- 语法
@QueryParam(name: string, options?: ParamOptions)
@QueryParams()
name:获取指定名称的参数。
options:参数配置项,如 required 是否必需。
- 示例
get(@QueryParam("id") id: number) {}
相当于 express: request.query.id。
get(@QueryParams() params: any)
相当于 express: request.query。
@Body、@BodyParam
- 用途
注入 Body 参数,常用于 post 请求。
- 语法
@BodyParam(name: string, options?: ParamOptions)
@Body()
name:获取指定名称的参数。
options:参数配置项,如 required 是否必需。
- 示例
get(@BodyParam("id") id: number) {}
相当于 express: request.body.id。
get(@Body() body: any) {}
相当于 express: request.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 是如何支持中间件的。
局部中间件
创建
可以通过以下两种方式创建局部中间件:
- 函数方式
函数方式比较简单,定义一个中间件函数即可。
参数与 express 中间件的参数一致,分别为 请求对象 request 和 响应对象 response 以及可选的 next 函数。
export function CustomMiddleware(request: any, response: any, next?: (err?: any) => any): any {
console.log('我是自定义局部中间件...');
next();
}
- 类方式
此方式需要实现 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 的应用理解,希望对您有所帮助。
如您对文章内容有任何疑问或想深入讨论,欢迎评论区留下您的问题和见解。
技术简而不凡,创新生生不息。我是 嘟老板,咱们下期再会。