All free skills / Other majors

hono 4.x

Validate requests, compose middleware and consume status-aware RPC types across packages.

For
hono@4.13.12
Verified
2026-10-10, 12 examples executed
Skill package
@modootoday/pmcp-hono@4.0.1

Use this skill for free

No PMCP login or subscription is required. Download the original instructions, or install the content-only archive with your project's package manager.

Download SKILL.md · Download package archive

npm install --save-dev https://pmcp.build/skills/hono/4/package.tgz

PMCP discovers the installed package's instructions. Connect your runtime after installation.

Archive integritysha512-jULntcAqbLMqWT2mMU8vpI67qAFmNxtCq9xvZZZybkTdU2xtHAzVEEoDIXR3vBN9P2Ve/MEXnOBdM9uLhxiavw==

Complete instructions

The original four request examples were verified against hono@4.13.7 on 2026-09-07. Development fixtures separately verify middleware ordering, invalid JSON responses, RPC client/server type consumption and a Node HTTP request followed by server shutdown; current exact versions are recorded in the catalog.

Hono 4.x

Use the current app API

Import the recommended preset from hono, construct an app, register routes, and export or pass the app to a runtime server:

import { Hono } from 'hono'

const app = new Hono()
app.get('/', (c) => c.text('Hello Hono!'))

export default app

Hono creates a Fetch-compatible app. Its runtime/server integration is separate. For Node.js, the documented integration uses @hono/node-server and serve(app); Bun and Deno can serve Fetch handlers natively. Do not assume that importing hono starts an HTTP server.

For a standalone script, use app.request() to exercise routes without a server:

import { strict as assert } from 'node:assert'
import { Hono } from 'hono'

const app = new Hono()
app.get('/hello', (c) => c.text('Hello Hono!'))

const response = await app.request('/hello')
assert.equal(response.status, 200)
assert.equal(await response.text(), 'Hello Hono!')

app.fetch(request, env, event) is the Fetch-compatible handler API. app.request(path, options) is the convenient request helper for programmatic calls. Other documented app methods include HTTP method helpers, all, on, use, route, basePath, notFound, onError, mount, and fire.

Context and responses

Route and middleware callbacks receive a context object. The documented context surface includes c.req, c.status(), c.text(), c.json(), c.html(), c.set(), c.get(), c.var, c.header(), c.redirect(), c.notFound(), and c.body(). Cookie helpers are imported from hono/cookie.

Read request headers through c.req.header(), and set the response status with c.status() before returning a response:

import { strict as assert } from 'node:assert'
import { Hono } from 'hono'

const app = new Hono()
app.get('/created', (c) => {
  const userAgent = c.req.header('User-Agent')
  c.status(201)
  return c.json({ userAgent })
})

const response = await app.request('/created', {
  headers: { 'User-Agent': 'pmcp-example' },
})

assert.equal(response.status, 201)
assert.deepEqual(await response.json(), { userAgent: 'pmcp-example' })

Use c.json() rather than the removed c.jsonT() API.

Middleware

Create custom middleware with createMiddleware from hono/factory. Middleware receives (c, next) and must await next() when it wants downstream handlers to run:

import { strict as assert } from 'node:assert'
import { Hono } from 'hono'
import { createMiddleware } from 'hono/factory'

const app = new Hono()

const addMarker = createMiddleware(async (c, next) => {
  c.set('marker', 'middleware')
  await next()
})

app.use(addMarker)
app.get('/', (c) => c.text(c.get('marker')))

const response = await app.request('/')
assert.equal(await response.text(), 'middleware')

The documented built-in middleware entry points include logger from hono/logger and cors from hono/cors, used as app.use(logger()) and app.use('/api/*', cors()).

Validation

Use validator from hono/validator. Documented validation targets are form, json, query, header, cookie, and param. A validator callback may return a response for invalid input or return validated data; later handlers retrieve it with c.req.valid(target).

import { strict as assert } from 'node:assert'
import { Hono } from 'hono'
import { validator } from 'hono/validator'

const app = new Hono()

