Help us improve
Share bugs, ideas, or general feedback.
From prototyping-skills
This skill generates or scaffolds a Hono API package using @hono/zod-openapi with Zod validation and OpenAPI spec generation. It should be used when the user asks to create an API, add API routes, build endpoints, scaffold a REST API, work on the api package, or mentions Hono, OpenAPI, or API development. It also applies when the user is working inside a packages/api directory or mentions exposing core functionality via HTTP.
npx claudepluginhub kjgarza/marketplace-claude --plugin prototyping-skillsHow this skill is triggered — by the user, by Claude, or both
Slash command
/prototyping-skills:generate-apiThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Generate code for a Hono API that runs on **Bun** and uses **@hono/zod-openapi**
Scaffolds Hono API routes for Cloudflare Workers with Zod validation, middleware, typed bindings, error handling, and endpoint documentation. Use post-project setup to add endpoints.
Provides code examples for building Hono APIs on Bun, covering setup, HTTP routing with params and groups, request parsing, response types, and middleware.
Builds ultra-fast web APIs and full-stack apps with Hono on Cloudflare Workers, Deno, Bun, Node.js. Covers routing, middleware, JSX support, RPC client for edge and BFFs.
Share bugs, ideas, or general feedback.
Generate code for a Hono API that runs on Bun and uses @hono/zod-openapi for route-level schema definitions with automatic OpenAPI spec generation.
These are the team defaults. Multiple people work on these prototypes, so consistency matters. In execution mode, follow these defaults unless the project's CLAUDE.md explicitly overrides them.
In Plan mode, suggest alternatives using the deviation protocol from the
prototyping-skills:team-conventions skill: state the default, name the alternative,
explain the trade-off, flag the blast radius, let the human decide.
@hono/zod-openapi with createRoute() + app.openapi(). Not hono/validator directly, not raw app.get().https://jsonapi.org/format/). Resource-centric, plural nouns, relationships as nested paths.http, fs, etc. Use Bun.env not process.env.c), not Express-style (req, res, next).@repo/types. Never duplicate type definitions in the API package.@repo/core. API handlers are thin wrappers.bun:sqlite when persistence is needed. Not better-sqlite3, not Prisma, not Drizzle (unless deviation approved).packages/api/
├── src/
│ ├── index.ts # App entry point, mounts route groups
│ ├── routes/ # One file per resource/domain
│ │ ├── health.ts
│ │ └── [resource].ts
│ ├── middleware/ # Custom Hono middleware
│ └── lib/ # Shared utilities (error formatting, etc.)
├── package.json
└── tsconfig.json
package.json must include:
{
"type": "module",
"scripts": {
"dev": "bun --watch src/index.ts",
"start": "bun src/index.ts"
},
"dependencies": {
"hono": "^4",
"@hono/zod-openapi": "^0.18",
"zod": "^3",
"@repo/types": "workspace:*",
"@repo/core": "workspace:*"
}
}
Follow the JSON:API specification for URL design:
| Pattern | Example | Purpose |
|---|---|---|
/{resource} | /articles | Collection (plural nouns) |
/{resource}/{id} | /articles/1 | Individual resource |
/{resource}/{id}/{related} | /articles/1/comments | Related resources |
/{resource}/{id}/relationships/{relation} | /articles/1/relationships/author | Relationship linkage |
Rules:
/articles, /blog-posts (not /article, /blogPosts).POST /articles, not POST /articles/create)./articles/1/comments (not /authors/1/articles/2/comments).Every route MUST use createRoute + app.openapi(). This is non-negotiable.
import { createRoute, z } from "@hono/zod-openapi";
import { OpenAPIHono } from "@hono/zod-openapi";
const app = new OpenAPIHono();
const getItemRoute = createRoute({
method: "get",
path: "/items/{id}",
request: {
params: z.object({
id: z.string().openapi({ description: "Item ID", example: "abc-123" }),
}),
},
responses: {
200: {
content: {
"application/json": {
schema: z.object({ id: z.string(), name: z.string() }),
},
},
description: "Item found",
},
404: {
content: {
"application/json": {
schema: z.object({ error: z.string() }),
},
},
description: "Item not found",
},
},
tags: ["Items"],
});
app.openapi(getItemRoute, async (c) => {
const { id } = c.req.valid("param");
// Call into @repo/core for business logic
return c.json({ id, name: "Example" }, 200);
});
const createItemRoute = createRoute({
method: "post",
path: "/items",
request: {
body: {
content: {
"application/json": {
schema: z.object({
name: z.string().min(1),
description: z.string().optional(),
}),
},
},
required: true,
},
},
responses: {
201: {
content: {
"application/json": {
schema: z.object({ id: z.string(), name: z.string() }),
},
},
description: "Created",
},
},
tags: ["Items"],
});
app.openapi(createItemRoute, async (c) => {
const body = c.req.valid("json");
return c.json({ id: "new-id", name: body.name }, 201);
});
// src/index.ts
import { OpenAPIHono } from "@hono/zod-openapi";
import { swaggerUI } from "@hono/swagger-ui";
import { cors } from "hono/cors";
import itemRoutes from "./routes/items";
import healthRoutes from "./routes/health";
const app = new OpenAPIHono();
app.use("/*", cors());
app.route("/api", itemRoutes);
app.route("/", healthRoutes);
app.doc("/doc", {
openapi: "3.1.0",
info: { title: "API", version: "0.1.0" },
});
app.get("/swagger", swaggerUI({ url: "/doc" }));
export default {
port: Bun.env.PORT ?? 3001,
fetch: app.fetch,
};
import { HTTPException } from "hono/http-exception";
app.onError((err, c) => {
if (err instanceof HTTPException) {
return c.json({ error: err.message }, err.status);
}
console.error(err);
return c.json({ error: "Internal server error" }, 500);
});
OpenAPIHono instance, mounted in index.ts via app.route().@repo/core, not in route handlers. Handlers: validate, call core, format response.schemas.ts if shared.@repo/types (add .openapi() refinements in the API package).Bun.env.VARIABLE_NAME, never process.env.