npx skills add https://github.com/yusukebe/hono-skill --skill honoHow Hono fits into a Paperclip company.
Hono drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.
Pre-configured AI company — 18 agents, 18 skills, one-time purchase.
SKILL.md579 linesExpandCollapse
---name: honodescription: 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 `npx hono request` to test endpoints.--- # Hono Skill Build Hono web applications. This skill provides inline API knowledge for AI. Use `npx hono request` to test endpoints. If the `hono-docs` MCP server is configured, prefer its tools for the latest documentation over the inline reference. ## Hono CLI Usage ### Request Testing Test endpoints without starting an HTTP server. Uses `app.request()` internally. ```bash# GET requestnpx hono request [file] -P /path # POST request with JSON bodynpx hono request [file] -X POST -P /api/users -d '{"name": "test"}'``` **Note:** Do not pass credentials directly in CLI arguments. Use environment variables for sensitive values. `hono request` does not support Cloudflare Workers bindings (KV, D1, R2, etc.). When bindings are required, use `workers-fetch` instead: ```bashnpx workers-fetch /pathnpx workers-fetch -X POST -H "Content-Type:application/json" -d '{"name":"test"}' /api/users``` --- ## Hono API Reference ### App Constructor ```tsimport { Hono } from 'hono' const app = new Hono() // With TypeScript genericstype Env = { Bindings: { DATABASE: D1Database; KV: KVNamespace } Variables: { user: User }}const app = new Hono<Env>()``` ### Routing Methods ```tsapp.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 methodsapp.on('PURGE', '/path', handler) // custom methodapp.on(['PUT', 'DELETE'], '/path', handler) // multiple methods``` ### Routing Patterns ```ts// Path parametersapp.get('/user/:name', (c) => { const name = c.req.param('name') return c.json({ name })}) // Multiple paramsapp.get('/posts/:id/comments/:commentId', (c) => { const { id, commentId } = c.req.param()}) // Optional parametersapp.get('/api/animal/:type?', (c) => c.text('Animal!')) // Wildcardsapp.get('/wild/*/card', (c) => c.text('Wildcard')) // Regexp constraintsapp.get('/post/:date{[0-9]+}/:title{[a-z]+}', (c) => { const { date, title } = c.req.param()}) // Chained routesapp .get('/endpoint', (c) => c.text('GET')) .post((c) => c.text('POST')) .delete((c) => c.text('DELETE'))``` ### Route Grouping ```ts// 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/users``` ### Error Handling ```tsapp.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 ```tsc.text('Hello') // text/plainc.json({ message: 'Hello' }) // application/jsonc.html('<h1>Hello</h1>') // text/htmlc.redirect('/new-path') // 302 redirectc.redirect('/new-path', 301) // 301 redirectc.body('raw body', 200, headers) // raw responsec.notFound() // 404 response``` ### Headers & Status ```tsc.status(201)c.header('X-Custom', 'value')c.header('Cache-Control', 'no-store')``` ### Variables (request-scoped data) ```ts// In middlewarec.set('user', { id: 1, name: 'Alice' }) // In handlerconst user = c.get('user')// orconst user = c.var.user``` ### Environment (Cloudflare Workers) ```tsconst value = await c.env.KV.get('key')const db = c.env.DATABASEc.executionCtx.waitUntil(promise)``` ### Renderer ```tsapp.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) ```tsc.req.param('id') // path parameterc.req.param() // all path params as objectc.req.query('page') // query string parameterc.req.query() // all query params as objectc.req.queries('tags') // multiple values: ?tags=A&tags=B → ['A', 'B']c.req.header('Authorization') // request headerc.req.header() // all headers (keys are lowercase) // Body parsingawait c.req.json() // parse JSON bodyawait c.req.text() // parse text bodyawait c.req.formData() // parse as FormDataawait c.req.parseBody() // parse multipart/form-data or urlencodedawait c.req.arrayBuffer() // parse as ArrayBufferawait 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') // Propertiesc.req.url // full URL stringc.req.path // pathnamec.req.method // HTTP methodc.req.raw // underlying Request object``` --- ## Middleware ### Using Built-in Middleware ```tsimport { 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 { csrf } from 'hono/csrf'import { ipRestriction } from 'hono/ip-restriction'import { bodyLimit } from 'hono/body-limit'import { requestId } from 'hono/request-id'import { methodOverride } from 'hono/method-override'import { trailingSlash, trimTrailingSlash } from 'hono/trailing-slash' // Registrationapp.use(logger()) // all routesapp.use('/api/*', cors()) // specific pathapp.post('/api/*', basicAuth({ username: 'admin', password: 'secret' }))``` ### Custom Middleware ```ts// Inlineapp.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 createMiddlewareimport { 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 → Response``` ```tsapp.use(async (c, next) => { // before handler await next() // after handler})``` --- ## Validation Validation targets: `json`, `form`, `query`, `header`, `param`, `cookie`. ### Zod Validator ```tsimport { 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 ```tsimport { 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 ### Setup In `tsconfig.json`: ```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 ```tsximport 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. See `npx hono docs /docs/middleware/builtin/jsx-renderer` for details. ### Async Components ```tsxconst UserList = async () => { const users = await fetchUsers() return ( <ul> {users.map((u) => ( <li>{u.name}</li> ))} </ul> )}``` ### Fragments ```tsxconst Items = () => ( <> <li>Item 1</li> <li>Item 2</li> </>)``` --- ## Streaming ```tsimport { stream, streamText, streamSSE } from 'hono/streaming' // Basic streamapp.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 streamapp.get('/stream-text', (c) => { return streamText(c, async (stream) => { await stream.writeln('Hello') await stream.sleep(1000) await stream.write('World') })}) // Server-Sent Eventsapp.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: ```ts// GETconst res = await app.request('/posts')expect(res.status).toBe(200)expect(await res.json()).toEqual({ posts: [] }) // POST with JSONconst res = await app.request('/posts', { method: 'POST', body: JSON.stringify({ title: 'Hello' }), headers: { 'Content-Type': 'application/json' }}) // POST with FormDataconst 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 objectconst 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.** ```ts// Server: routes MUST be chained to preserve typesconst 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 typeimport { 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 typed``` Type utilities: ```tsimport 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>`: ```tsimport { 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 other adapter``` Available helpers: Accepts, Adapter, ConnInfo, Cookie, css, Dev, Factory, html, JWT, Proxy, Route, SSG, Streaming, Testing, WebSocket. For details, use `npx hono docs /docs/helpers/<helper-name>`. ### Factory Use `createFactory` to define `Env` once and share it across app, middleware, and handlers: ```tsimport { createFactory } from 'hono/factory' const factory = createFactory<Env>() // Create app (Env type is inherited)const app = factory.createApp() // Create middleware (Env type 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: ```ts// Cloudflare Workers / Deno / Bunexport default app // Node.jsimport { serve } from '@hono/node-server'serve(app)```1password
Install 1password skill for Claude Code from steipete/clawdis.
3d Web Experience
Install 3d Web Experience skill for Claude Code from sickn33/antigravity-awesome-skills.
Ab Test Setup
This handles the full A/B testing workflow from hypothesis formation to statistical analysis. It walks you through proper test design, calculates sample sizes,