app.get(
  '/search',
  validator('query', (value, c) => {
    const term = value['term']
    if (!term || typeof term !== 'string') return c.text('Invalid!', 400)
    return { term }
  }),
  (c) => c.json(c.req.valid('query'))
)

const valid = await app.request('/search?term=hono')
assert.equal(valid.status, 200)
assert.deepEqual(await valid.json(), { term: 'hono' })

const invalid = await app.request('/search')
assert.equal(invalid.status, 400)
assert.equal(await invalid.text(), 'Invalid!')

Hono 4 migration traps

When updating older examples, apply these changes:

  • c.jsonT() was removed; use c.json().
  • c.stream() and c.streamText() moved to hono/streaming.
  • c.env() was removed; use getRuntimeKey().
  • app.handleEvent() was removed; use app.fetch().
  • app.showRoutes() and app.routerName moved to showRoutes() and getRouterName() from hono/dev.
  • c.req.cookie() was removed; use getCookie() from hono/cookie.
  • app.head() is no longer needed because app.get() implicitly handles HEAD.
  • Hono request members including headers(), body(), bodyUsed(), integrity(), keepalive(), referrer(), and signal() are now accessed on req.raw; for example, req.raw.headers.
  • Cloudflare Workers serveStatic requires a manifest, passed as { root: './assets', manifest }.
  • JSX renderer docType now defaults to true.
  • FC in hono/jsx no longer passes children; use PropsWithChildren when children are needed.

Other entry points

The research documents these entry points:

  • hono/quick: Hono for applications initialized per request.
  • hono/tiny: the smallest router package.
  • hono/html: html and raw.
  • hono/cookie: getCookie and setCookie.
  • hono/testing: testClient.
  • hono/adapter: env and getRuntimeKey.

Use the documented exports from those subpaths rather than assuming they are methods on the app or context. In particular, cookie access moved out of c.req, and runtime-key access replaced c.env().

JSX and RPC boundaries

JSX requires a .tsx file and compiler configuration with jsx: "react-jsx" and jsxImportSource: "hono/jsx"; JSX is not a plain .ts feature.

RPC is type-level and build-tool dependent. The server exports typeof route as AppType; separate projects need matching Hono versions, strict TypeScript and built declarations before consumption. Preserve project references when the existing build graph uses them. It is not something to validate with a bare bun example.ts script.

Build a typed client from the route value

Preserve the return type of the chained route registration. Export that route's type and import it with import type in the client. Keep strict TypeScript and matching Hono versions across both projects.

import { Hono } from "hono";
import { validator } from "hono/validator";

const route = new Hono().post(
  "/items",
  validator("json", (value: { title: string }, c) => {
    if (typeof value?.title !== "string") return c.json({ error: "title" }, 400);
    return { title: value.title };
  }),
  (c) => c.json({ title: c.req.valid("json").title }, 201),
);

export type AppType = typeof route;
export default route;

For a separate package, build exported declarations before the consumer typecheck. Use hc<AppType> from hono/client. The JSON input and status-specific response types should fail compilation when the client sends the wrong shape. Request-only tests cannot prove that boundary.

Keep serving and runtime boundaries separate

Use the runtime's documented adapter to serve app.fetch; request tests do not start a listener. Verify one valid request, one rejected request and middleware ordering before serving. Set the JSON Content-Type in validator tests. Retain the host's connection, authentication and shutdown conventions.

For Node.js, install a compatible @hono/node-server separately and close its returned server during graceful shutdown:

import { serve } from "@hono/node-server";
import app from "./app.js";

const server = serve({ fetch: app.fetch, port: 3000 });
process.once("SIGTERM", () => {
  server.close();
});

The loopback fixture checks adapter 2.1.4, an assigned local port, response status and body, and listener closure. It does not validate production signal handling, WebSockets, Bun or Deno serving.

Read Hono RPC when consuming typed routes, and Node integration when starting a Node server. Other runtimes need their own documented entry point.

Remaining API coverage

Detailed signatures for the quick and tiny presets, HTML and cookie helpers, streaming, static serving and JSX components require their own task-specific examples. RPC transport authentication and non-Node server lifecycles remain outside these fixtures. Consult the relevant runtime documentation before deployment.