Skip to main content

hono-fieldguide

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.

来源信息

仓库
outfitter-dev/outfitter
最近来源活动
2026年2月24日 23:09
检测到的 SKILL.md 语言
英语
星标
6
分支
1

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

文件资源管理器
7 个文件

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
hono-fieldguide
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.1"}
# 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-fieldguide), 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> ```typescript // ✅ Chained routes preserve types const app = new Hono() .get("/users", (c) => c.json({ users: [] })) .get("/users/:id", (c) => { const id = c.req.param("id"); // Typed! return c.json({ id }); }) .post("/users", async (c) => { const body = await c.req.json(); return c.json({ created: true }, 201); }); export type AppType = typeof app; // Full route types! ``` **❌ NEVER break the chain:** ```typescript const app = new Hono(); app.get("/users", handler1); // Types LOST! app.post("/users", handler2); ``` **Path parameters** — typed automatically: ```typescript .get('/posts/:id/comments/:commentId', (c) => { const { id, commentId } = c.req.param(); // Both string return c.json({ postId: id, commentId }); }) ``` **Query parameters** — use Zod for validation: ```typescript 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"); // Fully typed! return c.json({ page, limit }); }); ``` **Middleware in chain:** ```typescript 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: [] })); ``` </route_chaining> ## Factory Pattern — Context Typing Use `createFactory<Env>()` to type context variables across middleware and routes. <factory_pattern> ```typescript 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>(); // Typed middleware 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); // Type-checked! await next(); }); // Typed handlers const getProfile = factory.createHandlers((c) => { const user = c.get("user"); // Typed: { id: string; role: 'admin' | 'user' } return c.json({ user }); }); // Assemble app const app = factory .createApp() .use("*", dbMiddleware) .use("/api/*", authMiddleware) .get("/api/profile", ...getProfile); export type AppType = typeof app; ``` **Multi-module structure:** ```typescript // routes/users.ts 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); }); // index.ts const app = factory .createApp() .use("*", dbMiddleware) .route("/users", usersRoute) .route("/posts", postsRoute); ``` See [factory-pattern.md](references/factory-pattern.md) for advanced patterns. </factory_pattern> ## Error Handling <error_handling> ```typescript import { HTTPException } from "hono/http-exception"; // Throw typed errors 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 }); }); // Custom error classes class NotFoundError extends HTTPException { constructor(resource: string) { super(404, { message: `${resource} not found` }); } } class UnauthorizedError extends HTTPException { constructor(message = "Unauthorized") { super(401, { message }); } } // Centralized handler 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)); ``` See [error-handling.md](references/error-handling.md) for patterns. </error_handling> ## Zod OpenAPI <zod_openapi> ```typescript 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"); // Typed! 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); }); // Swagger UI app.get("/docs", swaggerUI({ url: "/openapi.json" })); app.doc("/openapi.json", { openapi: "3.1.0", info: { title: "API", version: "1.0.0" }, }); ``` See [zod-openapi.md](references/zod-openapi.md) for complete patterns. </zod_openapi> ## RPC Client — End-to-End Types <rpc_client> ```typescript // Server 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; // Client import { hc } from "hono/client"; import type { AppType } from "./server"; const client = hc<AppType>("http://localhost:3000"); // GET request const res = await client.posts.$get(); const data = await res.json(); // Typed: { posts: any[] } // GET with params const res2 = await client.posts[":id"].$get({ param: { id: "123" } }); // POST request const res3 = await client.posts.$post({ json: { title: "Hello", content: "World" }, }); // With headers const res4 = await client.posts.$get( {}, { headers: { Authorization: "Bearer token" }, } ); ``` </rpc_client> ## Testing with testClient <testing> ```typescript 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({
在 GitHub 查看
这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看