Skip to content

Commit c9025fc

Browse files
committed
feat: support koa
1 parent dac4672 commit c9025fc

35 files changed

+3345
-238
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ import {
7979
generateDocument,
8080
generateSwaggerUI,
8181
getSwaggerUIAssetInfo,
82-
validateRequest,
82+
zodValidator,
8383
} from "api-morph";
8484
import express from "express";
8585
import { UpdateUserDto, UpdateUserVo, UserIdDto } from "./schema";
@@ -98,7 +98,7 @@ app.use(express.static(getSwaggerUIAssetInfo().assetPath));
9898
*/
9999
app.put(
100100
"/api/users/:id",
101-
validateRequest({ params: UserIdDto, body: UpdateUserDto }),
101+
zodValidator({ params: UserIdDto, body: UpdateUserDto }),
102102
(req, res) => {
103103
const { id } = req.params;
104104
const { email, username } = req.body;

ROADMAP.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
1-
- [ ] 设计一个API支持用户覆盖最终生成的文档
21
- [ ] 支持配置路径全局前缀,需要适配框架的写法,或者自己封装
32
- [ ] 模仿 nestjs 添加 addxxxAuth 方法
43
- [ ] 模仿 nestjs 添加 addGlobalResponse/addGlobalParameter 方法
54
- [ ] 更严格的校验(对 JSON Schema 进行校验)
6-
- [ ] 支持将handler写成独立的函数
7-
- [ ] 支持在注释中通过 @link 引用 TypeScript 类型并生成 schema
85
- [ ] 支持使用 new Router() 定义路由,并对tag分组,需要新设计 API
96
- [ ] 支持通过 ClassValidator 校验请求参数并生成 schema
107
- [ ] 适配其他 Node.js 框架
11-
- [ ] 支持编译时和运行时两种模式

examples/express/index.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { generateDocument } from "api-morph";
2+
import { setupSwaggerUI, zodValidator } from "api-morph/express";
3+
import express from "express";
4+
import { UpdateUserDto, UpdateUserVo, UserIdDto } from "./schema";
5+
6+
const app = express();
7+
8+
app.use(express.json());
9+
10+
/**
11+
* @summary 更新用户信息
12+
* @description 更新指定用户的个人信息
13+
* @tags users
14+
* @response 200 {@link UpdateUserVo} 更新用户信息成功
15+
*/
16+
app.put(
17+
"/api/users/:id",
18+
zodValidator({
19+
params: UserIdDto,
20+
body: UpdateUserDto,
21+
}),
22+
(req, res) => {
23+
const { id } = req.params;
24+
const { email, username } = req.body;
25+
26+
res.json({
27+
id,
28+
email,
29+
username,
30+
});
31+
},
32+
);
33+
34+
// 生成 OpenAPI 文档
35+
const openapi = await generateDocument(
36+
{
37+
info: {
38+
version: "1.0.0",
39+
title: "用户管理 API",
40+
description: "这是一个用户管理 API 的文档示例",
41+
},
42+
},
43+
{
44+
parserOptions: {
45+
include: ["examples/express/**/*.ts"],
46+
},
47+
},
48+
);
49+
50+
// 提供 OpenAPI JSON 文档
51+
app.get("/openapi.json", (_req, res) => {
52+
res.json(openapi);
53+
});
54+
55+
// 提供 Swagger UI 界面
56+
setupSwaggerUI("/swagger-ui", app);
57+
58+
const port = 3000;
59+
app.listen(port, () => {
60+
console.log(`Example app listening on port ${port}`);
61+
console.log(`访问 http://localhost:${port}/swagger-ui 查看 API 文档`);
62+
});

examples/express/schema.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { z } from "zod/v4";
2+
3+
export const UserIdDto = z.object({
4+
id: z.string().meta({ description: "用户ID" }),
5+
});
6+
7+
export const UpdateUserDto = z.object({
8+
email: z.email().meta({
9+
description: "用户邮箱地址",
10+
examples: ["[email protected]"],
11+
}),
12+
username: z
13+
.string()
14+
.min(3)
15+
.max(50)
16+
.meta({
17+
description: "用户名",
18+
examples: ["John Doe"],
19+
}),
20+
});
21+
22+
export const UpdateUserVo = z.object({
23+
id: z.string().meta({ description: "用户ID" }),
24+
email: z.email().meta({
25+
description: "用户邮箱地址",
26+
examples: ["[email protected]"],
27+
}),
28+
username: z
29+
.string()
30+
.min(3)
31+
.max(50)
32+
.meta({
33+
description: "用户名",
34+
examples: ["John Doe"],
35+
}),
36+
});

examples/koa/index.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import Router from "@koa/router";
2+
import { generateDocument } from "api-morph";
3+
import { setupSwaggerUI, zodValidator } from "api-morph/koa";
4+
import Koa from "koa";
5+
import { UpdateUserDto, UpdateUserVo, UserIdDto } from "./schema";
6+
7+
const app = new Koa();
8+
const router = new Router();
9+
10+
/**
11+
* @summary 更新用户信息
12+
* @description 更新指定用户的个人信息
13+
* @tags users
14+
* @response 200 {@link UpdateUserVo} 更新用户信息成功
15+
*/
16+
router.put(
17+
"/api/users/:id",
18+
zodValidator({
19+
params: UserIdDto,
20+
body: UpdateUserDto,
21+
}),
22+
(ctx) => {
23+
const { id } = ctx.params;
24+
const { email, username } = ctx.request.body;
25+
26+
ctx.body = {
27+
id,
28+
email,
29+
username,
30+
};
31+
},
32+
);
33+
34+
app.use(router.routes()).use(router.allowedMethods());
35+
36+
// 生成 OpenAPI 文档
37+
const openapi = await generateDocument(
38+
{
39+
info: {
40+
version: "1.0.0",
41+
title: "用户管理 API",
42+
description: "这是一个用户管理 API 的文档示例",
43+
},
44+
},
45+
{
46+
parserOptions: {
47+
include: ["examples/koa/**/*.ts"],
48+
},
49+
},
50+
);
51+
52+
// 提供 OpenAPI JSON 文档
53+
router.get("/openapi.json", (ctx) => {
54+
ctx.body = openapi;
55+
});
56+
57+
// 提供 Swagger UI 界面
58+
setupSwaggerUI("/swagger-ui", app);
59+
60+
const port = 3000;
61+
app.listen(port, () => {
62+
console.log(`Example app listening on port ${port}`);
63+
console.log(`访问 http://localhost:${port}/swagger-ui 查看 API 文档`);
64+
});

examples/koa/schema.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { z } from "zod/v4";
2+
3+
export const UserIdDto = z.object({
4+
id: z.string().meta({ description: "用户ID" }),
5+
});
6+
7+
export const UpdateUserDto = z.object({
8+
email: z.email().meta({
9+
description: "用户邮箱地址",
10+
examples: ["[email protected]"],
11+
}),
12+
username: z
13+
.string()
14+
.min(3)
15+
.max(50)
16+
.meta({
17+
description: "用户名",
18+
examples: ["John Doe"],
19+
}),
20+
});
21+
22+
export const UpdateUserVo = z.object({
23+
id: z.string().meta({ description: "用户ID" }),
24+
email: z.email().meta({
25+
description: "用户邮箱地址",
26+
examples: ["[email protected]"],
27+
}),
28+
username: z
29+
.string()
30+
.min(3)
31+
.max(50)
32+
.meta({
33+
description: "用户名",
34+
examples: ["John Doe"],
35+
}),
36+
});

package.json

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
"swagger-generator",
2222
"express-openapi",
2323
"express-swagger",
24+
"koa-openapi",
25+
"koa-swagger",
2426
"zod-openapi",
2527
"zod-swagger"
2628
],
@@ -34,6 +36,11 @@
3436
"types": "./dist/express.d.ts",
3537
"import": "./dist/express.js",
3638
"require": "./dist/express.js"
39+
},
40+
"./koa": {
41+
"types": "./dist/koa.d.ts",
42+
"import": "./dist/koa.js",
43+
"require": "./dist/koa.js"
3744
}
3845
},
3946
"main": "./dist/index.js",
@@ -67,9 +74,12 @@
6774
},
6875
"dependencies": {
6976
"@hyperjump/json-schema": "^1.16.0",
77+
"@types/koa-static": "^4.0.4",
7078
"deepmerge": "^4.3.1",
79+
"koa-static": "^5.0.0",
7180
"mime-types": "^3.0.1",
7281
"radashi": "^12.6.0",
82+
"supertest": "^7.1.1",
7383
"swagger-ui-dist": "^5.25.3",
7484
"ts-morph": "^26.0.0",
7585
"typescript": "^5.8.3",
@@ -81,6 +91,7 @@
8191
"@oxc-node/core": "^0.0.29",
8292
"@types/mime-types": "^3.0.1",
8393
"@types/node": "^24.0.4",
94+
"@types/supertest": "^6.0.3",
8495
"@vitest/coverage-v8": "^3.2.4",
8596
"lefthook": "^1.11.14",
8697
"nanoid": "^5.1.5",
@@ -95,7 +106,11 @@
95106
"vue": "^3.5.17"
96107
},
97108
"peerDependencies": {
109+
"@koa/router": "^13.0.0",
98110
"@types/express": "^5.0.0",
99-
"express": "^5.0.0"
111+
"@types/koa": "^2.0.0",
112+
"@types/koa__router": "^12.0.0",
113+
"express": "^5.0.0",
114+
"koa": "^3.0.0"
100115
}
101116
}

0 commit comments

Comments
 (0)