API Development with Hono + OpenAPI
Quick Guide: Use Hono with@hono/zod-openapifor type-safe REST APIs that auto-generate OpenAPI specs. Importzfrom@hono/zod-openapi(NOT fromzod) so.openapi()is available on all schemas. Always includeoperationIdin routes and export theappinstance for spec generation.
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)
(You MUST import z from @hono/zod-openapi, NOT from zod -- this gives Zod the .openapi() method)
(You MUST export the app instance for OpenAPI spec generation)
(You MUST include operationId in every route for clean client generation)
</critical_requirements>
Auto-detection: Hono, @hono/zod-openapi, OpenAPIHono, createRoute, Zod schemas with .openapi(), app.route(), createMiddleware, rate limiting, CORS configuration, health checks, hc client, RPC mode, getContext, tryGetContext, contextStorage, some/every/except middleware
When to use:
- Building type-safe REST APIs with auto-generated OpenAPI specs
- Defining OpenAPI specifications with automatic Zod validation
- Creating standardized error responses with proper status codes
- Implementing filtering, pagination, and sorting patterns
- Public or multi-client APIs needing formal documentation
- Production APIs requiring rate limiting, CORS, health checks
When NOT to use:
- Simple CRUD with no external consumers (framework-native endpoints are simpler)
- Internal-only APIs without documentation requirements
- Single-use endpoints with no schema reuse (over-engineering)
Key patterns covered:
- Modular route setup with
app.route()andOpenAPIHono - Zod schema definitions with
.openapi()metadata - Route definition with
createRoute(operationId, tags, responses) - Error handling with named error codes
- Filtering, pagination, and data transformation
- Auth, rate limiting, CORS, logging, caching middleware
- Health check endpoints (shallow and deep)
- RPC client (
hc) with end-to-end type safety - Context Storage for out-of-handler context access
- Combine Middleware (
some/every/except) for declarative auth
Detailed Resources:
- [examples/core.md](examples/core.md) - Route setup, list/detail endpoints
- [examples/validation.md](examples/validation.md) - Zod schema definitions with OpenAPI
- [examples/routes.md](examples/routes.md) - Filtering, pagination, data transformation
- [examples/middleware.md](examples/middleware.md) - Auth, rate limiting, CORS, logging, caching
- [examples/error-handling.md](examples/error-handling.md) - Standardized error responses
- [examples/openapi.md](examples/openapi.md) - Spec generation (build-time and endpoint)
- [examples/health-checks.md](examples/health-checks.md) - Liveness and readiness checks
- [examples/advanced-v4.md](examples/advanced-v4.md) - RPC, Context Storage, Combine Middleware
- [reference.md](reference.md) - Decision frameworks, anti-patterns, production checklist
<philosophy>
Philosophy
Type safety + documentation from code. Zod schemas serve both validation AND OpenAPI spec generation. Single source of truth flows to clients via generated SDKs or Hono's RPC client.
Use Hono + OpenAPI when: Building public/multi-client APIs, need auto-generated documentation, require formal OpenAPI specs, want type-safe validation.
Use simpler approaches when: Internal-only CRUD, no external API consumers, no documentation needs.
</philosophy>
<patterns>
Core Patterns
Pattern 1: Modular Route Setup
Structure routes using app.route() for modularization. Export the app instance for spec generation.
import { OpenAPIHono } from "@hono/zod-openapi";
const app = new OpenAPIHono().basePath("/api");
app.route("/", jobsRoutes);
app.route("/", companiesRoutes);
// REQUIRED: Export app for spec generation
export { app };
Why good: app.route() prevents God files, app export enables build-time spec generation
See [examples/core.md](examples/core.md) for complete setup with framework adapter exports.
Pattern 2: Zod Schemas with OpenAPI Metadata
Import z from @hono/zod-openapi (not zod). Use .openapi() for schema registration and documentation.
import { z } from "@hono/zod-openapi";
const MIN_SALARY = 0;
const CURRENCY_CODE_LENGTH = 3;
export const SalarySchema = z
.object({
min: z.number().min(MIN_SALARY),
max: z.number().min(MIN_SALARY),
currency: z.string().length(CURRENCY_CODE_LENGTH),
})
.openapi("Salary", {
example: { min: 60000, max: 90000, currency: "EUR" },
});
Why good: importing z from @hono/zod-openapi provides .openapi() automatically, named constants prevent magic number bugs, .openapi("Name") registers as #/components/schemas/Name
See [examples/validation.md](examples/validation.md) for complete schema patterns.
Pattern 3: Route Definition with createRoute
Define routes with createRoute and implement with app.openapi(). Always include operationId.
import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
const getJobsRoute = createRoute({
method: "get",
path: "/jobs",
operationId: "getJobs", // Becomes client method name
tags: ["Jobs"],
request: { query: JobsQuerySchema },
responses: {
200: {
description: "List of jobs",
content: { "application/json": { schema: JobsResponseSchema } },
},
},
});
app.openapi(getJobsRoute, async (c) => {
const { country } = c.req.valid("query"); // Type-safe validated params
// ... handler logic
return c.json({ jobs: results }, 200);
});
Why good: operationId becomes clean client method name (getJobs vs get_api_jobs), c.req.valid() enforces schema validation with types
See [examples/core.md](examples/core.md) for list/detail endpoint examples.
Pattern 4: Error Handling with Named Codes
Use named error code constants for consistent, machine-parseable error responses.
export const ErrorCodes = {
VALIDATION_ERROR: "validation_error",
NOT_FOUND: "not_found",
UNAUTHORIZED: "unauthorized",
INTERNAL_ERROR: "internal_error",
} as const;
Why good: Named codes enable frontend switch handling, consistent shape across all endpoints
See [examples/error-handling.md](examples/error-handling.md) for the full handleRouteError utility.
Pattern 5: JWT Authentication with Explicit Algorithm
Always specify the alg option on JWT/JWK middleware to prevent algorithm confusion attacks (CVE-2026-22817, CVE-2026-22818, patched in v4.11.4+).
import { verify } from "hono/jwt";
const JWT_ALGORITHM = "HS256";
const payload = await verify(token, secret, JWT_ALGORITHM);
Why good: explicit algorithm prevents attackers from switching to symmetric verification with known public keys
See [examples/middleware.md](examples/middleware.md) for complete auth middleware with type-safe variables.
</patterns>
<red_flags>
RED FLAGS
High Priority:
- Importing
zfrom"zod"instead of"@hono/zod-openapi"--.openapi()won't be available - Missing
operationIdin routes -- generated client has ugly method names - Not exporting
appinstance -- can't generate OpenAPI spec at build time - JWT/JWK without explicit
algoption -- algorithm confusion vulnerability (CVE-2026-22817/22818)
Medium Priority:
- Using
c.req.param()/c.req.query()instead ofc.req.valid()-- bypasses Zod validation - No pagination limits on list endpoints -- returns massive datasets
- Generating spec at runtime instead of build time -- wasted CPU per request
- Not returning proper status codes -- always specify (200, 404, 500)
- Wildcard CORS (
"*") withcredentials: true-- browsers reject this (spec violation)
**Gotchas & Edg