koa中使用prisma与swagger
prisma-openapi
prisma-openapi 是一个 Prisma 官方生态中的生成器插件,
在ORM生成模型的基础上,还会生成对应的 OpenAPI 3 规范文档(JSON/YAML)和 JSDoc 注释,无需手写任何额外代码。
在 schema.prisma 内(建议顶部)加上一行代码如下
generator openapi {
provider = "prisma-openapi"
output = "./openapi"
generateJsDoc = "true"
}
然后再次运行生成命令 npx prisma generate
,即可看到项目的prisma
目录下多了一层 openapi
|--prisma
|--openapi
|--openapi.js
|--openapi.yaml
好了,这就够了,这份 openApi的文档就留着备用!
定义接口
为了测试我们定义一些接口
root-router.ts
import Router from '@koa/router';
import { queryOne } from '@/service/user';
import reqCtx from '@/middleware/req-ctx';
import JsonResult from '@/utils/json-result';
import { login } from '@/service/root';
const router = new Router({ prefix: '/api' });
/**
* @swagger
* /login:
* get:
* summary: 登录
* description: 登录接口
* requestBody:
* required: true
* content:
* application/json:
* schema:
* properties:
* email:
* type: string
* description: desc
* example: abc@xx.com
*/
router.post('/login', async (ctx) => {
const bodyParams = ctx.request.body;
const token = await login(bodyParams);
const userId = reqCtx.get('userId');
const me = await queryOne({ id: userId });
ctx.body = JsonResult.success({ token, me });
});
export default router;
user-router.ts
import _ from 'lodash';
import Router from '@koa/router';
import JsonResult from '@/utils/json-result';
import { queryOne } from '@/service/user';
const router = new Router({ prefix: '/api/user' });
/**
* @swagger
* /user:
* get:
* description: 获取单个用户接口
* summary: 用户
* tags:
* - 用户
* properties:
* id:
* type: string
* responses:
* 200:
* description: 成功返回用户
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/user'
*/
router.get('/', async (ctx) => {
const query = ctx.query as any;
query.id && (query.id = Number(query.id));
const res = await queryOne(query);
ctx.body = res ? JsonResult.success(res) : JsonResult.failed('未找到数据');
});
export default router;
swagger-jsdoc
swagger-jsdoc 是根据代码里的 JSDoc 注释收集起来,生成一份完整的 Swagger 文档,说白了就是 js版本的swagger的实现!
安装改插件后,写一个生成脚本,运行此脚本就会扫描 apis中的文件(中的jsdoc部分),并在根目录中生成 swagger.json,也就是Swagger 文档!
import fs from "fs";
import path from 'path';
import swaggerJsdoc from 'swagger-jsdoc';
const options = {
definition: {
openapi: '3.1.0',
info: {
title: 'Hello World',
version: '1.0.0',
description: 'A sample API',
},
host: "localhost:3003", // the host or url of the app
basePath: "/swagger", // the basepath of your endpoint
},
apis:['**/router/module/*.ts', '**/prisma/openapi/openapi.js'], // 这里就用到了上一步生成的文件 openapi.js
};
const res = swaggerJsdoc(options);
const swaggerPath = path.resolve(process.cwd(), 'swagger.json');
fs.writeFileSync(swaggerPath, JSON.stringify(res, null, 2), 'utf8');
当然,如果你的路由中的jsdoc经常变化,可以将其封装为接口,便于每次访问都是最新的(扫描)
import _ from 'lodash';
import Router from '@koa/router';
import swaggerJsdoc from 'swagger-jsdoc';
const router = new Router({ prefix: '/api/swagger' });
router.get('/doc', async (ctx) => {
const options = {
definition: {
openapi: '3.1.0',
info: {
title: 'Hello World',
version: '1.0.0',
description: 'A sample API',
},
host: "localhost:3003", // the host or url of the app
basePath: "/swagger", // the basepath of your endpoint
},
apis: ['**/router/module/*.ts', '**/prisma/openapi/openapi.js'],
};
const res = swaggerJsdoc(options);
ctx.body = res;
});
export default router;
至此,你的swagger文档,已经产出了,也就是说工作已经完成了!
此时的你可以给前端,比如让它根据这个生成前端api的封装等等操作!
koa2-swagger-ui
上边不是说都结束了嘛,怎么又开始了?!
听我辩解:如果你把这个给前端,前端肯定不愿意看,纯 json 可读性太差,所以我们还需一套漂亮的ui界面!
koa2-swagger-ui 就是做这个的,它结合你上一步生成 swagger文档,以优美的界面展示!
它是个中间件,我们直接使用即可!
import Koa from 'koa';
import { koaSwagger } from 'koa2-swagger-ui';
const app = new Koa();
app.use(
koaSwagger({
routePrefix: '/api/swagger', // ui路由地址
swaggerOptions: {
url: '/api/swagger/doc', // 这里需要放入 swagger文档(接口和静态静态文件都可以)
},
}),
);
app.listen(3003);
其它
白名单
如果你有鉴权,记得将swagger作为白名单放行
/^\/api\/swagger($|\/.*)/
更懒人的方式
不知道你发现没有:即便我没有jsdoc,但是根据已有条件,已经很清楚:路由地址、路由请求方式、以及返回类型。
import { user as User } from '@prisma/client';
router.get('/', async (ctx: {query:{id:string}}):Promise<User> => {
const query = ctx.query as any;
query.id && (query.id = Number(query.id));
const res = await queryOne(query);
ctx.body = res ? JsonResult.success(res) : JsonResult.failed('未找到数据');
});
那我为何还要必须还再写一遍jsdoc吗,没有插件能够完善吗
import { user as User } from '@prisma/client';
/**
* @swagger
* /user:
* get:
* description: 获取单个用户接口
* summary: 用户
* tags:
* - 用户
* properties:
* id:
* type: string
* responses:
* 200:
* description: 成功返回用户
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/user'
*/
router.get('/', async (ctx: {query:{id:string}}):Promise<User> => {
const query = ctx.query as any;
query.id && (query.id = Number(query.id));
const res = await queryOne(query);
ctx.body = res ? JsonResult.success(res) : JsonResult.failed('未找到数据');
});
答案是,目前(2025)没有任何官方或社区插件能把「Koa 路由文件里的 TS 类型 + 具体实现」直接反向推导成完整的 OpenAPI 文档。
但是我们仍然可选“半自动”方案tsoa,它支持koa、express等主流就架。
只需要利用ts的少量注解,即可完成,就像springBoot那样!
确定就是它有固定的写法,你不能再像以前那样随心所欲的写了
import { queryOne } from "@/service/user";
import { user as User } from '@prisma/client';
import { Controller, Get, Path, Route, } from "tsoa";
@Route("user")
export class UsersController extends Controller {
@Get("{userId}")
public async getUser(@Path() userId: number): Promise<User> {
return queryOne({ id: userId });
}
}
写完之后,运行 tsoa spec
生成静态的openApi文档,或 generateSpec()
运行时动态获取,直接喂给 Swagger UI 即可。
具体用法,我这里有总结:tsoa基础使用