openapi-ts-hono
v0.1.4
Published
Type-check Hono apps against OpenAPI paths generated by openapi-typescript
Downloads
1,857
Readme
openapi-ts-hono
Type-check a Hono app against an OpenAPI paths type generated by openapi-typescript.
openapi-ts-hono keeps normal Hono route definitions as the authoring API, then
uses TypeScript to catch drift from your OpenAPI contract. This is especially
useful when humans and AI coding agents both edit handlers: they can keep writing
plain Hono code, while mismatched paths, params, validators, status codes, content
types, and response bodies surface as readable compiler errors, with no runtime overhead.
Installation
pnpm add hono
pnpm add -D openapi-ts-hono openapi-typescript typescriptWhen using openapi-ts-hono, include the DOM library in your TypeScript
configuration:
{
"compilerOptions": {
"lib": ["ESNext", "DOM"]
}
}Usage
defineApp is a type-level assertion. It returns the same Hono app at runtime,
but TypeScript checks that the app implements the routes described by the
OpenAPI paths type.
Assume openapi-typescript generated a paths type like this:
export interface paths {
"/users/{id}": {
get: {
parameters: {
path: { id: string };
};
responses: {
200: {
content: {
"application/json": { id: string; name: string };
};
};
};
};
};
}Then a matching Hono app type-checks:
import { Hono } from "hono";
import { defineApp } from "openapi-ts-hono";
// generated by openapi-typescript
import type { paths } from "./openapi-types";
const app = defineApp<paths>()(
new Hono().get("/users/:id", (c) => {
return c.json({ id: c.req.param("id"), name: "Alice" });
}),
);If you prefer to reuse the OpenAPI binding, keep the curried function:
const defineAppWithPaths = defineApp<paths>();
const app = defineAppWithPaths(
new Hono().get("/users/:id", (c) => {
return c.json({ id: c.req.param("id"), name: "Alice" });
}),
);For sub apps, pass the mounted base path as the second type parameter:
const userApp = defineApp<paths, "/users">()(
new Hono().get("/:id", (c) => {
return c.json({ id: c.req.param("id"), name: "Alice" });
}),
);
const routedApp = defineApp<paths>()(new Hono().route("/users", userApp));
export default app;Extra routes are allowed. The check focuses on whether the OpenAPI routes are implemented with compatible path parameters, validated inputs, response status codes, content types, and response bodies.
Allowing middleware-handled response statuses
By default, every response status in the OpenAPI operation must be represented
by the route handler. If a middleware handles common error responses such as
404 or 500, mark those statuses as optional with the type-level
WithOptionalResponseStatuses helper:
import { Hono } from "hono";
import { defineApp, type WithOptionalResponseStatuses } from "openapi-ts-hono";
import type { paths } from "./openapi-types";
type AppPaths = WithOptionalResponseStatuses<paths, 404 | 500>;
const app = defineApp<AppPaths>()(
new Hono().get("/users/:id", (c) => {
return c.json({ id: c.req.param("id"), name: "Alice" });
}),
);Error Examples
If the app does not conform to the OpenAPI schema, TypeScript raises an error on
the defineApp<paths>()(...) call. The error contains a readable key that points
to the mismatch.
Missing route or method:
defineApp<paths>()(
new Hono().post("/users/:id", (c) => {
return c.json({ id: c.req.param("id"), name: "Alice" });
}),
);
// Type error includes:
// "GET /users/:id is missing"Path parameter name mismatch:
defineApp<paths>()(
new Hono().get("/users/:userId", (c) => {
return c.json({ id: c.req.param("userId"), name: "Alice" });
}),
);
// Type error includes:
// "Path input mismatch at GET /users/:id"Response body mismatch:
defineApp<paths>()(
new Hono().get("/users/:id", (c) => {
return c.json({ userId: c.req.param("id") });
}),
);
// Type error includes:
// "Output mismatch at GET /users/:id for 200 application/json"Required JSON request body without a validator:
type createUserPaths = {
"/users": {
post: {
requestBody: {
content: {
"application/json": { name: string };
};
};
responses: {
201: {
content: {
"application/json": { id: string; name: string };
};
};
};
};
};
};
defineApp<createUserPaths>()(
new Hono().post("/users", (c) => {
return c.json({ id: "1", name: "Alice" }, 201);
}),
);
// Type error includes:
// "JSON input mismatch at POST /users"Optional request bodies do not require a validator, but required request bodies do. Add a Hono validator middleware when the OpenAPI operation requires JSON, form, query, header, or cookie input.
License
MIT License
