hono
Skill21 starsChanged 10 days agoMITView on GitHub
Use when building Hono web applications or when the user asks about Hono APIs, routing, middleware, JSX, validation, testing, or streaming. TRIGGER when code imports from 'hono' or 'hono/*', or user mentions Hono. Use Hono CLI to inspect and test the app.
- Reads credentials
- Installs packages
22 other projects use a version of this, 10 of them edited.
Adopted by
Most recently taken up by
- louisbrulenaudet/monorepo-templateword for word, first seen Oct 29, 2026
- philtzjp/skillstheir own version, first seen Oct 28, 2026
- Marvellbrazil/my-agentic-skillstheir own version, first seen Oct 17, 2026
- rabius-sunny/agent-skillstheir own version, first seen Oct 13, 2026
- G1Joshi/Agent-Skillstheir own version, first seen Oct 13, 2026
- rockcookies/skillsedited, first seen Oct 13, 2026
Used with
Projects with it also use turborepo, better-auth-best-practices and 3 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
VercelSkillturborepo
Turborepo monorepo build system guidance. Triggers on: turbo.json, task pipelines, dependsOn, caching, remote cache, the "turbo" CLI, --filter, --affected, CI optimization, environment variables, internal packages, monorepo structure/best practices, and boundaries.
Also in 5 projects that use this
anasahmed07Skillbetter-auth-best-practices
Skill for integrating Better Auth - the comprehensive TypeScript authentication framework.
Also in 5 projects that use this
VercelSkillvercel-react-best-practices
React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.
Also in 8 projects that use this
VercelSkillweb-design-guidelines
Review UI code for Web Interface Guidelines compliance. Use when asked to "review my UI", "check accessibility", "audit design", "review UX", or "check my site against best practices".
Also in 5 projects that use this
DatabricksSkillshadcn
Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI. Provides project context, component docs, and usage examples. Applies when working with shadcn/ui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for "shadcn init", "create an app with --preset", or "switch to --preset".
Also in 4 projects that use this
Other versions22 projects use a version of this.
Word for word · 2
- louisbrulenaudet/monorepo-template★ 19100% same
- honojs/skills★ 17100% same
Edited · 10
- pedronauck/skills★ 64991% same
- freestyle-voice/freestyle★ 49087% same
- getsentry/outpost★ 587% same
- arvindrk/twitter-agent★ 487% same
- vercel-labs/chat-sdk-form-bot★ 374% same
- buduan/soul-graph★ 287% same
- JustineDevs/CRUX★ 187% same
- rockcookies/skills★ 1100% same
- themonk-dev/mcp-toolkit★ 187% same
- escwxyz/skills-re★ 087% same
Wrote their own version · 10
- epicenter-md/epicenter★ 4.8ksame name
- EpicenterHQ/epicenter★ 4.8ksame name
- yusukebe/skills★ 36same name
- G1Joshi/Agent-Skills★ 15same name
- LeonardoTrapani/better-skills★ 11same name
- Marvellbrazil/my-agentic-skills★ 4same name
- yumazak/joy★ 4same name
- FelipeAngeli/skills★ 2same name
- philtzjp/skills★ 2same name
- rabius-sunny/agent-skills★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository16 other files it gives its agents.
- AGENTS.mdAGENTS.md
- .agents/skills/drizzle-best-practices/AGENTS.mdAGENTS.md
- .agents/skills/vercel-composition-patterns/AGENTS.mdAGENTS.md
- .agents/skills/drizzle-best-practices/CLAUDE.mdCLAUDE.md
- drizzle-best-practicesSkill
- frontend-designSkill
- openspec-apply-changeSkill
- openspec-archive-changeSkill
- openspec-exploreSkill
- openspec-proposeSkill
- openspec-sync-specsSkill
- pnpmSkill
- vercel-composition-patternsSkill
- viteSkill
- vitestSkill
- oneselfSkill
The file
We split this skill into the rules it gives an agent
12 rules. 0 are common, 2 are distinctive, and 10 are very specific to this project.
0
Common
Most of the 19 edited versions we read keep these.
2
Distinctive
This author's own choices. Worth a look.
10
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 20
Verify with Hono CLI, not with throwaway scripts or a dev server.
- Very specific: line 29
Follow the note at the top.
- Very specific: line 48
Before changing existing routes, capture the current behavior: npx hono snapshot prints it as batch JSONL lines (real responses become the …
- Very specific: line 180
Never write the Bindings type by hand: generate it, and rerun after every change to the bindings.
- Very specific: line 437
Use jsxRenderer middleware for layouts.
- Distinctive: line 510
Test endpoints without starting an HTTP server:
- Very specific: line 598
Use createFactory to define the app's Env type (AppEnv above) once and share it across app, middleware, and handlers:
- Distinctive: line 622
Write handlers inline in route definitions for proper type inference of path params.
- Very specific: line 623
Use app.route() to organize large apps by feature, not Rails-style controllers.
- Very specific: line 624
Use createFactory() to share Env type across app, middleware, and handlers.
- Very specific: line 625
Use c.set()/c.get() to pass data between middleware and handlers.
- Very specific: line 628
Use app.request() for testing — no server startup needed.
“Common” is measured: kept by at least 6 in 10 of the 19 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Hono Skill
Build Hono web applications. This skill provides inline API knowledge for AI. Use Hono CLI to inspect and test the app.
Latest Documentation
For details beyond this inline reference, fetch the latest documentation from https://hono.dev. Get the index of doc pages from https://hono.dev/llms.txt, then fetch a page with the Accept: text/markdown header to receive it as Markdown:
curl -H "Accept: text/markdown" https://hono.dev/docs/helpers/cookieHono CLI
Verify with Hono CLI, not with throwaway scripts or a dev server. It is a dev dependency (projects from create-hono already have it), loads the app in-process with app.request(), and prints JSON. Install it if missing, then read its help:
<!-- TODO at the 1.0 release: change @hono/cli@next to @hono/cli -->npm install -D @hono/cli@next
npx hono --helpFollow the note at the top. It explains the JSON output contract and the workflow. npx hono <command> --help has examples and notes for each command (routes, request, batch, snapshot, benchmark, ssg, init).
<!-- TODO at the Hono v5 GA: change `npm create hono@next` to `npm create hono@latest` here and in hono-jsx -->Notes:
npx hono routeslists the routes;npx hono request /pathsends one request — no server needed. If the app is not at the default path (src/index.ts), pass the file to every command:npx hono request / src/app.ts. Do not pass credentials directly in CLI arguments; use environment variables for sensitive values.- Cloudflare bindings (KV, D1, R2, vars) reach
c.envautomatically — no server, no flag. In a project withcloudflare.config.ts(thecfCLI) and a Vite config,request,batch, andsnapshotrun through the Vite dev server by default, andc.envhas the local bindings;requestandbatchdefault to Vite as well in a Vite project with nosrc/index.*(an app built by a Vite plugin;snapshotstill needs the file), and--runtime viteselects it explicitly. In a project with a wrangler config, the app runs on Node.js with the real local bindings loaded; for the full Workers runtime there, add--runtime workerd(pass no file argument; the entry ismainin the wrangler config).--trace,--watch, and a file argument run the app on Node.js, wherec.envis empty in acfproject. routesandsnapshotalso load apps that importcloudflare:workers(Durable Objects); the import is stubbed while the routes are read.- To add a Hono app to an existing directory, run
npx hono init --template cloudflare-workers(ornodejs,bun,deno, ...; without--templateit is detected from the files in the directory), then install the dependencies. It copies thecreate-honotemplate and never overwrites an existing file. For an empty directory,npm create hono@nextdoes the same with prompts. For several requests, or a flow that keeps state (POST, then use the returned id), run them in one
hono batch -call. One JSON object per line;savea value and use it as{{id}}in later steps. The steps share one app instance. Declare the expected status/body per step withexpect(body is a deep partial match) and iterate until the summary shows"failed": 0:npx hono batch - <<'EOF' {"method":"POST","path":"/users","body":{"name":"Alice"},"save":{"id":".id"},"expect":{"status":201}} {"path":"/users/{{id}}","expect":{"status":200,"body":{"name":"Alice"}}} EOF- Before changing existing routes, capture the current behavior:
npx hono snapshotprints it as batch JSONL lines (real responses become theexpect). Keep the lines, make the change, then rerun them withnpx hono batch -until"failed": 0. - On a large API, use
npx hono snapshot --status-onlyandnpx hono batch - --compact— same loop, much smaller output. A failed step carriesdiff: fix what it names.
Hono API Reference
App Constructor
import { Hono } from 'hono'
const app = new Hono()
// With TypeScript generics
type AppEnv = {
Bindings: Env // generated from the bindings config — see "Environment (Cloudflare Workers)"
Variables: { user: User }
}
const app = new Hono<AppEnv>()Routing Methods
app.get('/path', handler)
app.post('/path', handler)
app.put('/path', handler)
app.delete('/path', handler)
app.patch('/path', handler)
app.options('/path', handler)
app.all('/path', handler) // all HTTP methods
app.on('PURGE', '/path', handler) // custom method
app.on(['PUT', 'DELETE'], '/path', handler) // multiple methodsRouting Patterns
// Path parameters
app.get('/user/:name', (c) => {
const name = c.req.param('name')
return c.json({ name })
})
// Multiple params
app.get('/posts/:id/comments/:commentId', (c) => {
const { id, commentId } = c.req.param()
})
// Optional parameters
app.get('/api/animal/:type?', (c) => c.text('Animal!'))
// Wildcards
app.get('/wild/*/card', (c) => c.text('Wildcard'))
// Regexp constraints
app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (c) => {
const { date, title } = c.req.param()
})
// Chained routes
app
.get('/endpoint', (c) => c.text('GET'))
.post((c) => c.text('POST'))
.delete((c) => c.text('DELETE'))Route Grouping
// Using route()
const api = new Hono()
api.get('/users', (c) => c.json([]))
const app = new Hono()
app.route('/api', api) // mounts at /api/users
// Using basePath()
const app = new Hono().basePath('/api')
app.get('/users', (c) => c.json([])) // GET /api/usersError Handling
app.notFound((c) => c.json({ message: 'Not Found' }, 404))
app.onError((err, c) => {
console.error(err)
return c.json({ message: 'Internal Server Error' }, 500)
})Context (c)
Response Methods
c.text('Hello') // text/plain
c.json({ message: 'Hello' }) // application/json
c.html('<h1>Hello</h1>') // text/html
c.redirect('/new-path') // 302 redirect
c.redirect('/new-path', 301) // 301 redirect
c.body('raw body', 200, headers) // raw response
c.notFound() // 404 responseHeaders & Status
c.status(201)
c.header('X-Custom', 'value')
c.header('Cache-Control', 'no-store')Variables (request-scoped data)
// In middleware
c.set('user', { id: 1, name: 'Alice' })
// In handler
const user = c.get('user')
// or
const user = c.var.userEnvironment (Cloudflare Workers)
Never write the Bindings type by hand: generate it, and rerun after every change to the bindings. Which tool a project uses is visible from its files.
cf CLI projects (cloudflare.config.ts; this is what create-hono generates for cloudflare-workers now): bindings are declared in cloudflare.config.ts with the bindings helpers from cf/config. Local bindings need no IDs; do not create Cloudflare resources (KV, D1, R2) or set IDs unless the user asks. Then run npm run typecheck (= cf workers types && tsc; it writes .cloudflare/types) and use the generated Env:
// cloudflare.config.ts
import { bindings, defineConfig } from 'cf/config'
import * as entrypoint from './src/index.ts' with { type: 'cf-worker' }
export default defineConfig({
worker: {
name: 'my-app',
compatibilityDate: '2026-10-06',
entrypoint,
env: {
MY_KV: bindings.kv(),
MY_VAR: bindings.text('my-variable'),
},
},
})const app = new Hono<{ Bindings: Env }>()Scripts are cf dev, cf build, and cf deploy. Such a project has no wrangler config; do not add one, and do not run cf init in a Hono project (its autoconfig stops on Hono).
wrangler projects (wrangler.jsonc or wrangler.toml): bindings are in the wrangler config. npm run cf-typegen (= wrangler types --env-interface CloudflareBindings) writes worker-configuration.d.ts, which declares CloudflareBindings together with the Workers runtime types. If there is no cf-typegen script, run npx wrangler types and use the interface it declares instead (Env, also available as Cloudflare.Env). @cloudflare/workers-types is superseded by either: do not install it.
const app = new Hono<{ Bindings: CloudflareBindings }>()
app.get('/', async (c) => {
const value = await c.env.MY_KV.get('key')
const db = c.env.MY_DB
c.executionCtx.waitUntil(promise)
})Renderer
app.use(async (c, next) => {
c.setRenderer((content) =>
c.html(
<html><body>{content}</body></html>
)
)
await next()
})
app.get('/', (c) => c.render(<h1>Hello</h1>))HonoRequest (c.req)
c.req.param('id') // path parameter
c.req.param() // all path params as object
c.req.query('page') // query string parameter
c.req.query() // all query params; values are string | undefined
c.req.queries('tags') // multiple values: ?tags=A&tags=B → ['A', 'B']
c.req.header('Authorization') // request header
c.req.header() // all headers (keys are lowercase)
// Body parsing
await c.req.json<{ title: string }>() // parse JSON body (unknown without the type argument)
await c.req.text() // parse text body
await c.req.formData() // parse as FormData
await c.req.parseBody() // parse multipart/form-data or urlencoded
await c.req.arrayBuffer() // parse as ArrayBuffer
await c.req.blob() // parse as Blob
// Validated data (used with validator middleware)
c.req.valid('json')
c.req.valid('query')
c.req.valid('form')
c.req.valid('param')
// Properties
c.req.url // full URL string
c.req.path // pathname
c.req.method // HTTP method
c.req.raw // underlying Request objectMiddleware
Using Built-in Middleware
import { cors } from 'hono/cors'
import { logger } from 'hono/logger'
import { basicAuth } from 'hono/basic-auth'
import { prettyJSON } from 'hono/pretty-json'
import { secureHeaders } from 'hono/secure-headers'
import { etag } from 'hono/etag'
import { compress } from 'hono/compress'
import { poweredBy } from 'hono/powered-by'
import { timing } from 'hono/timing'
import { cache } from 'hono/cache'
import { bearerAuth } from 'hono/bearer-auth'
import { jwt } from 'hono/jwt'
import { jwk } from 'hono/jwk'
import { csrf } from 'hono/csrf'
import { ipRestriction } from 'hono/ip-restriction'
import { bodyLimit } from 'hono/body-limit'
import { timeout } from 'hono/timeout'
import { requestId } from 'hono/request-id'
import { methodOverride } from 'hono/method-override'
import { methodNotAllowed } from 'hono/method-not-allowed'
import { languageDetector } from 'hono/language'
import { some, every, except } from 'hono/combine'
import { contextStorage, getContext } from 'hono/context-storage'
import { trailingSlash, trimTrailingSlash } from 'hono/trailing-slash'
// Registration
app.use(logger()) // all routes
app.use('/api/*', cors()) // specific path
app.post('/api/*', basicAuth({ username: 'admin', password: 'secret' }))Custom Middleware
// Inline
app.use(async (c, next) => {
const start = Date.now()
await next()
const elapsed = Date.now() - start
c.res.headers.set('X-Response-Time', `${elapsed}ms`)
})
// Reusable with createMiddleware
import { createMiddleware } from 'hono/factory'
const auth = createMiddleware(async (c, next) => {
const token = c.req.header('Authorization')
if (!token) return c.json({ error: 'Unauthorized' }, 401)
await next()
})
app.use('/api/*', auth)Middleware Execution Order
Middleware executes in registration order. await next() calls the next middleware/handler, and code after next() runs on the way back:
Request → mw1 before → mw2 before → handler → mw2 after → mw1 after → Responseapp.use(async (c, next) => {
// before handler
await next()
// after handler
})Validation
Validation targets: json, form, query, header, param, cookie.
Zod Validator
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const schema = z.object({
title: z.string().min(1),
body: z.string()
})
app.post('/posts', zValidator('json', schema), (c) => {
const data = c.req.valid('json') // fully typed
return c.json(data, 201)
})Valibot / Standard Schema Validator
import { sValidator } from '@hono/standard-validator'
import * as v from 'valibot'
const schema = v.object({ name: v.string(), age: v.number() })
app.post('/users', sValidator('json', schema), (c) => {
const data = c.req.valid('json')
return c.json(data, 201)
})JSX
To build UI with it (layouts with jsxRenderer, Vite, client-side code), use the hono-jsx skill. This section covers the syntax only.
Setup
In tsconfig.json:
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx"
}
}Or use pragma: /** @jsxImportSource hono/jsx */
Important: Files using JSX must have a .tsx extension. Rename .ts to .tsx or the compiler will fail.
Components
import type { PropsWithChildren } from 'hono/jsx'
const Layout = (props: PropsWithChildren) => (
<html>
<head>
<title>My App</title>
</head>
<body>{props.children}</body>
</html>
)
const UserCard = ({ name }: { name: string }) => (
<div class="card">
<h2>{name}</h2>
</div>
)
app.get('/', (c) => {
return c.html(
<Layout>
<UserCard name="Alice" />
</Layout>
)
})jsxRenderer Middleware
Use jsxRenderer middleware for layouts. For details, see https://hono.dev/docs/middleware/builtin/jsx-renderer
Async Components
const UserList = async () => {
const users = await fetchUsers()
return (
<ul>
{users.map((u) => (
<li>{u.name}</li>
))}
</ul>
)
}Fragments
const Items = () => (
<>
<li>Item 1</li>
<li>Item 2</li>
</>
)Streaming
import { stream, streamText, streamSSE } from 'hono/streaming'
// Basic stream
app.get('/stream', (c) => {
return stream(c, async (stream) => {
stream.onAbort(() => console.log('Aborted'))
await stream.write(new Uint8Array([0x48, 0x65]))
await stream.pipe(readableStream)
})
})
// Text stream
app.get('/stream-text', (c) => {
return streamText(c, async (stream) => {
await stream.writeln('Hello')
await stream.sleep(1000)
await stream.write('World')
})
})
// Server-Sent Events
app.get('/sse', (c) => {
return streamSSE(c, async (stream) => {
let id = 0
while (true) {
await stream.writeSSE({
data: JSON.stringify({ time: new Date().toISOString() }),
event: 'time-update',
id: String(id++)
})
await stream.sleep(1000)
}
})
})Testing with app.request()
Test endpoints without starting an HTTP server:
// GET
const res = await app.request('/posts')
expect(res.status).toBe(200)
expect(await res.json()).toEqual({ posts: [] })
// POST with JSON
const res = await app.request('/posts', {
method: 'POST',
body: JSON.stringify({ title: 'Hello' }),
headers: { 'Content-Type': 'application/json' }
})
// POST with FormData
const formData = new FormData()
formData.append('name', 'Alice')
const res = await app.request('/users', { method: 'POST', body: formData })
// With mock env (Cloudflare Workers bindings)
const res = await app.request('/api/data', {}, { KV: mockKV, DATABASE: mockDB })
// Using Request object
const req = new Request('http://localhost/api', { method: 'DELETE' })
const res = await app.request(req)Hono Client (RPC)
Type-safe API client using shared types between server and client.
IMPORTANT: Routes MUST be chained for type inference to work. Without chaining, the client cannot infer route types.
// Server: routes MUST be chained to preserve types
const route = app
.post('/posts', zValidator('json', schema), (c) => {
return c.json({ ok: true }, 201)
})
.get('/posts', (c) => {
return c.json({ posts: [] })
})
export type AppType = typeof route
// Client: use hc() with the exported type
import { hc } from 'hono/client'
import type { AppType } from './server'
const client = hc<AppType>('http://localhost:8787/')
const res = await client.posts.$post({ json: { title: 'Hello' } })
const data = await res.json() // fully typedType utilities:
import type { InferRequestType, InferResponseType } from 'hono/client'
type ReqType = InferRequestType<typeof client.posts.$post>
type ResType = InferResponseType<typeof client.posts.$post, 200>Helpers
Helpers are utility functions imported from hono/<helper-name>:
import { getConnInfo } from 'hono/conninfo'
import { getCookie, setCookie, deleteCookie } from 'hono/cookie'
import { css, Style } from 'hono/css'
import { createFactory } from 'hono/factory'
import { html, raw } from 'hono/html'
import { stream, streamText, streamSSE } from 'hono/streaming'
import { testClient } from 'hono/testing'
import { upgradeWebSocket } from '@hono/cloudflare-workers' // or another @hono/<runtime> adapter packageAvailable helpers: Accepts, Adapter, ConnInfo, Cookie, css, Dev, Factory, html, JWT, Proxy, Route, SSG, Streaming, Testing, WebSocket.
For details, see https://hono.dev/docs/helpers/<helper-name> (fetch with Accept: text/markdown).
Factory
Use createFactory to define the app's Env type (AppEnv above) once and share it across app, middleware, and handlers:
import { createFactory } from 'hono/factory'
const factory = createFactory<AppEnv>()
// Create app (AppEnv is inherited)
const app = factory.createApp()
// Create middleware (AppEnv is inherited, no need to pass generics)
const mw = factory.createMiddleware(async (c, next) => {
await next()
})
// Create handlers separately (preserves type inference)
const handlers = factory.createHandlers(logger(), (c) => c.json({ message: 'Hello' }))
app.get('/api', ...handlers)Best Practices
- Write handlers inline in route definitions for proper type inference of path params.
- Use
app.route()to organize large apps by feature, not Rails-style controllers. - Use
createFactory()to share Env type across app, middleware, and handlers. - Use
c.set()/c.get()to pass data between middleware and handlers. - Chain validators for multiple request parts (param + query + json).
- Export app type for RPC:
export type AppType = typeof routes - Use
app.request()for testing — no server startup needed.
Adapters
Hono runs on multiple runtimes. The default export works for Cloudflare Workers, Deno, and Bun. For Node.js, use the Node adapter:
// Cloudflare Workers / Deno / Bun
export default app
// Node.js
import { serve } from '@hono/node-server'
serve(app)Runtime-specific helpers (serveStatic, upgradeWebSocket, getConnInfo, Lambda handlers) live in separate packages since Hono v5: @hono/cloudflare-workers, @hono/bun, @hono/deno, @hono/aws-lambda, @hono/lambda-edge, @hono/netlify, @hono/vercel, @hono/service-worker. Install the one for the runtime and import from it; hono/<runtime> only exists on v4. hono/adapter (env(), getRuntimeKey()) stays in hono. Hono v5 is ESM only and needs Node.js 22.12 or later.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

