Skip to content
Merged

v5 #914

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
394b971
docs: add the Coding Agents pages
yusukebe Oct 8, 2026
01514ba
docs: add the v5 migration guide
yusukebe Oct 8, 2026
93dc52a
docs(factory): add defineHandler() and defineMiddleware()
yusukebe Oct 8, 2026
011a1dd
docs: rewrite Best Practices with defineHandler()
yusukebe Oct 8, 2026
7cf07f9
docs: mention coding agents in Getting Started
yusukebe Oct 8, 2026
1ab3c84
feat: show the create command on the top page
yusukebe Oct 8, 2026
71ab6c7
docs(agents): install the skills with skills.sh or GitHub CLI
yusukebe Oct 8, 2026
0ae7a78
docs(agents): show the Verify section for AGENTS.md
yusukebe Oct 8, 2026
4b948e0
docs(agents): point AGENTS.md at hono --help
yusukebe Oct 8, 2026
e6e33b6
docs(agents): the Verify lines also work as a prompt
yusukebe Oct 8, 2026
f173a1b
docs(agents): one line for AGENTS.md, under a Hono CLI heading
yusukebe Oct 8, 2026
109c16f
docs: drop the type arguments from Best Practices
yusukebe Oct 8, 2026
b4cf2db
docs: Env as a type argument, factory for larger apps
yusukebe Oct 8, 2026
89bb485
docs: plain handlers also lose the RPC types
yusukebe Oct 8, 2026
f106dfc
docs: a handler defined elsewhere needs no type argument
yusukebe Oct 8, 2026
71e39e7
docs: compare a handler variable, not an inline handler
yusukebe Oct 8, 2026
7a60d7e
docs: compare the same handler in the first section
yusukebe Oct 8, 2026
487b0a2
docs: validate the request value, so use defineHandler()
yusukebe Oct 8, 2026
14b60ad
docs: mention createFactory() for a shared Env
yusukebe Oct 8, 2026
156cec1
docs: write the Validate and middleware examples inline
yusukebe Oct 8, 2026
0fc9a2b
docs: start Best Practices with defineHandler(), without the Controll…
yusukebe Oct 8, 2026
d36308d
docs: mention Valibot
yusukebe Oct 8, 2026
184555c
docs: keep the example comment inside the code block width
yusukebe Oct 8, 2026
cf94ddb
docs: drop the deno.land/x note from the v5 migration guide
yusukebe Oct 8, 2026
6228782
docs(agents): follow @hono/cli rc.1, without optimize
yusukebe Oct 8, 2026
09d0d5b
docs(agents): add --runtime vite from @hono/cli rc.1
yusukebe Oct 10, 2026
7e74551
docs(agents): do not point readers to cf init
yusukebe Oct 10, 2026
134bd96
docs: follow hono v5 rc.1 in the migration guide and the factory helper
yusukebe Oct 10, 2026
63383a9
docs: hold the v5-only pages for GA
yusukebe Oct 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ const sidebars = (): DefaultTheme.SidebarItem[] => [
text: 'Guides',
collapsed: true,
items: [
{ text: 'Migrating to v5', link: '/docs/guides/migrating-to-v5' },
{ text: 'create-hono', link: '/docs/guides/create-hono' },
{ text: 'Middleware', link: '/docs/guides/middleware' },
{ text: 'Helpers', link: '/docs/guides/helpers' },
Expand Down Expand Up @@ -231,9 +232,11 @@ const sidebars = (): DefaultTheme.SidebarItem[] => [
],
},
{
text: 'LLM',
text: 'Coding Agents',
collapsed: true,
items: [
{ text: 'Overview', link: '/docs/agents/' },
{ text: 'Hono CLI', link: '/docs/agents/cli' },
{
text: 'Docs List',
link: '/llms.txt',
Expand Down
4 changes: 4 additions & 0 deletions .vitepress/theme/Layout.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
<script setup lang="ts">
import DefaultTheme from 'vitepress/theme'
import HeroCommand from './components/HeroCommand.vue'
import HeroImage from './components/HeroImage.vue'

const { Layout } = DefaultTheme
</script>

<template>
<Layout>
<template #home-hero-actions-after>
<HeroCommand />
</template>
<template #home-hero-image>
<HeroImage />
</template>
Expand Down
67 changes: 67 additions & 0 deletions .vitepress/theme/components/HeroCommand.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<script setup lang="ts">
import { ref } from 'vue'

const command = 'npm create hono@latest'
const copied = ref(false)

async function copy() {
try {
await navigator.clipboard.writeText(command)
copied.value = true
setTimeout(() => {
copied.value = false
}, 1500)
} catch {
// Clipboard access can be unavailable; the command is still selectable text.
}
}
</script>

<template>
<div class="hero-command">
<code class="hero-command-text"
><span class="hero-command-prompt" aria-hidden="true">$ </span
>{{ command }}</code
>
<button
type="button"
class="hero-command-copy"
:class="{ copied }"
:aria-label="copied ? 'Copied' : 'Copy command'"
:title="copied ? 'Copied' : 'Copy'"
@click="copy"
>
<svg
v-if="!copied"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
<path
d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"
/>
</svg>
<svg
v-else
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M20 6 9 17l-5-5" />
</svg>
</button>
</div>
</template>
52 changes: 52 additions & 0 deletions .vitepress/theme/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -330,3 +330,55 @@ html.kawaii-mode .hero-image-slot {
display: none;
}
}

/* The create command under the hero buttons. Follows the hero's text-align, so it is
centered on narrow screens and left-aligned next to the code window. */
.hero-command {
display: inline-flex;
align-items: center;
gap: 4px;
margin-top: 20px;
padding: 0 6px 0 16px;
height: 40px;
border: 1px solid var(--vp-c-divider);
border-radius: 20px;
background-color: var(--vp-c-bg-soft);
}

.hero-command-text {
font-family: var(--vp-font-family-mono);
font-size: 14px;
color: var(--vp-c-text-1);
white-space: nowrap;
}

.hero-command-prompt {
color: var(--vp-c-text-3);
user-select: none;
}

.hero-command-copy {
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: 14px;
color: var(--vp-c-text-2);
transition: color 0.25s, background-color 0.25s;
}

.hero-command-copy:hover {
color: var(--vp-c-text-1);
background-color: var(--vp-c-default-soft);
}

.hero-command-copy.copied {
color: var(--vp-c-brand-1);
}

@media (min-width: 640px) {
.hero-command {
margin-top: 24px;
}
}
224 changes: 224 additions & 0 deletions docs/agents/cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
# Hono CLI

Hono CLI (`hono`) is a command-line tool for Hono, made for coding agents. It loads your Hono app directly, so an agent can inspect and test the app without starting a server. Commands print JSON by default. `routes`, `request`, `benchmark`, and `ssg` take `--plain` when a human reads the output.

## Installation

Install it in your project. Coding agents find it in `package.json`.

```sh
npm install -D @hono/cli
```

Or globally:

```sh
npm install -g @hono/cli
```

## Output

Commands print JSON to stdout. `snapshot` prints batch JSONL lines instead.

- Success: `{ "ok": true, "data": ... }` with exit code 0
- Failure: `{ "ok": false, "error": { "code", "message", "suggestions", "docs" } }` with exit code 1

On failure, `error.suggestions` says what to try next, and `error.docs` points to a page on this site. Logs go to stderr.

`hono --help` starts with a short note for coding agents, and `hono <command> --help` has examples and notes for each command. An agent needs nothing else to use the CLI.

## Commands

| Command | What it does |
| ---------------------------- | ------------------------------------------------------ |
| `hono routes [file]` | Show routes of your Hono app |
| `hono request [path] [file]` | Send a request to your app using `app.request()` |
| `hono batch <source> [file]` | Run multiple requests from JSONL using `app.request()` |
| `hono snapshot [file]` | Print the current behavior as batch JSONL lines |
| `hono benchmark [file]` | Measure the performance of your Hono app |
| `hono ssg [file]` | Generate static files from your Hono app |

`file` is the path to your app file. When omitted, the app is found in `src/index.ts`, `src/index.tsx`, `src/index.js`, or `src/index.jsx`. TypeScript and JSX are supported.

### routes

Show all routes of your Hono app, like [`showRoutes()`](/docs/helpers/dev#showroutes). Routes are resolved from the real app instance, so mounted sub-apps and `basePath` are expanded.

```sh
hono routes
hono routes --verbose src/app.ts
```

- `--verbose` - include middleware
- `--plain` - human-readable output instead of JSON
- `-e, --external <package>` - mark a package as external (can be used multiple times)

```json
{
"ok": true,
"data": {
"router": "SmartRouter + RegExpRouter",
"routes": [
{
"method": "GET",
"path": "/",
"name": "[handler]",
"isMiddleware": false
},
{
"method": "POST",
"path": "/posts",
"name": "[handler]",
"isMiddleware": false
}
]
}
}
```

### request

Send a request to your app through `app.request()`. No server is needed. `path` defaults to `/`.

```sh
hono request /
hono request /users/123
hono request /api/users -X POST -d '{"name":"Alice"}'
hono request /api/protected -H 'Authorization: Bearer token'
cat payload.json | hono request /api/users -X POST -d @-
hono request /api/users/123 --trace
hono request / --runtime bun
```

- `-X, --method <method>` - HTTP method (default: `GET`)
- `-d, --data <data>` - request body (`@file` reads a file, `@-` reads stdin)
- `-H, --header <header>` - custom header (can be used multiple times)
- `--trace` - include the matched routes in the output
- `--runtime <runtime>` - run the app on `node` (default), `bun`, `deno`, `workerd`, or `vite`
- `--compact` - one-line JSON without the headers
- `--no-bindings` - skip loading the local Cloudflare bindings
- `-w, --watch` - watch for changes and resend the request
- `-o, --output <file>` - write the response body to a file
- `--plain` - print the raw body like curl (`-i` adds the status and headers, `-I` shows only them)

A JSON response body is embedded as an object:

```json
{
"ok": true,
"data": {
"status": 200,
"headers": { "content-type": "application/json" },
"body": { "message": "Hello World" }
}
}
```

With `--trace`, the output has `matchedRoutes`: which middleware and handler matched, and which one responded. A 404 result suggests running it.

You can also pass the app code from stdin with `-` as the file. `app` is predefined and exported for you:

```sh
echo 'app.get("/hello", (c) => c.json({ ok: true }))' | hono request /hello -
```

#### Cloudflare bindings

In a project with a wrangler config, `c.env` carries the real local bindings (KV, D1, R2, vars) automatically, while the app runs on Node.js. This works in `request`, `batch`, `snapshot`, and `ssg`. Skip it with `--no-bindings`. It needs [wrangler](https://developers.cloudflare.com/workers/wrangler/) installed in the project.

`--runtime workerd` runs the whole app inside workerd instead, with the wrangler config. It is heavier, but it is the full runtime. The entry is `main` in the wrangler config, so pass no file argument.

#### Vite

`--runtime vite` sends the requests through the Vite dev server of the project, for an app that a Vite plugin builds. The app comes from the Vite config, so pass no file argument. In a project with `cloudflare.config.ts` and a Vite config, it is the default, and `c.env` has the bindings. This works in `request`, `batch`, and `snapshot`. A file argument, `--no-bindings`, `--trace`, or `--watch` runs the app on Node.js instead.

### batch

Run multiple requests from JSONL in one call, in order, against one app instance. In-memory state carries between steps.

```sh
hono batch - <<'JSONL'
{"path":"/users","expect":{"status":200}}
{"method":"POST","path":"/users","body":{"name":"Momo"},"expect":{"status":201,"body":{"name":"Momo"}},"save":{"id":".id"}}
{"path":"/users/{{id}}","expect":{"status":200}}
{"method":"DELETE","path":"/users/{{id}}","expect":{"status":204}}
JSONL
```

One JSON object per line: `method`, `path`, `body`, `headers`, `expect`, and `save`.

- `expect` declares the acceptance criteria. `status` matches exactly. `body` is a deep partial match: declared fields must match, and extra fields in the response are ignored.
- `save` stores a value from the response body by dot path, and later steps use it as `{{id}}`.
- A step without `expect` passes on any 2xx or 3xx and fails on a 4xx or 5xx. To accept a 4xx on purpose, declare it with `expect.status`.
- A failed step carries `diff`, one line per mismatch.

The output has the actual `status` and `body` of each step, `pass` per step, and a `summary`. Rerun until `failed` is 0.

- `-H, --header <header>` - a shared header for every step
- `--compact` - print only the failed steps and the summary
- `--runtime <runtime>` - `node` (default), `workerd`, or `vite`
- `--no-bindings` - skip loading the local Cloudflare bindings

### snapshot

Print the current behavior of the app as batch JSONL lines, to stdout. No file is written.

```sh
hono snapshot
hono snapshot --status-only src/app.ts
```

Paramless GET routes are executed, and their actual status and body become the `expect`. Routes with params and non-GET routes are printed without one, for you to fill in. One probe line records the response for a path that matches no route.

Capture before a refactor, then rerun the lines with `hono batch` until `failed` is 0.

- `--status-only` - capture only the status codes, not the bodies
- `--runtime <runtime>` - `node` (default), `workerd`, or `vite`
- `--no-bindings` - skip loading the local Cloudflare bindings

### benchmark

Measure the performance of your Hono app. It is a micro benchmark of routing and handlers: `app.request()` is called directly, with no HTTP stack and no network. Each run happens in a fresh process, so results are comparable.

```sh
hono benchmark
hono benchmark -P /users
hono benchmark -P /users -X POST -d '{"name":"Alice"}' -H 'Content-Type: application/json'
hono benchmark --hono 4.12.3 --hono 4.13.0
hono benchmark --hono ../hono
```

- `-P, --path <path>` - benchmark only this path (can be used multiple times)
- `-X`, `-d`, `-H` - the method, body, and headers for `-P` paths
- `--duration <ms>` - how long to measure each route (default: `500`)
- `--warmup <count>` - requests before measuring (default: `30`)
- `--hono <version-or-path>` - benchmark the same app with another Hono: an npm version, or a local checkout

A few percent of difference is noise. To compare, run it more than once and check that the difference repeats.

### ssg

Generate static files from your Hono app, like the [SSG helper](/docs/helpers/ssg).

```sh
hono ssg
hono ssg -o dist/static src/app.ts
hono ssg --exclude '/api/*'
```

- `-o, --outdir <dir>` - output directory (default: `static`)
- `--include <path>` / `--exclude <path>` - select routes by path. `*` matches anything
- `--no-bindings` - skip loading the local Cloudflare bindings

A page that does not answer 200 is not written. It is listed in `skipped` with its status, so check it with `hono request <path>`.

```json
{
"ok": true,
"data": {
"output": "static",
"files": ["static/index.html", "static/about.html"],
"skipped": [{ "path": "/counter", "status": 500 }]
}
}
```
Loading
Loading