基于ts装饰器,reflect-metadata来优雅的管理express的api路由

背景

最近开始学习使用express来写api,按照官方文档写出如下的代码

// index.ts
import express from 'express'
import { Express, Request, Response } from 'express'
const app: Express = express()
const port = 3000

app.get('/', (req: Request, res: Response) => {
  res.status(200).json({
    message: 'ok'
  })
})

app.get('/user/getUser', (req: Request, res: Response) => {
  // 业务逻辑.........
  res.status(200).json({
    message: 'getUser'
  })
})

// 随着业务的拓展,这里会有很多的接口............

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

随着业务逻辑当增多,把所有的api平铺在一块,这显然不是我想要的

一、初步改进-按业务分文件

很容易就会想到,可以把同样类型业务的api抽出来单独放到文件中,形成这样的目录的目录结构,这显然是有助于拓展和维护的

// User.ts
import { Express, Request, Response } from 'express'
export default class User {
  app: Express
  constructor(app: Express) {
    this.app = app
  }

  getUserById() {
    this.app.get('/user/getUser', (req: Request, res: Response) => {
      // 业务逻辑.........
      res.status(200).json({
        message: 'getUser'
      })
    })
  }

  getUserList() {}

  addUser() {}

  deleteUser() {}
}


// 在index.ts中注册User文件中的接口
import User from './api/User'
const user = new User(app)

for (const key in user) {
  if (typeof user[key] === 'function' && key !== 'app') {
    user[key]()
  }
}

但是,我们会发现,在User.ts中,每个接口都要写一遍app.get,或者app.post来注册api,这显然还是产生了大量的重复代码,怎么继续优化使得我只在User类中写业务代码?

二、用类装饰器和reflect-metedata来彻底将业务代码和非业务代码剥离

1. 先看下优化过的目录结构

这里新增了router.ts和decorators/http.ts 两个文件

  • api:业务接口存放当目录,一个文件用一个类来代表一个业务
  • decorators:装饰器目录,http.ts主要对api里面的类进行装饰
  • index.ts:项目入口文件
  • router.ts:路由,用来解析api里的类,注册接口到express的app实例中

2. 核心代码

  • index.ts
import express from 'express'
import { Express, Request, Response } from 'express'
import 'reflect-metadata'
import router from './router'

const app: Express = express()
const port = 3000

router(app)

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})
  • http.ts
import { Request, Response } from 'express'
const httpMethodKey = Symbol('router:httpMethod')

// 注意这里是typescript5的装饰器写法
// 请求类型装饰器
const httpMethod = function (method: 'get' | 'post') {
  return function (target: any, context: any) {
    // 挂载元数据,请求类型
    Reflect.defineMetadata(httpMethodKey, method, target, context.name)
  }
}

const httpPathKey = Symbol('router:httpPath')

// 请求地址装饰器
const path = function (path: string) {
  return function (target: any, context: any) {
    // 1. 覆盖原方法
    const replaceMethod = (req: Request, res: Response): any => {
      const p = Object.assign({}, req.query, req.body)
      let result = target.call(this, p)
      res.status(200).send(result)
      return result
    }

    // 2. 挂载元数据,请求地址
    Reflect.defineMetadata(httpPathKey, path, replaceMethod, context.name)

    return replaceMethod
  }
}

export { httpMethodKey, httpMethod, httpPathKey, path }
  • router.ts
import { Express, Request, Response } from 'express'
import { httpMethodKey, httpPathKey } from './decorators/http'
import User from './api/User'
// 路由
export default function router(app: Express) {
  let user = new User()
  for (let methodName in user) {
    let method = user[methodName]
    if (typeof method !== 'function') break
    // 解析元数据
    let httpMethod = Reflect.getMetadata(httpMethodKey, method, methodName)
    let httpPath = Reflect.getMetadata(httpPathKey, method, methodName)
    // 注册api
    if (httpMethod && httpPath) app[httpMethod](httpPath, method)
  }
}
  • User.ts
import { httpMethod, path } from '../decorators/http'

export default class User {
  @httpMethod('get')
  @path('/getUserById')
  getUserById(param: any, ...Args: any[]): any {
    // 参数是query和body拼接的参数
    console.log(param)
    //........这里只需要写业务代码,接口函数的包装交由装饰器,接口注册交由router
    // return值就是接口返回值
    return {
      name: 'xiaoming',
      age: 10
    }
  }

  @httpMethod('get')
  @path('/getUserList')
  getUserList(param: any, ...Args: any[]) {
    return [
      {
        name: 'xiaoming',
        age: 10
      },
      {
        name: 'xiaohua',
        age: 20
      },
      {
        name: 'xiaoli',
        age: 30
      }
    ]
  }

  @httpMethod('post')
  addUser() {}

  @httpMethod('post')
  deleteUser() {}
}

完整代码参考express-router-demo(gitee.com)


测试

通过!

这里为接口路由的管理提供了一种简单的思路,希望大家多指点改进。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值