Document a Koa API with an OpenAPI Spec and Swagger UI
OpenAPI Documentation
Write an OpenAPI 3.1 specification for a Koa REST API and serve an interactive Swagger UI from the same application using koa-swagger-ui.
What you'll learn
- Structure an OpenAPI 3.1 document describing your Koa API's paths and schemas
- Serve the raw spec as JSON from a dedicated Koa route
- Mount Swagger UI so developers can explore and test the API in a browser
An OpenAPI specification is a machine-readable contract that describes every endpoint, parameter, request body, and response schema in your API. Tools can generate client SDKs, mock servers, and interactive documentation from it.
Install Dependencies
npm install koa-swagger-ui No code-generation library is required — you can write the spec as a plain JS object and let Koa serve it.
Write the OpenAPI Document
Create src/openapi.js and export the spec:
// src/openapi.js
export const spec = {
openapi: "3.1.0",
info: {
title: "Articles API",
version: "1.0.0",
description: "A simple Koa REST API for managing articles.",
},
paths: {
"/articles": {
get: {
summary: "List all articles",
parameters: [
{ name: "limit", in: "query", schema: { type: "integer", default: 20 } },
{ name: "offset", in: "query", schema: { type: "integer", default: 0 } },
],
responses: {
"200": {
description: "Paginated list of articles",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ArticleList" },
},
},
},
},
},
post: {
summary: "Create an article",
requestBody: {
required: true,
content: {
"application/json": {
schema: { $ref: "#/components/schemas/CreateArticle" },
},
},
},
responses: {
"201": { description: "Created" },
"422": { description: "Validation failed" },
},
},
},
},
components: {
schemas: {
Article: {
type: "object",
properties: {
id: { type: "integer" },
title: { type: "string" },
body: { type: "string" },
},
},
ArticleList: {
type: "object",
properties: {
data: { type: "array", items: { $ref: "#/components/schemas/Article" } },
total: { type: "integer" },
limit: { type: "integer" },
offset: { type: "integer" },
},
},
CreateArticle: {
type: "object",
required: ["title", "body"],
properties: {
title: { type: "string", minLength: 1, maxLength: 200 },
body: { type: "string", minLength: 1 },
},
},
},
},
}; Serve the Spec as JSON
Add a route that returns the spec so tools can fetch it at a stable URL:
import Router from "@koa/router";
import { spec } from "./openapi.js";
const docsRouter = new Router();
docsRouter.get("/openapi.json", async (ctx) => {
ctx.type = "application/json";
ctx.body = spec;
}); Mount Swagger UI
koa-swagger-ui serves the HTML explorer and points it at your spec URL:
import { koaSwagger } from "koa-swagger-ui";
app.use(
koaSwagger({
routePrefix: "/docs", // browse to http://localhost:3000/docs
swaggerOptions: {
url: "/openapi.json", // spec URL served above
},
})
); Mount the docs router and Swagger UI before your API routers:
app.use(docsRouter.routes());
app.use(docsRouter.allowedMethods());
app.use(articlesRouter.routes());
app.use(articlesRouter.allowedMethods()); Keeping the Spec in Sync
For larger APIs, consider generating the spec from your Zod schemas using
zod-to-openapi — the schema is the single source of truth, and the OpenAPI
document is derived from it automatically.
| Tool | Approach | Best for |
|---|---|---|
| Hand-written YAML/JS | Spec-first | Small, stable APIs |
| zod-to-openapi | Code-first via Zod | Teams already using Zod validation |
| koa-swagger-decorator | Decorator annotations | TypeScript / class-based controllers |
Up Next
With a fully documented REST API in place, the next section introduces authentication — protecting these endpoints with JWTs and session cookies.
Authentication Overview →