[koa-joi-swagger] nodejs 写 swagger 文档的正确方式
发布于 2 年前 作者 zaaack 6272 次浏览 来自 分享

本来在网上想找一个 koa 的基于 swagger 的验证框架,试了几个不是太老,就是 bug 太多,不仅不能满足需求,而且扩展性也很差。在给 swagger2 (swagger2-koa) 提了个 pr 之后,还是放弃了 …

开始想用 ajv 来折腾一个,但是发现它缺乏成熟的方案满足我的需求

  1. 自动删除多余字段
  2. 自动转换各种格式类型
  3. 写起来不要太冗长 (JSON schema 躺枪)

JavaScript 中最强大的验证框架莫过于 joi 了,丰富而简洁的 API,良好的文档,足够的扩展性,足以满足绝大多数的验证需求。

其中还纠结了半天用 joi 来验证 JSON schema 的 enjoi,不过感觉转来转去似乎坑更多,于是最终还是选择了 手写 joi,使用 joi 验证,joi schema 转 swagger使用 joi-to-json-schema 文档的方案 最终的成果便是一个全新的 koa swagger 验证库诞生了:

koa-joi-swagger


使用方法

app.js
import { toSwaggerDoc, ui, mixedValidate } from '../../src'
import mixedDoc from './mixed-doc'
import Koa from 'koa'
import DecRouter from 'koa-dec-router'
import bodyparser from 'koa-bodyparser'

const app = new Koa()
这里使用 koa-dec-router 做路由,支持 oo + decorators + 自动挂载 的路由
const decRouter = DecRouter({
  controllersDir: `${__dirname}/controllers`,
})

app.use(bodyparser())

const swaggerDoc = toSwaggerDoc(mixedDoc)
// 将 swagger ui 挂载到 `/swagger`
app.use(ui(swaggerDoc, {pathRoot: '/swagger'}))

// handle validation errors
app.use(async (ctx, next) => {
  try {
    await next()
  } catch (e) {
    if (e.name === 'RequestValidationError') {
      ctx.status = 400
      ctx.body = { code: 1, message: e.message, data: e.data }
    } else if (e.name === 'ResponseValidationError') {
      ctx.status = 500
      ctx.body = { code: 1, message: e.message, data: e.data }
    }
  }
})

// 使用 mixedDoc 来验证 request 和 response
app.use(mixedValidate(mixedDoc, {
  onError: e => console.log(e.details, e._object),
}))

// koa-dec-router
app.use(decRouter.router.routes())
app.use(decRouter.router.allowedMethods())
app.listen(3456)

mixed-doc.js joi + swagger 混合文档, 基本就是 swagger 文档,只不是原来是 JSON schema 的 parameters 和 responses 全都替换成了 joi schema。

export default {
  swagger: '2.0',
  info: {
    title: 'Test API',
    description: 'Test API',
    version: '1.0.0',
  },
  //  the domain of the service
  //  host: 127.0.0.1:3457
  //  array of all schemes that your API supports
  schemes: ['https', 'http'],
  //  will be prefixed to all paths
  basePath: '/api/v1',
  consumes: ['application/x-www-form-urlencoded'],
  produces: ['application/json'],
  paths: {
    '/posts': {
      get: {
        summary: 'Some posts',
        tags: ['Post'],
        parameters: {
          query: Joi.object().keys({
            type: Joi.string().valid(['news', 'article']),
          }),
        },
        responses: {
          '200': {
            description: 'Post list',
            schema: Joi.object().keys({
              lists: Joi.array().items(Joi.object().keys({
                title: Joi.string().description('Post title'),
                content: Joi.string().required().description('Post content'),
              }))
            }),
          },
          'default': {
            description: 'Error happened',
            schema: Joi.object().json().keys({
              code: Joi.number().integer(),
              message: Joi.string(),
              data: Joi.object(),
            }),
          },
        }
      }
    },
  },
}

优势

  1. 直接用 js 写文档,方便复用,可以对所有的最终 schema 包装一层函数,比如:
function api(schema = Joi.object()) {
	return Joi.object().keys({
		code: Joi.number().integer(),
		data: schema,
	})
}
// mixed doc
// ...
responses: {
	'200': {
		description: 'Some data',
		schema: api(Joi.object().keys({
			title: Joi.string(),
			content: Joi.string(),
		}))
	}

2. 简单可靠,手写的 joi schema 直接拿来验证,不用担心 swagger 是否冲突,开发环境不会干扰生产环境 3. 无侵入性,验证直接挂载在全局的中间件中,无论是验证还是文档都不会侵入已有的代码,可以渐进添加。

希望大家能喜欢~ 🤓

2 回复

非常不错,支持一下,另外建议加一个可以直接跑起来的 sample

@rwing 代码的测试文件夹中就有能跑起来的 demo 已更新 readme

回到顶部