defined your api with simple decorators and generate api docs automatically
this docs is written for V2 and it's currently under development. refer to V1 Docs for V1 version
V2 version has undergone complete refactoring, introducing break change and new APIs to provide more type-safe functionality.
npm i koa-swagger-decorator@next
Developing your type-safe API using simple decorators and zod schema with auto-generated OpenAPI docs based on OpenAPI V3.
- use zod schema to define and validate Request/Response objects.
- use zod-to-openapi to convert zod schema into OpenAPI V3 schemas.
- use @koa/router to register routes & api handler to Koa application.
// define your api handler with @routeConfig decorator and it will generate OpenAPI Docs automatically
class UserController {
@routeConfig({ // define your API route info using @routeConfig decorator
method: "post",
path: "/users",
summary: "create a user",
tags: ["USER"],
operationId: "CreateUser",
})
@body(z.object({uid: z.string(), name: z.string(), age: z.number().min(18).optional()}))
async CreateUser(ctx: Context, args) {
// body params will be validated using zodSchema.parse(ctx.request.body)
// and assigned the parsed value to args.
console.log(args, args.body.uid, args.body.name);
ctx.body = { message: "create", id: "123" } as ICreateUserRes;
}
}
you can refer to example dir for details.
git clone https://github.com/Cody2333/koa-swagger-decorator.git
cd koa-swagger-decorator
npm i
npm run dev
// open http://localhost:3000/api/swagger-html to execute api
// open example dir for detail codes.
typescript is required. Please make sure compilerOptions is set correctly in tsconfig.json
// tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"esModuleInterop": true,
}
}
following below steps to integrate your koa application with koa-swagger-decorator
- Define your Request/Response with Zod Schema
// file -> ./schema/user.ts
import { z } from 'koa-swagger-decorator'
// define req/res schema
const CreateUserReq = z.object({
uid: z.string().nonempty(),
name: z.string().nullable().optional(),
age: z.number().min(18).nullable(),
operator: z.string().nonempty().optional(),
});
const CreateUserRes = z.object({
id: z.string().nullable(),
message: z.string().nullable(),
});
// export req/res typings
export type ICreateUserRes = z.infer<typeof CreateUserRes>;
export type ICreateUserReq = z.infer<typeof CreateUserReq>;
- Write your API Handler with decorator and zod schema
// file -> ./controller/user.ts
import { Context } from "koa";
import { body, responses, routeConfig } from "../../lib/decorator";
import {
CreateUserReq,
CreateUserRes,
} from "../schemas/user";
import { ParsedArgs, z } from "../../lib";
class UserController {
@routeConfig({ // define your API route info using @routeConfig decorator
method: "post",
path: "/users",
summary: "create a user",
tags: ["USER"],
operationId: "CreateUser",
})
@body(CreateUserReq)
@responses(CreateUserRes)
async CreateUser(ctx: Context, args: ParsedArgs<ICreateUserReq>) {
// args is injected with values = CreateUserReq.parse(ctx.request.body)
console.log(args, args.body.uid, args.body.name);
ctx.body = { message: "create", id: "123" } as ICreateUserRes;
}
}
export { UserController };
- Init SwaggerRouter Instance
// file -> ./routes/index.ts
import { SwaggerRouter } from 'koa-swagger-decorator'
import { UserController } from './controller/user'
const router = new SwaggerRouter({
spec: {
info: {
title: "Example API Server",
version: "v1.0",
},
},
swaggerHtmlEndpoint: '/swagger-html',
swaggerJsonEndpoint: '/swagger-json',
});
// apply swagger docs routes
router.swagger();
// register user defined routes implementation
router
.applyRoute(UserController)
// .applyRoute(DemoController); // chained for more then one controller imports
export {router}
- Init Koa Application
// file -> ./main.ts
import {router} from './routes/index'
const app = new Koa();
app
.use(cors())
.use(bodyParser())
.use(router.routes())
.use(router.allowedMethods());
export default app.listen(config.port, () => {
console.log(`App is listening on ${config.port}.`);
});
// running ts-node ./main.ts and that's all
@routeConfig({
method: "get",
path: "/user/{uid}",
summary: "get user by id",
description: "detailed user",
tags: ["USER"],
operationId: "GetUserById",
request: {
params: z.object({
uid: z.string().nonempty(), // path params, don't forget to add {uid} in [path] field.
}),
query: z.object({
count: z.coerce.number().default(10), // using z.coerce to convert query string into number & validate
limit: z.coerce.number().default(0),
})
},
})
define params by adding @body/@responses decorators to your handler
@routeConfig({
path: "/users/update",
method: "put",
tags: ["USER"],
operationId: "UpdateUser",
})
@body([ZodSchema])
@responses([ZodSchema])
const router = new SwaggerRouter({});
router.prefix("/api");
by calling router.prefix, your swagger docs routes & biz routes will automatically add prefix "/api". Open http://localhost:3000/api/swagger-html to get swagger docs.
using @middlewares decorator for your handler method
@middlewares([
async (ctx, next) => {
const x = ctx._swagger_decorator_meta as ItemMeta; // get swagger decorator meta info through ctx
console.log("biz mid", x.routeConfig);
await next();
},
])
- support request validation
- support response validation
- support middleware decorator
- support adding exist components to spec
- support generate openapi docs without starting app
- add unit test
- support form-data request
- support define non-200 responses
- support class decorators