Configuration
The openapi() function takes your routes and a config object, and returns spec and docs route handlers.
import { setup, route } from "@hectoday/http";
import { openapi } from "@hectoday/openapi";
const routes = [
/* ... your routes ... */
];
const api = openapi(routes, {
info: {
title: "Book Catalog API",
version: "2.0.0",
description: "A catalog of books, authors, and reviews.",
},
servers: [
{ url: "http://localhost:3000", description: "Development" },
{ url: "https://api.example.com", description: "Production" },
],
security: [{ bearerAuth: [] }],
securitySchemes: {
bearerAuth: {
type: "http",
scheme: "bearer",
bearerFormat: "JWT",
},
},
tags: [
{ name: "Books", description: "Book catalog management" },
{ name: "Reviews", description: "Book reviews and ratings" },
],
});
const app = setup({
routes: [
...routes,
api.spec(route), // serves /openapi.json
api.docs(route), // serves /docs (Scalar UI)
],
}); Function signature
openapi(routes: RouteDescriptor[], config: OpenApiConfig): OpenApiResult The first argument is your routes array. The function reads the Zod schemas on each route to generate the spec. The second argument is the config.
Return value
interface OpenApiResult {
spec: (route) => RouteDescriptor; // GET /openapi.json
docs: (route) => RouteDescriptor; // GET /docs (Scalar UI)
} Both methods take the route object and return a route descriptor that you add to your routes array.
Config options
info
Type: { title: string; version: string; description?: string; license?: { name: string; url?: string } } — Required
API metadata. Appears at the top of the generated spec and documentation UI.
info: {
title: "Book Catalog API",
version: "2.0.0",
description: "A catalog of books, authors, and reviews.",
}, servers
Type: Array<{ url: string; description?: string }> — Optional
Base URLs for the API. Documentation UIs show a dropdown to select the server.
servers: [
{ url: "http://localhost:3000", description: "Development" },
{ url: "https://staging-api.example.com", description: "Staging" },
{ url: "https://api.example.com", description: "Production" },
], security
Type: Array<Record<string, string[]>> — Optional
Default security requirements for all endpoints.
security: [{ bearerAuth: [] }], securitySchemes
Type: Record<string, SecurityScheme> — Optional
Authentication scheme definitions.
securitySchemes: {
bearerAuth: {
type: "http",
scheme: "bearer",
bearerFormat: "JWT",
description: "JWT token from POST /auth/login",
},
apiKey: {
type: "apiKey",
in: "header",
name: "X-API-Key",
},
}, tags
Type: Array<{ name: string; description?: string }> — Optional
Grouping labels for endpoints. The order determines display order in the documentation UI.
tags: [
{ name: "Books", description: "Book catalog management" },
{ name: "Reviews", description: "Book reviews and ratings" },
{ name: "Auth", description: "Authentication endpoints" },
], specPath
Type: string — Default: "/openapi.json"
The URL path where the spec is served.
openapi(routes, {
info: { title: "My API", version: "1.0.0" },
specPath: "/api/spec.json",
}); docsPath
Type: string — Default: "/docs"
The URL path where the Scalar documentation UI is served.
openapi(routes, {
info: { title: "My API", version: "1.0.0" },
docsPath: "/reference",
}); Generated spec
The generated spec follows OpenAPI 3.1. Zod schemas on request are converted to JSON Schema for the spec. Path parameters in the :param format are converted to the OpenAPI {param} format.
Routes without any schemas still appear in the spec with a default 200 OK response.
Zod to JSON Schema mapping
| Zod | JSON Schema |
|---|---|
z.string() | { "type": "string" } |
z.string().min(1).max(200) | { "type": "string", "minLength": 1, "maxLength": 200 } |
z.url() | { "type": "string", "format": "uri" } |
z.uuid() | { "type": "string", "format": "uuid" } |
z.email() | { "type": "string", "format": "email" } |
z.number() | { "type": "number" } |
z.number().int() | { "type": "integer" } |
z.number().min(1).max(5) | { "type": "number", "minimum": 1, "maximum": 5 } |
z.boolean() | { "type": "boolean" } |
z.enum(["a", "b"]) | { "type": "string", "enum": ["a", "b"] } |
z.array(z.string()) | { "type": "array", "items": { "type": "string" } } |
z.object({ ... }) | { "type": "object", "properties": { ... }, "required": [...] } |
.optional() | Field not in required array |
.nullable() | { "type": ["string", "null"] } |
.default("x") | { "default": "x" } |