Skip to main content
hono-dev This skill should be used when building APIs with Hono, using hc client, implementing OpenAPI, or when "Hono", "RPC", or "type-safe API" are mentioned.
跳到安装 Skills Marketplace 发现并探索由社区构建的 Agent Skills
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/outfitter-dev/agents --skill hono-dev命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
下载 Zip 下载中... 同仓库更多 Skills gitbutler-complete-branch This skill should be used when the user asks to "complete a branch", "merge to main", "finish my feature", "ship this branch", "integrate to main", "create a PR from GitButler", or when `--complete-branch` flag is mentioned. Guides completion of GitButler virtual branches with safety snapshots, integration workflows, and cleanup.
This skill should be used when coordinating multiple AI agents working concurrently, handling agent handoffs, transferring commits between agents, or when "multi-agent", "concurrent agents", "parallel agents", "agent collaboration", or "parallel execution" are mentioned with GitButler. Provides virtual branch patterns for parallel execution without coordination overhead.
This skill should be used when creating stacks, dependent branches, or when "stack", "stacked branches", "anchor", "--anchor", "but branch new -a", "create dependent branch", or "break feature into PRs" are mentioned with GitButler. Covers anchor-based stacking for dependent features and reviewable PR breakdown.
name hono-dev description This skill should be used when building APIs with Hono, using hc client, implementing OpenAPI, or when "Hono", "RPC", or "type-safe API" are mentioned. metadata {"version":"1.0.0"}
Hono API Development
Route chaining → type-safe RPC → end-to-end types.
<when_to_use>
Building REST APIs with Hono
Type-safe RPC with hono/client
OpenAPI documentation with Zod
Testing APIs with testClient
When user mentions "Hono", "RPC", or "OpenAPI"
NOT for: Bun runtime APIs (use bun-dev), other frameworks (Express, Fastify)
</when_to_use>
<version_notes>
Hono v4+ with @hono/zod-openapi v1.0+
Check hono.dev for latest patterns.
</version_notes>
Route Chaining — Critical Pattern
Type inference flows through method chain. Break chain = lose types.
<route_chaining>
const app = new Hono ()
.get ('/users' , (c ) => c.json ({ users : [] }))
.get ('/users/:id' , (c ) => {
const id = c. . ( );
c. ({ id });
})
. ( , (c) => {
body = c. . ();
c. ({ : }, );
});
= app;
req
param
'id'
return
json
post
'/users'
async
const
await
req
json
return
json
created
true
201
export
type
AppType
typeof
const app = new Hono ();
app.get ('/users' , handler1);
app.post ('/users' , handler2);
Path parameters — typed automatically:
.get ('/posts/:id/comments/:commentId' , (c ) => {
const { id, commentId } = c.req .param ();
return c.json ({ postId : id, commentId });
})
Query parameters — use Zod for validation:
import { zValidator } from '@hono/zod-validator' ;
import { z } from 'zod' ;
const QuerySchema = z.object ({
page : z.coerce .number ().int ().positive ().default (1 ),
limit : z.coerce .number ().int ().positive ().max (100 ).default (20 ),
});
const app = new Hono ()
.get ('/search' , zValidator ('query' , QuerySchema ), (c ) => {
const { page, limit } = c.req .valid ('query' );
return c.json ({ page, limit });
});
const app = new Hono ()
.use ('*' , logger ())
.use ('/api/*' , cors ())
.get ('/api/public' , (c ) => c.json ({ public : true }))
.use ('/api/admin/*' , authMiddleware)
.get ('/api/admin/users' , (c ) => c.json ({ users : [] }));
Factory Pattern — Context Typing Use createFactory<Env>() to type context variables across middleware and routes.
import { createFactory } from 'hono/factory' ;
import type { Database } from 'bun:sqlite' ;
type Env = {
Variables : {
user : { id : string ; role : 'admin' | 'user' };
requestId : string ;
db : Database ;
};
};
const factory = createFactory<Env >();
const authMiddleware = factory.createMiddleware (async (c, next) => {
const token = c.req .header ('authorization' )?.replace ('Bearer ' , '' );
if (!token) throw new HTTPException (401 , { message : 'Unauthorized' });
const user = await verifyToken (token);
c.set ('user' , user);
await next ();
});
const getProfile = factory.createHandlers ((c ) => {
const user = c.get ('user' );
return c.json ({ user });
});
const app = factory.createApp ()
.use ('*' , dbMiddleware)
.use ('/api/*' , authMiddleware)
.get ('/api/profile' , ...getProfile);
export type AppType = typeof app;
export const usersRoute = factory.createApp ()
.get ('/' , (c ) => c.json ({ users : [] }))
.post ('/' , zValidator ('json' , CreateUserSchema ), async (c) => {
const data = c.req .valid ('json' );
return c.json ({ created : true }, 201 );
});
const app = factory.createApp ()
.use ('*' , dbMiddleware)
.route ('/users' , usersRoute)
.route ('/posts' , postsRoute);
Error Handling import { HTTPException } from 'hono/http-exception' ;
app.get ('/users/:id' , async (c) => {
const user = await findUser (c.req .param ('id' ));
if (!user) {
throw new HTTPException (404 , { message : 'User not found' });
}
return c.json ({ user });
});
class NotFoundError extends HTTPException {
constructor (resource : string ) {
super (404 , { message : `${resource} not found` });
}
}
class UnauthorizedError extends HTTPException {
constructor (message = 'Unauthorized' ) {
super (401 , { message });
}
}
app.onError ((err, c ) => {
if (err instanceof HTTPException ) {
return c.json ({ error : err.message }, err.status );
}
if (err instanceof ZodError ) {
return c.json ({
error : 'Validation failed' ,
issues : err.issues .map (i => ({ path : i.path .join ('.' ), message : i.message }))
}, 400 );
}
const isDev = Bun .env .NODE_ENV !== 'production' ;
return c.json ({ error : isDev ? err.message : 'Internal server error' }, 500 );
});
app.notFound ((c ) => c.json ({ error : 'Not found' , path : c.req .path }, 404 ));
Zod OpenAPI import { createRoute, OpenAPIHono , z } from '@hono/zod-openapi' ;
import { swaggerUI } from '@hono/swagger-ui' ;
const UserSchema = z.object ({
id : z.string ().uuid (),
email : z.string ().email (),
name : z.string ().min (1 ).max (100 ),
}).openapi ('User' );
const route = createRoute ({
method : 'get' ,
path : '/users/{id}' ,
request : {
params : z.object ({ id : z.string ().uuid () }),
},
responses : {
200 : {
content : { 'application/json' : { schema : UserSchema } },
description : 'User found' ,
},
404 : {
content : { 'application/json' : { schema : z.object ({ error : z.string () }) } },
description : 'User not found' ,
},
},
tags : ['Users' ],
summary : 'Get user by ID' ,
});
const app = new OpenAPIHono ();
app.openapi (route, (c ) => {
const { id } = c.req .valid ('param' );
const user = db.query ('SELECT * FROM users WHERE id = ?' ).get (id);
if (!user) return c.json ({ error : 'User not found' }, 404 );
return c.json (user, 200 );
});
app.get ('/docs' , swaggerUI ({ url : '/openapi.json' }));
app.doc ('/openapi.json' , {
openapi : '3.1.0' ,
info : { title : 'API' , version : '1.0.0' },
});
RPC Client — End-to-End Types
const app = new Hono ()
.get ('/posts' , (c ) => c.json ({ posts : [] }))
.get ('/posts/:id' , (c ) => c.json ({ id : c.req .param ('id' ) }))
.post ('/posts' , zValidator ('json' , CreatePostSchema ), async (c) => {
const data = c.req .valid ('json' );
return c.json ({ id : '123' , ...data }, 201 );
});
export type AppType = typeof app;
import { hc } from 'hono/client' ;
import type { AppType } from './server' ;
const client = hc<AppType >('http://localhost:3000' );
const res = await client.posts .$get();
const data = await res.json ();
const res2 = await client.posts [':id' ].$get({ param : { id : '123' } });
const res3 = await client.posts .$post({
json : { title : 'Hello' , content : 'World' }
});
const res4 = await client.posts .$get({}, {
headers : { Authorization : 'Bearer token' }
});
Testing with testClient import { describe, expect, test, beforeEach, afterEach } from 'bun:test' ;
import { testClient } from 'hono/testing' ;
import { Database } from 'bun:sqlite' ;
import app from './server' ;
describe ('API Tests' , () => {
let db : Database ;
beforeEach (() => {
db = new Database (':memory:' );
db.run ('CREATE TABLE posts (id TEXT PRIMARY KEY, title TEXT, content TEXT)' );
});
afterEach (() => {
db.close ();
});
const client = testClient (app);
test ('GET /posts returns posts' , async () => {
const res = await client.posts .$get();
expect (res.status ).toBe (200 );
const data = await res.json ();
expect (data).toHaveProperty ('posts' );
});
test ('POST /posts creates post' , async () => {
const res = await client.posts .$post({
json : { title : 'Test' , content : 'Content' }
});
expect (res.status ).toBe (201 );
const data = await res.json ();
expect (data).toMatchObject ({ title : 'Test' });
});
test ('Protected route requires auth' , async () => {
const res = await client.api .profile .$get();
expect (res.status ).toBe (401 );
});
test ('Protected route accepts valid token' , async () => {
const res = await client.api .profile .$get({}, {
headers : { Authorization : 'Bearer valid-token' }
});
expect (res.status ).toBe (200 );
});
});
Middleware Patterns
import { logger } from 'hono/logger' ;
app.use ('*' , logger ());
import { cors } from 'hono/cors' ;
app.use ('/api/*' , cors ({
origin : ['http://localhost:3000' ],
credentials : true ,
}));
const rateLimiter = factory.createMiddleware (async (c, next) => {
const ip = c.req .header ('x-forwarded-for' ) || 'unknown' ;
const key = `rate:${ip} ` ;
const count = await cache.incr (key);
if (count === 1 ) await cache.expire (key, 60 );
if (count > 100 ) {
throw new HTTPException (429 , { message : 'Rate limit exceeded' });
}
await next ();
});
const requestId = factory.createMiddleware (async (c, next) => {
c.set ('requestId' , crypto.randomUUID ());
await next ();
c.res .headers .set ('x-request-id' , c.get ('requestId' ));
});
Rules
Chain routes for type inference → .get().post().put()
Export type AppType = typeof app for RPC client
Use createFactory<Env>() for typed context variables
Validate with Zod schemas via zValidator
Handle errors with HTTPException and centralized onError
Test with testClient for type safety
Break method chain with variable assignment between routes
Use any types — let Hono infer or define explicitly
Use JSON.parse(await c.req.text()) — use c.req.json() or Zod validator
Skip request validation on user input
Expose stack traces in production
Check route chaining not broken
Verify export type AppType matches actual app
Ensure middleware uses createFactory for context types
Check client using correct param, query, or json keys
References