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;

image

至此,你的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);

image

其它

白名单

如果你有鉴权,记得将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基础使用

posted @ 2025-08-04 23:09  丁少华  阅读(27)  评论(0)    收藏  举报