Declarative vs Chained
to-openapi provides two APIs that produce identical output: the openapi() function and the OpenAPI class. This page helps you choose the right one and shows how to use each effectively.
openapi() — Declarative
Define your entire API in a single object. All routes, schemas, and metadata go in one place.
import { openapi } from "to-openapi";
import { z } from "zod";
const PetSchema = z.object({
id: z.number(),
name: z.string(),
});
const ErrorSchema = z.object({
message: z.string(),
code: z.string(),
});
const doc = openapi({
info: { title: "Pet Store", version: "1.0.0" },
schemas: {
Pet: PetSchema,
Error: ErrorSchema,
},
paths: {
"GET /pets": {
summary: "List all pets",
tags: ["Pets"],
200: PetSchema,
},
"POST /pets": {
summary: "Create a pet",
tags: ["Pets"],
body: PetSchema,
201: PetSchema,
400: ErrorSchema,
},
"GET /pets/{id}": {
summary: "Get a pet",
tags: ["Pets"],
200: PetSchema,
404: ErrorSchema,
},
},
});Route keys combine the HTTP method and path in a single string: "GET /pets", "POST /pets/{id}". This keeps route definitions compact and scannable.
OpenAPI — Builder
Build your API incrementally with method calls. Routes and schemas are registered one at a time.
import { OpenAPI } from "to-openapi";
import { z } from "zod";
const PetSchema = z.object({
id: z.number(),
name: z.string(),
});
const ErrorSchema = z.object({
message: z.string(),
code: z.string(),
});
const api = new OpenAPI({
info: { title: "Pet Store", version: "1.0.0" },
});
api.schema("Pet", PetSchema);
api.schema("Error", ErrorSchema);
api.route("get", "/pets", {
summary: "List all pets",
tags: ["Pets"],
200: PetSchema,
});
api.route("post", "/pets", {
summary: "Create a pet",
tags: ["Pets"],
body: PetSchema,
201: PetSchema,
400: ErrorSchema,
});
api.route("get", "/pets/{id}", {
summary: "Get a pet",
tags: ["Pets"],
200: PetSchema,
404: ErrorSchema,
});
const doc = api.document();All methods return this, so you can chain them:
const doc = new OpenAPI({ info: { title: "Pet Store", version: "1.0.0" } })
.schema("Pet", PetSchema)
.schema("Error", ErrorSchema)
.route("get", "/pets", { 200: PetSchema })
.route("post", "/pets", { body: PetSchema, 201: PetSchema })
.document();When to Use Which
| Scenario | Recommended |
|---|---|
| All routes known at startup | openapi() |
| Routes registered across multiple files or modules | OpenAPI class |
| Config-driven or static specs | openapi() |
| Framework middleware that registers routes dynamically | OpenAPI class |
| Quick prototyping | openapi() |
| Large API with routes grouped by domain | OpenAPI class |
Use openapi() when routes are defined in one place
The declarative style works well when your entire API is visible in a single file. You get a complete picture of every route and schema at a glance.
Use OpenAPI when routes are spread across modules
The builder style shines when different parts of your codebase register their own routes. Pass the OpenAPI instance to each module and let them call .route() independently:
// pets.ts
export function registerPetRoutes(api: OpenAPI) {
api.route("get", "/pets", { 200: PetSchema });
api.route("post", "/pets", { body: CreatePetSchema, 201: PetSchema });
}
// users.ts
export function registerUserRoutes(api: OpenAPI) {
api.route("get", "/users", { 200: UserSchema });
api.route("post", "/users", { body: CreateUserSchema, 201: UserSchema });
}
// main.ts
const api = new OpenAPI({ info: { title: "API", version: "1.0.0" } });
registerPetRoutes(api);
registerUserRoutes(api);
const doc = api.document();Same Output
Both APIs use the same internal pipeline: route shorthand expansion, schema resolution, $ref deduplication, plugin hooks, and document assembly. The output is identical given the same inputs.
import { openapi, OpenAPI } from "to-openapi";
// These produce the same document
const a = openapi({
info: { title: "API", version: "1.0.0" },
schemas: { Pet: PetSchema },
paths: { "GET /pets": { 200: PetSchema } },
});
const b = new OpenAPI({ info: { title: "API", version: "1.0.0" } })
.schema("Pet", PetSchema)
.route("get", "/pets", { 200: PetSchema })
.document();Same Route Shorthand
Both APIs accept the same route shorthand format for defining operations. Request parameters, bodies, responses, tags, security, and vendor extensions work identically regardless of which API you use.
Related
- Getting Started -- quick start with both APIs
- Route Shorthand -- the full route definition format
- API Reference: openapi() -- complete function signature
- API Reference: OpenAPI class -- complete class interface