
Hono Api Scaffolder
- 1.4k installs
- 946 repo stars
- Updated July 2, 2026
- jezweb/claude-skills
hono-api-scaffolder is an agent skill that scaffolds Hono API routes on Cloudflare Workers with Zod validation and endpoint documentation.
About
The hono-api-scaffolder skill adds structured API routes to an existing Cloudflare Workers project after cloudflare-worker-builder or vite-flare-starter setup. It gathers endpoints grouped by resource such as users, posts, and auth, then creates one route file per group using Hono with typed Env bindings and @hono/zod-validator schemas. Patterns cover GET list and detail routes, POST with Zod body validation, PUT updates, DELETE handlers, and consistent JSON error responses. Middleware includes CORS, logging, and auth stubs wired in src/index.ts with app.route mounts under /api. The skill generates API_ENDPOINTS.md documenting methods, paths, request bodies, and response shapes. Templates live in assets/route-template.ts for consistent CRUD scaffolding. Use when developers need Hono endpoints, validation, and documentation on Workers with D1 or other bindings.
- Creates per-resource Hono route files with typed Cloudflare Env bindings.
- Uses Zod validators via @hono/zod-validator for request body schemas.
- Generates API_ENDPOINTS.md documenting routes, bodies, and responses.
- Runs after cloudflare-worker-builder or vite-flare-starter project shell exists.
- Includes middleware patterns for CORS, logging, and auth stubs.
Hono Api Scaffolder by the numbers
- 1,381 all-time installs (skills.sh)
- +25 installs in the week ending Jul 29, 2026 (Skillselion tracking)
- Ranked #333 of 4,353 Backend & APIs skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 31, 2026 (Skillselion catalog sync)
hono-api-scaffolder capabilities & compatibility
- Capabilities
- per resource hono route file generation · zod request validation middleware · typed cloudflare env bindings · api_endpoints.md documentation output · crud route templates with error handling
- Works with
- cloudflare
- Use cases
- api development · documentation
What hono-api-scaffolder says it does
Scaffold Hono API routes for Cloudflare Workers.
npx skills add https://github.com/jezweb/claude-skills --skill hono-api-scaffolderAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.4k |
|---|---|
| repo stars | ★ 946 |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 2, 2026 |
| Repository | jezweb/claude-skills ↗ |
How do I add structured Hono API routes with validation and docs to my Cloudflare Workers project?
Scaffold Hono API routes on Cloudflare Workers with Zod validation, typed bindings, middleware, and API_ENDPOINTS.md docs.
Who is it for?
Developers extending Cloudflare Workers projects with Hono CRUD routes and validated JSON APIs.
Skip if: Skip for greenfield Worker setup without an existing project shell from related builder skills.
When should I use this skill?
User adds API routes to a Hono Workers project, creates endpoints, or needs API documentation generation.
What you get
Route files per resource, middleware wiring, typed bindings, and API_ENDPOINTS.md for the new endpoints.
- Hono route source files
- Auth and error middleware
- API_ENDPOINTS.md
By the numbers
- Ships route-template.ts, middleware-template.ts, and error-handler.ts asset templates
- Produces API_ENDPOINTS.md endpoint documentation file
Files
Hono API Scaffolder
Add structured API routes to an existing Cloudflare Workers project. This skill runs AFTER the project shell exists (via cloudflare-worker-builder or vite-flare-starter) and produces route files, middleware, and endpoint documentation.
Workflow
Step 1: Gather Endpoints
Determine what the API needs. Either ask the user or infer from the project description. Group endpoints by resource:
Users: GET /api/users, GET /api/users/:id, POST /api/users, PUT /api/users/:id, DELETE /api/users/:id
Posts: GET /api/posts, GET /api/posts/:id, POST /api/posts, PUT /api/posts/:id
Auth: POST /api/auth/login, POST /api/auth/logout, GET /api/auth/meStep 2: Create Route Files
One file per resource group. Use the template from assets/route-template.ts:
// src/routes/users.ts
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
import type { Env } from '../types'
const app = new Hono<{ Bindings: Env }>()
// GET /api/users
app.get('/', async (c) => {
const db = c.env.DB
const { results } = await db.prepare('SELECT * FROM users').all()
return c.json({ users: results })
})
// GET /api/users/:id
app.get('/:id', async (c) => {
const id = c.req.param('id')
const user = await db.prepare('SELECT * FROM users WHERE id = ?').bind(id).first()
if (!user) return c.json({ error: 'Not found' }, 404)
return c.json({ user })
})
// POST /api/users
const createUserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
})
app.post('/', zValidator('json', createUserSchema), async (c) => {
const body = c.req.valid('json')
// ... insert logic
return c.json({ user }, 201)
})
export default appStep 3: Add Middleware
Based on project needs, add from assets/middleware-template.ts:
Auth middleware — protect routes requiring authentication:
import { createMiddleware } from 'hono/factory'
import type { Env } from '../types'
export const requireAuth = createMiddleware<{ Bindings: Env }>(async (c, next) => {
const token = c.req.header('Authorization')?.replace('Bearer ', '')
if (!token) return c.json({ error: 'Unauthorized' }, 401)
// Validate token...
await next()
})CORS — use Hono's built-in:
import { cors } from 'hono/cors'
app.use('/api/*', cors({ origin: ['https://example.com'] }))Step 4: Wire Routes
Mount all route groups in the main entry point:
// src/index.ts
import { Hono } from 'hono'
import type { Env } from './types'
import users from './routes/users'
import posts from './routes/posts'
import auth from './routes/auth'
import { errorHandler } from './middleware/error-handler'
const app = new Hono<{ Bindings: Env }>()
// Global error handler
app.onError(errorHandler)
// Mount routes
app.route('/api/users', users)
app.route('/api/posts', posts)
app.route('/api/auth', auth)
// Health check
app.get('/api/health', (c) => c.json({ status: 'ok' }))
export default appStep 5: Create Types
// src/types.ts
export interface Env {
DB: D1Database
KV: KVNamespace // if needed
R2: R2Bucket // if needed
API_SECRET: string // secrets
}Step 6: Generate API_ENDPOINTS.md
Document all endpoints. See references/endpoint-docs-template.md for the format:
## POST /api/users
Create a new user.
- **Auth**: Required (Bearer token)
- **Body**: `{ name: string, email: string }`
- **Response 201**: `{ user: User }`
- **Response 400**: `{ error: string, details: ZodError }`Key Patterns
Zod Validation
Always validate request bodies with @hono/zod-validator:
import { zValidator } from '@hono/zod-validator'
app.post('/', zValidator('json', schema), async (c) => {
const body = c.req.valid('json') // fully typed
})Install: pnpm add @hono/zod-validator zod
Error Handling
Use the standard error handler from assets/error-handler.ts:
export const errorHandler = (err: Error, c: Context) => {
console.error(err)
return c.json({ error: err.message }, 500)
}API routes must return JSON errors, not redirects. fetch() follows redirects silently, then the client tries to parse HTML as JSON.
RPC Type Safety
For end-to-end type safety between Worker and client:
// Worker: export the app type
export type AppType = typeof app
// Client: use hc (Hono Client)
import { hc } from 'hono/client'
import type { AppType } from '../worker/src/index'
const client = hc<AppType>('https://api.example.com')
const res = await client.api.users.$get() // fully typedRoute Groups vs Single File
| Project size | Structure |
|---|---|
| < 10 endpoints | Single index.ts with all routes |
| 10-30 endpoints | Route files per resource (routes/users.ts) |
| 30+ endpoints | Route files + shared middleware + typed context |
Reference Files
| When | Read |
|---|---|
| Hono patterns, middleware, RPC | references/hono-patterns.md |
| API_ENDPOINTS.md format | references/endpoint-docs-template.md |
Assets
| File | Purpose |
|---|---|
| assets/route-template.ts | Starter route file with CRUD + Zod |
| assets/middleware-template.ts | Auth middleware template |
| assets/error-handler.ts | Standard JSON error handler |
/**
* Standard Error Handler
*
* Returns JSON errors for all routes.
* API routes must return JSON, never HTML redirects.
*/
import type { Context } from 'hono'
import { HTTPException } from 'hono/http-exception'
export const errorHandler = (err: Error, c: Context) => {
if (err instanceof HTTPException) {
return c.json({ error: err.message }, err.status)
}
console.error('Unhandled error:', err)
return c.json({ error: 'Internal server error' }, 500)
}
/**
* Auth Middleware Template
*
* Validates Bearer token and sets userId/role in context.
* Customise the token validation logic for your auth system.
*/
import { createMiddleware } from 'hono/factory'
import type { Env } from '../types'
type AuthVariables = {
userId: string
role: string
}
export const requireAuth = createMiddleware<{
Bindings: Env
Variables: AuthVariables
}>(async (c, next) => {
const token = c.req.header('Authorization')?.replace('Bearer ', '')
if (!token) {
return c.json({ error: 'Unauthorized' }, 401)
}
// TODO: Validate token (JWT, session lookup, API key check, etc.)
// const decoded = await verifyToken(token, c.env.JWT_SECRET)
// c.set('userId', decoded.sub)
// c.set('role', decoded.role)
await next()
})
/**
* Route Template — [Resource Name]
*
* Copy this file to src/routes/[resource].ts and customise.
* Includes: list, get, create, update, delete with Zod validation.
*/
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
import type { Env } from '../types'
const app = new Hono<{ Bindings: Env }>()
const createSchema = z.object({
name: z.string().min(1).max(100),
// Add fields here
})
const updateSchema = createSchema.partial()
// GET /api/[resource]
app.get('/', async (c) => {
const db = c.env.DB
const { results } = await db.prepare('SELECT * FROM [table] ORDER BY created_at DESC').all()
return c.json({ items: results })
})
// GET /api/[resource]/:id
app.get('/:id', async (c) => {
const id = c.req.param('id')
const item = await c.env.DB.prepare('SELECT * FROM [table] WHERE id = ?').bind(id).first()
if (!item) return c.json({ error: 'Not found' }, 404)
return c.json({ item })
})
// POST /api/[resource]
app.post('/', zValidator('json', createSchema), async (c) => {
const body = c.req.valid('json')
const id = crypto.randomUUID()
await c.env.DB.prepare('INSERT INTO [table] (id, name) VALUES (?, ?)').bind(id, body.name).run()
return c.json({ item: { id, ...body } }, 201)
})
// PUT /api/[resource]/:id
app.put('/:id', zValidator('json', updateSchema), async (c) => {
const id = c.req.param('id')
const body = c.req.valid('json')
// ... update logic
return c.json({ item: { id, ...body } })
})
// DELETE /api/[resource]/:id
app.delete('/:id', async (c) => {
const id = c.req.param('id')
await c.env.DB.prepare('DELETE FROM [table] WHERE id = ?').bind(id).run()
return c.body(null, 204)
})
export default app
API Endpoints Documentation Template
Use this format when generating API_ENDPOINTS.md for a project.
Template
# API Endpoints
Base URL: `https://[project].workers.dev` (production) | `http://localhost:5173` (dev)
## Authentication
[Describe auth method: Bearer token, session cookie, API key, none]
## Endpoints
### [Resource Name]
#### GET /api/[resource]
List all [resources].
- **Auth**: [Required/None]
- **Query params**:
- `page` (number, default: 1)
- `limit` (number, default: 20, max: 100)
- `search` (string, optional)
- **Response 200**:{ "[resources]": [...], "total": 42, "page": 1, "limit": 20 }
#### GET /api/[resource]/:id
Get a single [resource] by ID.
- **Auth**: [Required/None]
- **Response 200**: `{ "[resource]": { ... } }`
- **Response 404**: `{ "error": "Not found" }`
#### POST /api/[resource]
Create a new [resource].
- **Auth**: Required
- **Body**:{ "name": "string (required)", "email": "string (required, email format)" }
- **Response 201**: `{ "[resource]": { ... } }`
- **Response 400**: `{ "error": "Validation failed", "details": { ... } }`
#### PUT /api/[resource]/:id
Update an existing [resource].
- **Auth**: Required
- **Body**: Same as POST (all fields optional)
- **Response 200**: `{ "[resource]": { ... } }`
- **Response 404**: `{ "error": "Not found" }`
#### DELETE /api/[resource]/:id
Delete a [resource].
- **Auth**: Required
- **Response 204**: (no body)
- **Response 404**: `{ "error": "Not found" }`
## Error Format
All errors return JSON:
{ "error": "Human-readable error message", "details": {} // optional, present for validation errors }
| Status | Meaning |
|--------|---------|
| 400 | Bad request / validation error |
| 401 | Not authenticated |
| 403 | Not authorised |
| 404 | Resource not found |
| 500 | Internal server error |Guidelines
- Document every endpoint, including auth requirements
- Show example request bodies with field types and constraints
- Show example response shapes (JSON)
- Include all possible error responses
- List query parameters with defaults and limits
- Keep descriptions to one line per endpoint
Hono Patterns
Advanced patterns for Hono on Cloudflare Workers. Load this when building complex APIs or troubleshooting route issues.
Route Priority
Hono matches routes in registration order. Place specific routes before generic ones:
// Correct order
app.get('/api/users/me', getMeHandler) // specific first
app.get('/api/users/:id', getUserHandler) // param route second
app.get('/api/users', listUsersHandler) // list lastMiddleware Chains
Middleware runs in order of app.use() registration:
app.use('*', logger()) // all routes
app.use('/api/*', cors()) // all API routes
app.use('/api/admin/*', requireAuth) // admin routes onlyPer-Route Middleware
app.get('/api/secret', requireAuth, async (c) => {
// runs after auth middleware
})Typed Context with Variables
// Set in middleware
export const authMiddleware = createMiddleware<{
Bindings: Env
Variables: { userId: string; role: string }
}>(async (c, next) => {
c.set('userId', decoded.sub)
c.set('role', decoded.role)
await next()
})
// Read in handler
app.get('/api/me', authMiddleware, (c) => {
const userId = c.get('userId') // typed string
})Request Handling
Path Parameters
app.get('/api/posts/:id', (c) => {
const id = c.req.param('id') // string
})
// Multiple params
app.get('/api/orgs/:orgId/users/:userId', (c) => {
const { orgId, userId } = c.req.param()
})Query Parameters
app.get('/api/users', (c) => {
const page = parseInt(c.req.query('page') || '1')
const limit = parseInt(c.req.query('limit') || '20')
const search = c.req.query('search')
})Headers
const token = c.req.header('Authorization')?.replace('Bearer ', '')
const contentType = c.req.header('Content-Type')Response Patterns
JSON (most common)
return c.json({ users }, 200)
return c.json({ error: 'Not found' }, 404)
return c.json({ user }, 201) // createdEmpty responses
return c.body(null, 204) // no content (DELETE success)
return new Response(null, { status: 204 })Redirects
return c.redirect('/login', 302)Streaming
return c.stream(async (stream) => {
await stream.write('chunk 1')
await stream.write('chunk 2')
})Error Handling
Global error handler
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)
})HTTPException
import { HTTPException } from 'hono/http-exception'
throw new HTTPException(403, { message: 'Forbidden' })Not Found handler
app.notFound((c) => c.json({ error: 'Not found' }, 404))Zod Validation Patterns
Body validation
const schema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
role: z.enum(['admin', 'editor', 'viewer']).optional(),
})
app.post('/', zValidator('json', schema), async (c) => {
const body = c.req.valid('json')
})Query validation
const querySchema = z.object({
page: z.coerce.number().min(1).default(1),
limit: z.coerce.number().min(1).max(100).default(20),
search: z.string().optional(),
})
app.get('/', zValidator('query', querySchema), async (c) => {
const { page, limit, search } = c.req.valid('query')
})Custom error response
app.post('/',
zValidator('json', schema, (result, c) => {
if (!result.success) {
return c.json({ error: 'Validation failed', details: result.error.flatten() }, 400)
}
}),
handler,
)RPC (Remote Procedure Call)
End-to-end type safety between Worker and client without code generation:
// Worker: chain routes for type inference
const routes = app
.get('/api/users', async (c) => c.json({ users: [] }))
.post('/api/users', zValidator('json', schema), async (c) => c.json({ user: {} }, 201))
export type AppType = typeof routes
// Client:
import { hc } from 'hono/client'
import type { AppType } from './worker'
const client = hc<AppType>('https://api.example.com')
const res = await client.api.users.$get()
const data = await res.json() // typed: { users: User[] }Key: The route chain must be assigned to a variable for type inference to work. Don't use app.route() for RPC — mount routes directly on the app.
Related skills
How it compares
Pick hono-api-scaffolder for Hono routes on existing Cloudflare Workers; pick cloudflare-worker-builder first when the Worker project shell does not exist yet.
FAQ
What does hono-api-scaffolder produce?
Hono route files with Zod validation, middleware wiring, typed bindings, and API_ENDPOINTS.md documentation.
When should I use hono-api-scaffolder?
After a Cloudflare Workers project exists and you need to add structured Hono API routes and docs.
Is hono-api-scaffolder safe to install?
Review the Security Audits panel on this page before installing in production.