Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Build ultra-fast web APIs and full-stack apps with Hono — runs on Cloudflare Workers, Deno, Bun, Node.js, and any WinterCG-compatible runtime.
.claude/skills/davila7-hono/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-08 | ✓→✗ | ▼ Worse | 108% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 259% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 243% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 217% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 136% | 0% |
Hono (炎, "flame" in Japanese) is a small, ultrafast web framework built on Web Standards (Request/Response/fetch). It runs anywhere: Cloudflare Workers, Deno Deploy, Bun, Node.js, AWS Lambda, and any WinterCG-compatible runtime — with the same code. Hono's router is one of the fastest available, and its middleware system, built-in JSX support, and RPC client make it a strong choice for edge APIs, BFFs, and lightweight full-stack apps.
c.req, c.json, or hc() RPC clientCloudflare Workers (recommended for edge):
bashnpm create hono@latest my-api # Select: cloudflare-workers cd my-api npm install npm run dev # Wrangler local dev npm run deploy # Deploy to Cloudflare
Bun / Node.js:
bashmkdir my-api && cd my-api bun init bun add hono
typescript// src/index.ts (Bun) import { Hono } from 'hono'; const app = new Hono(); app.get('/', c => c.text('Hello Hono!')); export default { port: 3000, fetch: app.fetch, };
typescriptimport { Hono } from 'hono'; const app = new Hono(); // Basic methods app.get('/posts', c => c.json({ posts: [] })); app.post('/posts', c => c.json({ created: true }, 201)); app.put('/posts/:id', c => c.json({ updated: true })); app.delete('/posts/:id', c => c.json({ deleted: true })); // Route params and query strings app.get('/posts/:id', async c => { const id = c.req.param('id'); const format = c.req.query('format') ?? 'json'; return c.json({ id, format }); }); // Wildcard app.get('/static/*', c => c.text('static file')); export default app;
Chained routing:
typescriptapp .get('/users', listUsers) .post('/users', createUser) .get('/users/:id', getUser) .patch('/users/:id', updateUser) .delete('/users/:id', deleteUser);
Hono middleware works exactly like fetch interceptors — before and after handlers:
typescriptimport { Hono } from 'hono'; import { logger } from 'hono/logger'; import { cors } from 'hono/cors'; import { bearerAuth } from 'hono/bearer-auth'; const app = new Hono(); // Built-in middleware app.use('*', logger()); app.use('/api/*', cors({ origin: 'https://myapp.com' })); app.use('/api/admin/*', bearerAuth({ token: process.env.API_TOKEN! })); // Custom middleware app.use('*', async (c, next) => { c.set('requestId', crypto.randomUUID()); await next(); c.header('X-Request-Id', c.get('requestId')); });
Available built-in middleware: logger, cors, csrf, etag, cache, basicAuth, bearerAuth, jwt, compress, bodyLimit, timeout, prettyJSON, secureHeaders.
typescriptapp.post('/submit', async c => { // Parse body const body = await c.req.json<{ name: string; email: string }>(); const form = await c.req.formData(); const text = await c.req.text(); // Headers and cookies const auth = c.req.header('authorization'); const token = getCookie(c, 'session'); // Responses return c.json({ ok: true }); // JSON return c.text('hello'); // plain text return c.html('<h1>Hello</h1>'); // HTML return c.redirect('/dashboard', 302); // redirect return new Response(stream, { status: 200 }); // raw Response });
typescriptimport { zValidator } from '@hono/zod-validator'; import { z } from 'zod'; const createPostSchema = z.object({ title: z.string().min(1).max(200), body: z.string().min(1), tags: z.array(z.string()).default([]), }); app.post( '/posts', zValidator('json', createPostSchema), async c => { const data = c.req.valid('json'); // fully typed const post = await db.post.create({ data }); return c.json(post, 201); } );
typescript// src/routes/posts.ts import { Hono } from 'hono'; const posts = new Hono(); posts.get('/', async c => { /* list posts */ }); posts.post('/', async c => { /* create post */ }); posts.get('/:id', async c => { /* get post */ }); export default posts;
typescript// src/index.ts import { Hono } from 'hono'; import posts from './routes/posts'; import users from './routes/users'; const app = new Hono().basePath('/api'); app.route('/posts', posts); app.route('/users', users); export default app;
Hono's RPC mode exports route types that the hc client consumes — similar to tRPC but using fetch conventions:
typescript// server: src/routes/posts.ts import { Hono } from 'hono'; import { zValidator } from '@hono/zod-validator'; import { z } from 'zod'; const posts = new Hono() .get('/', c => c.json({ posts: [{ id: '1', title: 'Hello' }] })) .post( '/', zValidator('json', z.object({ title: z.string() })), async c => { const { title } = c.req.valid('json'); return c.json({ id: '2', title }, 201); } ); export default posts; export type PostsType = typeof posts;
typescript// client: src/client.ts import { hc } from 'hono/client'; import type { PostsType } from '../server/routes/posts'; const client = hc<PostsType>('/api/posts'); // Fully typed — autocomplete on routes, params, and responses const { posts } = await client.$get().json(); const newPost = await client.$post({ json: { title: 'New Post' } }).json();
typescriptimport { Hono } from 'hono'; import { jwt, sign } from 'hono/jwt'; const app = new Hono(); const SECRET = process.env.JWT_SECRET!; app.post('/login', async c => { const { email, password } = await c.req.json(); const user = await validateUser(email, password); if (!user) return c.json({ error: 'Invalid credentials' }, 401); const token = await sign({ sub: user.id, exp: Math.floor(Date.now() / 1000) + 3600 }, SECRET); return c.json({ token }); }); app.use('/api/*', jwt({ secret: SECRET })); app.get('/api/me', async c => { const payload = c.get('jwtPayload'); const user = await getUserById(payload.sub); return c.json(user); }); export default app;
typescript// src/index.ts import { Hono } from 'hono'; type Bindings = { DB: D1Database; API_TOKEN: string; }; const app = new Hono<{ Bindings: Bindings }>(); app.get('/users', async c => { const { results } = await c.env.DB.prepare('SELECT * FROM users LIMIT 50').all(); return c.json(results); }); app.post('/users', async c => { const { name, email } = await c.req.json(); await c.env.DB.prepare('INSERT INTO users (name, email) VALUES (?, ?)') .bind(name, email) .run(); return c.json({ created: true }, 201); }); export default app;
typescriptimport { stream, streamText } from 'hono/streaming'; app.get('/stream', c => streamText(c, async stream => { for (const chunk of ['Hello', ' ', 'World']) { await stream.write(chunk); await stream.sleep(100); } }) );
app.route('/users', usersRouter)zValidator for all request body, query, and param validationBindings generic: new Hono<{ Bindings: Env }>()hc) when your frontend and backend share the same repoc.json()/c.text() over new Response() for cleaner codefs, path, process) if you want edge portabilityVariables, Bindings) to keep c.get() type-safezValidator before using data from requests.csrf middleware on mutation endpoints when serving HTML/forms.wrangler.toml [vars] (non-secret) or wrangler secret put (secret) — never hardcode them in source.bearerAuth or jwt, ensure tokens are validated server-side — do not trust client-provided user IDs.undefined — response is emptySolution: Always return a response from handlers: return c.json(...) not just c.json(...).
Solution: Call await next() before post-response logic; Hono runs code after next() as the response travels back up the chain.
c.env is undefined on Node.jsSolution: Cloudflare env bindings only exist in Workers. Use process.env on Node.js.
Solution: Check that app.route('/prefix', subRouter) uses the same prefix your client calls. Sub-routers should not repeat the prefix in their own routes.
@cloudflare-workers-expert — Deep dive into Cloudflare Workers platform specifics@trpc-fullstack — Alternative RPC approach for TypeScript full-stack apps@zod-validation-expert — Detailed Zod schema patterns used with @hono/zod-validator@nodejs-backend-patterns — When you need a Node.js-specific backend (not edge)| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | pass→pass | 6,438 | 6,185 | -4% | 1 | 1 | 0% | 1,113 | 4,001 | +259% | 0 | 0 | — |
case-02 | pass→pass | 5,283 | 5,105 | -3% | 1 | 1 | 0% | 1,127 | 3,865 | +243% | 0 | 0 | — |
case-03 | pass→pass | 7,839 | 8,388 | +7% | 1 | 1 | 0% | 1,464 | 4,638 | +217% | 0 | 0 | — |
case-04 | pass→pass | 9,845 | 8,594 | -13% | 1 | 1 | 0% | 1,939 | 4,581 | +136% | 0 | 0 | — |
case-05 | pass→pass | 8,309 | 6,337 | -24% | 1 | 1 | 0% | 1,516 | 4,109 | +171% | 0 | 0 | — |
case-06 | pass→pass | 9,586 | 8,376 | -13% | 1 | 1 | 0% | 1,913 | 4,531 | +137% | 0 | 0 | — |
case-07 | fail→fail | 7,231 | 7,033 | -3% | 1 | 1 | 0% | 1,439 | 4,394 | +205% | 0 | 0 | — |
case-08 | pass→fail | 13,849 | 13,554 | -2% | 1 | 1 | 0% | 2,736 | 5,694 | +108% | 0 | 0 | — |
case-09 | pass→pass | 7,059 | 9,160 | +30% | 1 | 1 | 0% | 1,438 | 4,740 | +230% | 0 | 0 | — |
case-10 | pass→pass | 9,580 | 7,414 | -23% | 1 | 1 | 0% | 1,850 | 4,317 | +133% | 0 | 0 | — |
case-11 | pass→pass | 5,418 | 6,103 | +13% | 1 | 1 | 0% | 1,013 | 4,141 | +309% | 0 | 0 | — |
case-12 | pass→pass | 5,649 | 13,391 | +137% | 1 | 1 | 0% | 985 | 3,772 | +283% | 0 | 0 | — |
case-13 | pass→pass | 6,147 | 7,767 | +26% | 1 | 1 | 0% | 1,171 | 3,622 | +209% | 0 | 0 | — |
case-14 | pass→pass | 7,049 | 7,110 | +1% | 1 | 1 | 0% | 1,255 | 4,109 | +227% | 0 | 0 | — |
case-15 | pass→pass | 8,898 | 6,931 | -22% | 1 | 1 | 0% | 1,628 | 4,227 | +160% | 0 | 0 | — |
case-16 | pass→pass | 6,520 | 7,727 | +19% | 1 | 1 | 0% | 1,176 | 4,327 | +268% | 0 | 0 | — |
case-17 | pass→pass | 11,398 | 9,225 | -19% | 1 | 1 | 0% | 2,268 | 4,738 | +109% | 0 | 0 | — |
case-18 | pass→pass | 4,255 | 3,365 | -21% | 1 | 1 | 0% | 733 | 3,546 | +384% | 0 | 0 | — |
case-19 | pass→pass | 6,134 | 4,439 | -28% | 1 | 1 | 0% | 1,011 | 3,831 | +279% | 0 | 0 | — |
case-20 | pass→pass | 12,335 | 10,591 | -14% | 1 | 1 | 0% | 2,199 | 4,901 | +123% | 0 | 0 | — |
case-21 | pass→pass | 9,482 | 5,906 | -38% | 1 | 1 | 0% | 1,729 | 4,054 | +134% | 0 | 0 | — |
case-22 | pass→pass | 2,213 | 3,608 | +63% | 1 | 1 | 0% | 358 | 3,504 | +879% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of -100 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Other measured skills in the registry, with their headline benchmark lift.