API Docs#
Auto-generate OpenAPI 3.0 documentation from your Zod validation schemas and serve a full-featured interactive API client.
Quick Start#
1. Enable docs in your app#
typescript
import { Buntok } from "@buntok/core";
const app = new Buntok();
app.apiDocs({
path: "/docs",
title: "My API",
version: "1.0.0",
description: "My awesome API documentation",
safeOnProduction: true, // hides docs when NODE_ENV=production
});
app.listen(1212);
// Visit http://localhost:1212/docs
// swagger.json is auto-generated in the background on startupInfo
swagger.json is automatically generated in the background when the server starts. No need to run buntok make:docs manually. The generation runs via setImmediate() and does not affect API response time.Info
Both
/docs and /docs/ (with trailing slash) work. Asset paths (CSS/JS) are automatically resolved to the correct absolute paths, so the docs UI loads correctly regardless of how the URL is accessed.Options#
| Option | Type | Default | Description |
|---|---|---|---|
| path | string | "/docs" | URL path for the docs UI |
| title | string | "API Documentation" | API title shown in the docs UI and swagger.json |
| version | string | "1.0.0" | API version shown in the docs UI and swagger.json |
| description | string | undefined | API description shown in the docs UI and swagger.json |
| safeOnProduction | boolean | false | When true, docs routes are not registered when NODE_ENV=production |
Annotating Routes#
Use zValidator and zResponse in your route handlers. The CLI picks them up automatically.
Body Validation#
typescript
import { z } from "@buntok/core/middlewares/validator";
const CreateUserSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
role: z.enum(["admin", "user"]).default("user"),
});
app.post(
"/users",
zValidator("body", CreateUserSchema),
async (ctx) => {
const data = ctx.valid("body");
// ...
}
);Query & Params Validation#
typescript
// Query params
app.get(
"/users",
zValidator("query", z.object({
page: z.coerce.number().int().default(1),
limit: z.coerce.number().int().default(10),
search: z.string().optional(),
})),
async (ctx) => {
const { page, limit } = ctx.valid("query");
// ...
}
);
// Path params
app.get(
"/users/:id",
zValidator("params", z.object({
id: z.string().uuid(),
})),
async (ctx) => {
const { id } = ctx.valid("params");
// ...
}
);Documenting Responses#
typescript
const UserSchema = z.object({
id: z.string().uuid(),
name: z.string(),
email: z.string().email(),
role: z.string(),
createdAt: z.string(),
});
app.get(
"/users/:id",
zValidator("params", z.object({ id: z.string().uuid() })),
zResponse(200, UserSchema, "User found"),
zResponse(404, z.object({}), "User not found"),
async (ctx) => {
const user = await userRepo.findById(ctx.valid("params").id);
if (!user) return ctx.error("Not found", 404);
return ctx.json(user);
}
);Full Example#
typescript
import { Buntok } from "@buntok/core";
import { z } from "@buntok/core/middlewares/validator";
const app = new Buntok();
// ─── Schemas ───────────────────────────────────────
const CreateUserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
});
const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string(),
createdAt: z.string(),
});
// ─── Routes ────────────────────────────────────────
app.get(
"/users",
zValidator("query", z.object({
page: z.coerce.number().default(1),
limit: z.coerce.number().default(10),
})),
zResponse(200, z.array(UserSchema), "List of users"),
async (ctx) => {
const users = await userRepo.findAll();
return ctx.json(users);
}
);
app.post(
"/users",
zValidator("body", CreateUserSchema),
zResponse(201, UserSchema, "User created"),
async (ctx) => {
const user = await userRepo.create(ctx.valid("body"));
return ctx.json(user, 201);
}
);
app.get(
"/users/:id",
zValidator("params", z.object({ id: z.string() })),
zResponse(200, UserSchema, "User found"),
async (ctx) => {
const user = await userRepo.findById(ctx.valid("params").id);
if (!user) return ctx.error("Not found", 404);
return ctx.json(user);
}
);
// ─── Enable API docs ───────────────────────────────
app.apiDocs({
path: "/docs",
title: "User Management API",
version: "1.0.0",
safeOnProduction: true,
});
app.listen(1212);
// Visit: http://localhost:1212/docs
// swagger.json is auto-generated in the background on startupProduction Safety#
Use safeOnProduction: true to automatically hide the docs UI when NODE_ENV=production. This prevents exposing your API documentation in production environments.
typescript
app.apiDocs({
path: "/docs",
title: "My API",
safeOnProduction: true, // disabled when NODE_ENV=production
});Warning
When
safeOnProduction is true and NODE_ENV=production, the docs routes return 404. Make sure your deployment sets NODE_ENV=production.Tips#
- Routes without
zValidatorare still registered in the spec (no request/response schema) - If a schema type is unsupported, that route is skipped with a warning. Annotate with
.openapi()from@asteasolutions/zod-to-openapi - The
zResponseschema is wrapped in the standard Buntok envelope:{ success, message, data } - The interactive docs UI includes request testing, code snippet generation, cookie management, and schema-powered body previews
Info
swagger.json is auto-generated when you call app.listen(). For manual regeneration (e.g., CI/CD), run buntok make:docs - it imports your src/index.ts and generates the file.