This is the tiny developer documentation for Hono.
# Start of Hono documentation
# Hono
Hono - _**\[ç\] ð¥**_ - ã¯å°ãããã·ã³ãã«ã§çéãªãšããžåãWebãã¬ãŒã ã¯ãŒã¯ã§ãã
ããããJavaScriptã©ã³ã¿ã€ã ã§åäœããŸã: Cloudflare Workers ã Fastly Compute ã Deno ã Bun ã Vercel ã Netlify ã AWS Lambda ã Lambda@Edge ãã㊠Node.jsã
Honoã¯éããã©ãéãã ãã§ã¯ãããŸããã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hono!'))
export default app
```
## ã¯ã€ãã¯ã¹ã¿ãŒã
ãããå®è¡ããã ãã§ã:
::: code-group
```sh [npm]
npm create hono@latest
```
```sh [yarn]
yarn create hono
```
```sh [pnpm]
pnpm create hono@latest
```
```sh [bun]
bun create hono@latest
```
```sh [deno]
deno init --npm hono@latest
```
:::
## ç¹åŸŽ
- **çé** ð - `RegExpRouter` ã¯éåžžã«é«éãªã«ãŒã¿ãŒã§ãã ç·åœ¢ã«ãŒãã䜿çšããŸããã ãã¡ããã¡ãéã!
- **軜é** 𪶠- `hono/tiny` ããªã»ãã㯠14KB æªæºã§ãã Hono ã¯äŸåé¢ä¿ãç¡ã Web æšæºã®ã¿ã䜿çšããŸãã
- **ãã«ãã©ã³ã¿ã€ã ** ð - Cloudflare Workers ã Fastly Compute ã Deno ã Bun ã AWS Lambda ã Node.js ã§åäœããŸãã åãã³ãŒãããã¹ãŠã®ãã©ãããã©ãŒã äžã§åäœããŸãã
- **ããããªãŒå梱** ð - Hono ã«ã¯ãã«ãã€ã³ããã«ãŠã§ã¢ãã«ã¹ã¿ã ããã«ãŠã§ã¢ããµãŒãããŒãã£ãŒããã«ãŠã§ã¢åã³ãã«ããŒãå«ãŸããŠããŸãã ããããªãŒå梱!
- **楜ãã DX** ð - éåžžã«ã¯ãªãŒã³ãª API ã æäžçŽã® TypeScript ãµããŒãã Now, we've got "Types".
## 䜿çšäŸ
Hono 㯠Express ã«äŒŒãããã³ããšã³ããæããªãWebã¢ããªã±ãŒã·ã§ã³ãã¬ãŒã ã¯ãŒã¯ã§ãã
ããã CDN ãšããžã§ããã«ãŠã§ã¢ãçµã¿åãããããšã§ãã倧èŠæš¡ãªã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ã§ããŸãã
以äžã«ããã€ãã®äœ¿çšäŸã玹ä»ããŸãã
- Web API ã®æ§ç¯
- ããã¯ãšã³ããµãŒããŒã®ãããã·
- CDN ã®ããã³ã
- ãšããžã¢ããªã±ãŒã·ã§ã³
- ã©ã€ãã©ãªã®ããŒã¹ãµãŒããŒ
- ãã«ã¹ã¿ãã¯ã¢ããªã±ãŒã·ã§ã³
## 誰ã Hono ã䜿ã£ãŠããŸãã?
| Project | Platform | What for? |
| ---------------------------------------------------------------------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------- |
| [cdnjs](https://cdnjs.com) | Cloudflare Workers | A free and open-source CDN service. _Hono is used for the API server_. |
| [Cloudflare D1](https://www.cloudflare.com/developer-platform/d1/) | Cloudflare Workers | Serverless SQL databases. _Hono is used for the internal API server_. |
| [Cloudflare Workers KV](https://www.cloudflare.com/developer-platform/workers-kv/) | Cloudflare Workers | Serverless key-value database. _Hono is used for the internal API server_. |
| [BaseAI](https://baseai.dev) | Local AI Server | Serverless AI agent pipes with memory. An open-source agentic AI framework for web. _API server with Hono_. |
| [Unkey](https://unkey.dev) | Cloudflare Workers | An open-source API authentication and authorization. _Hono is used for the API server_. |
| [OpenStatus](https://openstatus.dev) | Bun | An open-source website & API monitoring platform. _Hono is used for the API server_. |
| [Deno Benchmarks](https://deno.com/benchmarks) | Deno | A secure TypeScript runtime built on V8. _Hono is used for benchmarking_. |
ãããŠã
- [Drivly](https://driv.ly/) - Cloudflare Workers
- [repeat.dev](https://repeat.dev/) - Cloudflare Workers
Do you want to see more? See [Who is using Hono in production?](https://github.com/orgs/honojs/discussions/1510).
## Hono 1åã¯ããã³ã°
Hono ã䜿çšã㊠Cloudflare Workers åãã®ã¢ããªã±ãŒã·ã§ã³ãäœæãããã¢ã

## çé
**Hono ã¯æéã§ã**ã Cloudflare Workers åãã®ä»ã®ã«ãŒã¿ãŒãšæ¯èŒããŠã¿ãŸãããã
```
Hono x 402,820 ops/sec ±4.78% (80 runs sampled)
itty-router x 212,598 ops/sec ±3.11% (87 runs sampled)
sunder x 297,036 ops/sec ±4.76% (77 runs sampled)
worktop x 197,345 ops/sec ±2.40% (88 runs sampled)
Fastest is Hono
âš Done in 28.06s.
```
[ä»ã®ãã³ãããŒã¯](/docs/concepts/benchmarks) ã確èªããŠãã ããã
## 軜é
**Hono ã¯ãšãŠãå°ããã§ã**ã `hono/tiny` ããªã»ããã䜿çšããæã Minify ããã° **14KB 以äž** ã«ãªããŸãã ããã«ãŠã§ã¢ãã¢ããã¿ã¯ãããããããŸããã䜿çšãããšãã®ã¿ãã³ãã«ãããŸãã ã¡ãªã¿ã« Express 㯠572KB ãããŸãã
```
$ npx wrangler dev --minify ./src/index.ts
â
ïž wrangler 2.20.0
--------------------
⬣ Listening at http://0.0.0.0:8787
- http://127.0.0.1:8787
- http://192.168.128.165:8787
Total Upload: 11.47 KiB / gzip: 4.34 KiB
```
## è€æ°ã®ã«ãŒã¿ãŒ
**Hono ã¯è€æ°ã®ã«ãŒã¿ãŒãæã£ãŠããŸã**ã
**RegExpRouter** 㯠JavaScript ã§æéã®ã«ãŒã¿ãŒã§ãã ãã£ã¹ãããåã«äœæãããåäžã®å·šå€§ãªæ£èŠè¡šçŸã䜿çšããŠã«ãŒããæ€çŽ¢ããŸãã **SmartRouter** ãšäœµçšãããšå
šãŠã®ã«ãŒãã£ã³ã°ãã¿ãŒã³ããµããŒãããŸãã
**LinearRouter** ã¯ã«ãŒãã®ç»é²ãéåžžã«é«éãªãããã¢ããªã±ãŒã·ã§ã³ãæ¯ååæåãããç°å¢ã«é©ããŠããŸãã **PatternRouter** ã¯ãã¿ãŒã³ãè¿œå ããŠç
§åããã ããªã®ã§å°ãããªããŸãã
[ã«ãŒãã£ã³ã°ã®è©³çŽ°](/docs/concepts/routers)ãã確èªãã ããã
## Web æšæº
**Web æšæº**ã䜿çšããŠãããããã§ã Hono ã¯æ²¢å±±ã®ãã©ãããã©ãŒã äžã§åäœããŸãã
- Cloudflare Workers
- Cloudflare Pages
- Fastly Compute
- Deno
- Bun
- Vercel
- AWS Lambda
- Lambda@Edge
- ãã®ä»...!
[Node.js ã¢ããã¿](https://github.com/honojs/node-server)ã䜿ã£ãŠ Hono 㯠Node.js ã§ãåããŸãã
[Web æšæºã«ã€ããŠã®è©³çŽ°](/docs/concepts/web-standard)ãã確èªãã ããã
## ããã«ãŠã§ã¢ & ãã«ããŒ
**Hono ã«ã¯æ²¢å±±ã®ããã«ãŠã§ã¢ããã«ããŒããããŸã**ã ããã㯠"Write Less, do more" ãå®çŸããŸãã
Hono ã¯ä»¥äžã®ããã«äœ¿ããããã«ãŠã§ã¢ãšãã«ããŒãæäŸããŸã:
- [Basic èªèšŒ](/docs/middleware/builtin/basic-auth)
- [Bearer èªèšŒ](/docs/middleware/builtin/bearer-auth)
- [Body Limit](/docs/middleware/builtin/body-limit)
- [ãã£ãã·ã¥](/docs/middleware/builtin/cache)
- [å§çž®](/docs/middleware/builtin/compress)
- [Context Storage](/docs/middleware/builtin/context-storage)
- [Cookie](/docs/helpers/cookie)
- [CORS](/docs/middleware/builtin/cors)
- [ETag](/docs/middleware/builtin/etag)
- [html](/docs/helpers/html)
- [JSX](/docs/guides/jsx)
- [JWT èªèšŒ](/docs/middleware/builtin/jwt)
- [Logger](/docs/middleware/builtin/logger)
- [Language](/docs/middleware/builtin/language)
- [Pretty JSON](/docs/middleware/builtin/pretty-json)
- [Secure Headers](/docs/middleware/builtin/secure-headers)
- [SSG](/docs/helpers/ssg)
- [ã¹ããªãŒãã³ã°](/docs/helpers/streaming)
- [GraphQL ãµãŒããŒ](https://github.com/honojs/middleware/tree/main/packages/graphql-server)
- [Firebase èªèšŒ](https://github.com/honojs/middleware/tree/main/packages/firebase-auth)
- [Sentry](https://github.com/honojs/middleware/tree/main/packages/sentry)
- etc...!
äŸãã°ã ETag ãš ãªã¯ãšã¹ããã®ã³ã°ãè¿œå ããããã«ã¯ Hono ã䜿çšããŠä»¥äžã®ã³ãŒããæžãã ãã§ã:
```ts
import { Hono } from 'hono'
import { etag } from 'hono/etag'
import { logger } from 'hono/logger'
const app = new Hono()
app.use(etag(), logger())
```
[ããã«ãŠã§ã¢ã®è©³çŽ°](/docs/concepts/middleware)ãã確èªãã ããã
## éçºäœéš
Hono ã¯æ¥œãã "**éçºäœéš**" ãæäŸããŸãã
`Context` ãªããžã§ã¯ãã«ãã£ãŠ Request/Response ãžç°¡åã«ã¢ã¯ã»ã¹ã§ããŸãã
æŽã«ã Hono 㯠TypeScript ã§æžãããŠããã "**å**" ãæã£ãŠããŸãã
äŸãã°ããã¹ãã©ã¡ãŒã¿ã¯ãªãã©ã«åã«ãªããŸãã

ãããŠãããªããŒã¿ãŒãš Hono Client `hc` 㯠RPC ã¢ãŒããæå¹ã«ããŸãã RPC ã¢ãŒãã§ã¯ã
Zod ãªã©ã®ãæ°ã«å
¥ãã®ããªããŒã¿ãŒã䜿çšããŠããµãŒããŒãµã€ã API ä»æ§ãã¯ã©ã€ã¢ã³ããšç°¡åã«å
±æããŠã¿ã€ãã»ãŒããªã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ã§ããŸãã
[Hono Stacks](/docs/concepts/stacks)ãã確èªãã ããã
# ãã¹ããã©ã¯ãã£ã¹
Hono ã¯ãšãŠãæè»ã§ãã ããªãã®å¥œããªããã«ã¢ããªãæžãããšãåºæ¥ãŸãã
ããããåŸã£ãã»ããè¯ããã¹ããã©ã¯ãã£ã¹ããããŸãã
## ã§ããã ã "ã³ã³ãããŒã©ãŒ" ãäœããªãã§ãã ãã
極åã "Ruby on Rails ã®ãããªã³ã³ãããŒã©ãŒ" ã¯äœãã¹ãã§ã¯ãããŸããã
```ts
// ð
// A RoR-like Controller
const booksList = (c: Context) => {
return c.json('list books')
}
app.get('/books', booksList)
```
åé¡ã¯åã«é¢ä¿ããŠããŸãã äŸãã°ãè€éãªãžã§ããªã¯ã¹ãæžããªãéããã³ã³ãããŒã©ãŒã§ã¯ãã¹ãã©ã¡ãŒã¿ãæšè«ã§ããŸããã
```ts
// ð
// A RoR-like Controller
const bookPermalink = (c: Context) => {
const id = c.req.param('id') // Can't infer the path param
return c.json(`get ${id}`)
}
```
ãã®ããã RoR-like ãªã³ã³ãããŒã©ãŒãäœãå¿
èŠã¯ãªãããã¹å®çŸ©ã®çŽåŸã«ãã³ãã©ãæžãã¹ãã§ãã
```ts
// ð
app.get('/books/:id', (c) => {
const id = c.req.param('id') // Can infer the path param
return c.json(`get ${id}`)
})
```
## `hono/factory` ã® `factory.createHandlers()`
ããã§ã RoR-like ãªã³ã³ãããŒã©ãŒãäœãããå Žåã [`hono/factory`](/docs/helpers/factory) ã® `factory.createHandlers()` ã䜿ã£ãŠãã ããã ããã䜿ãå Žåãåæšè«ã¯æ£ããåäœããŸãã
```ts
import { createFactory } from 'hono/factory'
import { logger } from 'hono/logger'
// ...
// ð
const factory = createFactory()
const middleware = factory.createMiddleware(async (c, next) => {
c.set('foo', 'bar')
await next()
})
const handlers = factory.createHandlers(logger(), middleware, (c) => {
return c.json(c.var.foo)
})
app.get('/api', ...handlers)
```
## 倧ããªã¢ããªã±ãŒã·ã§ã³ãäœã
"Ruby on Rails ã®ãããªã³ã³ãããŒã©ãŒ" ãäœãããšç¡ã倧ããªã¢ããªã±ãŒã·ã§ã³ãäœãã«ã¯ `app.route()` ã䜿ããŸãã
ã¢ããªã±ãŒã·ã§ã³ã« `/authors` ãš `/books` ãšãããšã³ããã€ã³ãããã£ãŠ `index.ts` ãåå²ãããå Žå㯠`authors.ts` ãš `books.ts` ãäœæããŸãã
```ts
// authors.ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.json('list authors'))
app.post('/', (c) => c.json('create an author', 201))
app.get('/:id', (c) => c.json(`get ${c.req.param('id')}`))
export default app
```
```ts
// books.ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.json('list books'))
app.post('/', (c) => c.json('create a book', 201))
app.get('/:id', (c) => c.json(`get ${c.req.param('id')}`))
export default app
```
次ã«ãããããã€ã³ããŒãã `app.route()` 㧠`/authors` ãš `/books` ãããŠã³ãããŸãã
```ts
// index.ts
import { Hono } from 'hono'
import authors from './authors'
import books from './books'
const app = new Hono()
// ð
app.route('/authors', authors)
app.route('/books', books)
export default app
```
### RPC æ©èœã䜿ãããå Žå
äžã®ã³ãŒãã¯æ®éã®äœ¿ãæ¹ã§ã¯ããŸãåããŸãã
ãããã `RPC` æ©èœã䜿ãããå Žåã¯ä»¥äžã®ããã«å€æŽããããšã§æ£ããåã«ããããšãã§ããŸãã
```ts
// authors.ts
import { Hono } from 'hono'
const app = new Hono()
.get('/', (c) => c.json('list authors'))
.post('/', (c) => c.json('create an author', 201))
.get('/:id', (c) => c.json(`get ${c.req.param('id')}`))
export default app
```
`app` ã®åã `hc` ã«æž¡ãããšã§ãæ£ããåã«ãªããŸãã
```ts
import app from './authors'
import { hc } from 'hono/client'
// ð
const client = hc('http://localhost') // Typed correctly
```
詳ããã¯ã [RPC ã®ããŒãž](/docs/guides/rpc#using-rpc-with-larger-applications) ã埡芧ãã ããã
# äŸ
[䜿çšäŸã®äžèŠ§](/examples/)ãã芧ãã ããã
# ãããã質å
Hono ã®ãããã質å (FAQ) ãšè§£æ±ºæ¹æ³ããŸãšããã¬ã€ãã
## Hono å
¬åŒã® Renovate èšå®ã¯ãããŸãã?
Hono ããŒã ã¯çŸåš [Renovate](https://github.com/renovatebot/renovate) èšå®ãã¡ã³ããã³ã¹ããŠããŸããã
ãã®ãããäžã®ããã«ãµãŒãããŒãã£ãŒã® renovate-config ã䜿çšããŠãã ããã
`renovate.json` :
```json
// renovate.json
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"github>shinGangan/renovate-config-hono" // [!code ++]
]
}
```
[renovate-config-hono](https://github.com/shinGangan/renovate-config-hono) ãªããžããªã§è©³çŽ°ã確èªããŠãã ããã
# ãã«ããŒ
ã¢ããªã±ãŒã·ã§ã³ã®éçºãæå©ããããã«ããŒããããŸãã ããã«ãŠã§ã¢ãšéã£ãŠããã³ãã©ãšããŠæ©èœããã®ã§ã¯ãªãã䟿å©ãªé¢æ°ãæäŸããŸãã
äŸãã°ã [Cookie helper](/docs/helpers/cookie) ã®äœ¿ãæ¹ã¯ä»¥äžã®ãšããã§ãã
```ts
import { getCookie, setCookie } from 'hono/cookie'
const app = new Hono()
app.get('/cookie', (c) => {
const yummyCookie = getCookie(c, 'yummy_cookie')
// ...
setCookie(c, 'delicious_cookie', 'macha')
//
})
```
## å©çšå¯èœãªãã«ããŒ
- [Accepts](/docs/helpers/accepts)
- [Adapter](/docs/helpers/adapter)
- [Cookie](/docs/helpers/cookie)
- [css](/docs/helpers/css)
- [Dev](/docs/helpers/dev)
- [Factory](/docs/helpers/factory)
- [html](/docs/helpers/html)
- [JWT](/docs/helpers/jwt)
- [SSG](/docs/helpers/ssg)
- [Streaming](/docs/helpers/streaming)
- [Testing](/docs/helpers/testing)
- [WebSocket](/docs/helpers/websocket)
# Client Components
`hono/jsx` supports not only server side but also client side. This means that it is possible to create an interactive UI that runs in the browser. We call it Client Components or `hono/jsx/dom`.
It is fast and very small. The counter program in `hono/jsx/dom` is only 2.8KB with Brotli compression. But, 47.8KB for React.
This section introduces Client Components-specific features.
## Counter example
Here is an example of a simple counter, the same code works as in React.
```tsx
import { useState } from 'hono/jsx'
import { render } from 'hono/jsx/dom'
function Counter() {
const [count, setCount] = useState(0)
return (
Count: {count}
setCount(count + 1)}>Increment
)
}
function App() {
return (
)
}
const root = document.getElementById('root')
render( , root)
```
## `render()`
You can use `render()` to insert JSX components within a specified HTML element.
```tsx
render( , container)
```
## Hooks compatible with React
hono/jsx/dom has Hooks that are compatible or partially compatible with React. You can learn about these APIs by looking at [the React documentation](https://react.dev/reference/react/hooks).
- `useState()`
- `useEffect()`
- `useRef()`
- `useCallback()`
- `use()`
- `startTransition()`
- `useTransition()`
- `useDeferredValue()`
- `useMemo()`
- `useLayoutEffect()`
- `useReducer()`
- `useDebugValue()`
- `createElement()`
- `memo()`
- `isValidElement()`
- `useId()`
- `createRef()`
- `forwardRef()`
- `useImperativeHandle()`
- `useSyncExternalStore()`
- `useInsertionEffect()`
- `useFormStatus()`
- `useActionState()`
- `useOptimistic()`
## `startViewTransition()` family
The `startViewTransition()` family contains original hooks and functions to handle [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) easily. The followings are examples of how to use them.
### 1. An easiest example
You can write a transition using the `document.startViewTransition` shortly with the `startViewTransition()`.
```tsx
import { useState, startViewTransition } from 'hono/jsx'
import { css, Style } from 'hono/css'
export default function App() {
const [showLargeImage, setShowLargeImage] = useState(false)
return (
<>
startViewTransition(() =>
setShowLargeImage((state) => !state)
)
}
>
Click!
{!showLargeImage ? (
) : (
)}
>
)
}
```
### 2. Using `viewTransition()` with `keyframes()`
The `viewTransition()` function allows you to get the unique `view-transition-name`.
You can use it with the `keyframes()`, The `::view-transition-old()` is converted to `::view-transition-old(${uniqueName))`.
```tsx
import { useState, startViewTransition } from 'hono/jsx'
import { viewTransition } from 'hono/jsx/dom/css'
import { css, keyframes, Style } from 'hono/css'
const rotate = keyframes`
from {
rotate: 0deg;
}
to {
rotate: 360deg;
}
`
export default function App() {
const [showLargeImage, setShowLargeImage] = useState(false)
const [transitionNameClass] = useState(() =>
viewTransition(css`
::view-transition-old() {
animation-name: ${rotate};
}
::view-transition-new() {
animation-name: ${rotate};
}
`)
)
return (
<>
startViewTransition(() =>
setShowLargeImage((state) => !state)
)
}
>
Click!
{!showLargeImage ? (
) : (
)}
>
)
}
```
### 3. Using `useViewTransition`
If you want to change the style only during the animation. You can use `useViewTransition()`. This hook returns the `[boolean, (callback: () => void) => void]`, and they are the `isUpdating` flag and the `startViewTransition()` function.
When this hook is used, the Component is evaluated at the following two times.
- Inside the callback of a call to `startViewTransition()`.
- When [the `finish` promise becomes fulfilled](https://developer.mozilla.org/en-US/docs/Web/API/ViewTransition/finished)
```tsx
import { useState, useViewTransition } from 'hono/jsx'
import { viewTransition } from 'hono/jsx/dom/css'
import { css, keyframes, Style } from 'hono/css'
const rotate = keyframes`
from {
rotate: 0deg;
}
to {
rotate: 360deg;
}
`
export default function App() {
const [isUpdating, startViewTransition] = useViewTransition()
const [showLargeImage, setShowLargeImage] = useState(false)
const [transitionNameClass] = useState(() =>
viewTransition(css`
::view-transition-old() {
animation-name: ${rotate};
}
::view-transition-new() {
animation-name: ${rotate};
}
`)
)
return (
<>
startViewTransition(() =>
setShowLargeImage((state) => !state)
)
}
>
Click!
{!showLargeImage ? (
) : (
)}
>
)
}
```
## The `hono/jsx/dom` runtime
There is a small JSX Runtime for Client Components. Using this will result in smaller bundled results than using `hono/jsx`. Specify `hono/jsx/dom` in `tsconfig.json`. For Deno, modify the deno.json.
```json
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx/dom"
}
}
```
# JSX
`hono/jsx` ã§ã HTML ã JSX æ§æã§æžãããšãã§ããŸãã
`hono/jsx` ã¯ã¯ã©ã€ã¢ã³ãã§ãåäœããŸããããµãŒããŒåŽã§ã³ã³ãã³ããã¬ã³ããªã³ã°ãããšãæãé »ç¹ã«äœ¿ãããšã«ãªãã§ãããã ããã§ã¯ããµãŒããŒãšã¯ã©ã€ã¢ã³ãã®äž¡æ¹ã«å
±éããã JSX ã«é¢ããããã€ãã®ããšã説æããŸãã
## èšå®
JSX ã䜿ãããã« `tsconfig.json` ãå€æŽããŸã:
`tsconfig.json`:
```json
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx"
}
}
```
ãããã¯ããã©ã°ãã䜿çšããŸã:
```ts
/** @jsx jsx */
/** @jsxImportSource hono/jsx */
```
For Deno, you have to modify the `deno.json` instead of the `tsconfig.json`:
```json
{
"compilerOptions": {
"jsx": "precompile",
"jsxImportSource": "hono/jsx"
}
}
```
## 䜿ãæ¹
`index.tsx`:
```tsx
import { Hono } from 'hono'
import type { FC } from 'hono/jsx'
const app = new Hono()
const Layout: FC = (props) => {
return (
{props.children}
)
}
const Top: FC<{ messages: string[] }> = (props: {
messages: string[]
}) => {
return (
Hello Hono!
{props.messages.map((message) => {
return {message}!!
})}
)
}
app.get('/', (c) => {
const messages = ['Good Morning', 'Good Evening', 'Good Night']
return c.html( )
})
export default app
```
## ãã©ã°ã¡ã³ã
ãã©ã°ã¡ã³ãã䜿çšããŠãè€æ°ã®èŠçŽ ãè¿œå ããŒãç¡ãã§ã°ã«ãŒãåããŸã:
```tsx
import { Fragment } from 'hono/jsx'
const List = () => (
first child
second child
third child
)
```
ãã¡ããšèšå®ãããŠããã°ã `<>>` ã䜿ã£ãŠæžãããšãã§ããŸãã
```tsx
const List = () => (
<>
first child
second child
third child
>
)
```
## `PropsWithChildren`
`PropsWithChildren` ã䜿çšãããšãé¢æ°ã³ã³ããŒãã³ãå
ã®åèŠçŽ ãæ£ããæšè«ã§ããŸãã
```tsx
import { PropsWithChildren } from 'hono/jsx'
type Post = {
id: number
title: string
}
function Component({ title, children }: PropsWithChildren) {
return (
{title}
{children}
)
}
```
## ç HTML ã®æ¿å
¥
çŽæ¥ HTML ãæ¿å
¥ããã«ã¯ã `dangerouslySetInnerHTML` ã䜿çšããŸã:
```tsx
app.get('/foo', (c) => {
const inner = { __html: 'JSX · SSR' }
const Div =
})
```
## ã¡ã¢
`memo` ã䜿çšããŠãèšç®æžã¿ã®æååãä¿åããããšã§ã³ã³ããŒãã³ããæé©åããŸã:
```tsx
import { memo } from 'hono/jsx'
const Header = memo(() => )
const Footer = memo(() => )
const Layout = (
)
```
## Context
By using `useContext`, you can share data globally across any level of the Component tree without passing values through props.
```tsx
import type { FC } from 'hono/jsx'
import { createContext, useContext } from 'hono/jsx'
const themes = {
light: {
color: '#000000',
background: '#eeeeee',
},
dark: {
color: '#ffffff',
background: '#222222',
},
}
const ThemeContext = createContext(themes.light)
const Button: FC = () => {
const theme = useContext(ThemeContext)
return Push!
}
const Toolbar: FC = () => {
return (
)
}
// ...
app.get('/', (c) => {
return c.html(
)
})
```
## Async Component
`hono/jsx` supports an Async Component, so you can use `async`/`await` in your component.
If you render it with `c.html()`, it will await automatically.
```tsx
const AsyncComponent = async () => {
await new Promise((r) => setTimeout(r, 1000)) // sleep 1s
return Done!
}
app.get('/', (c) => {
return c.html(
)
})
```
## Suspense
The React-like `Suspense` feature is available.
If you wrap the async component with `Suspense`, the content in the fallback will be rendered first, and once the Promise is resolved, the awaited content will be displayed.
You can use it with `renderToReadableStream()`.
```tsx
import { renderToReadableStream, Suspense } from 'hono/jsx/streaming'
//...
app.get('/', (c) => {
const stream = renderToReadableStream(
loading...}>
)
return c.body(stream, {
headers: {
'Content-Type': 'text/html; charset=UTF-8',
'Transfer-Encoding': 'chunked',
},
})
})
```
## ErrorBoundary
You can catch errors in child components using `ErrorBoundary`.
In the example below, it will show the content specified in `fallback` if an error occurs.
```tsx
function SyncComponent() {
throw new Error('Error')
return Hello
}
app.get('/sync', async (c) => {
return c.html(
Out of Service}>
)
})
```
`ErrorBoundary` can also be used with async components and `Suspense`.
```tsx
async function AsyncComponent() {
await new Promise((resolve) => setTimeout(resolve, 2000))
throw new Error('Error')
return Hello
}
app.get('/with-suspense', async (c) => {
return c.html(
Out of Service}>
Loading...}>
)
})
```
## Integration with html Middleware
Combine the JSX and html middlewares for powerful templating.
For in-depth details, consult the [html middleware documentation](/docs/helpers/html).
```tsx
import { Hono } from 'hono'
import { html } from 'hono/html'
const app = new Hono()
interface SiteData {
title: string
children?: any
}
const Layout = (props: SiteData) =>
html`
${props.title}
${props.children}
`
const Content = (props: { siteData: SiteData; name: string }) => (
Hello {props.name}
)
app.get('/:name', (c) => {
const { name } = c.req.param()
const props = {
name: name,
siteData: {
title: 'JSX with html sample',
},
}
return c.html( )
})
export default app
```
## With JSX Renderer Middleware
The [JSX Renderer Middleware](/docs/middleware/builtin/jsx-renderer) allows you to create HTML pages more easily with the JSX.
## Override type definitions
You can override the type definition to add your custom elements and attributes.
```ts
declare module 'hono/jsx' {
namespace JSX {
interface IntrinsicElements {
'my-custom-element': HTMLAttributes & {
'x-event'?: 'click' | 'scroll'
}
}
}
}
```
# ããã«ãŠã§ã¢
ããã«ãŠã§ã¢ã¯ãã³ãã©ã®ååŸã§åäœããŸãã ãã£ã¹ãããåã« `Request` ãååŸãããããã£ã¹ãããåŸã« `Response` ãæäœãããã§ããŸãã
## ããã«ãŠã§ã¢ã®å®çŸ©
- ãã³ãã© - `Response` ãªããžã§ã¯ããè¿ãå¿
èŠããããŸãã äžã€ã®ãã«ããŒã®ã¿ãå®è¡ãããŸãã
- ããã«ãŠã§ã¢ - äœãè¿ããªãã¯ãã§ãã `await next()` ã§æ¬¡ã®ããã«ãŠã§ã¢ã«é²ã¿ãŸãã
ããã«ãŠã§ã¢ã®ç»é²ã«ã¯ `app.use` ã `app.HTTP_METHOD` ããã³ãã©ãšåãããã«ç»é²ã§ããŸãã ãã®æ¹æ³ã§ã¯ãã¹ã HTTP ã¡ãœãããç°¡åã«æå®ã§ããŸãã
```ts
// match any method, all routes
app.use(logger())
// specify path
app.use('/posts/*', cors())
// specify method and path
app.post('/posts/*', basicAuth())
```
ãã³ãã©ã `Response` ãè¿ããå Žåããšã³ããŠãŒã¶ã®ããã«äœ¿çšãããŠãåŠçãçµäºããŸãã
```ts
app.post('/posts', (c) => c.text('Created!', 201))
```
ãã®å Žåããã£ã¹ãããåã«4ã€ã®ããã«ãŠã§ã¢ã䜿çšãããŸã:
```ts
logger() -> cors() -> basicAuth() -> *handler*
```
## å®è¡é åº
ããã«ãŠã§ã¢ãå®è¡ãããé åºã¯ãããã«ãŠã§ã¢ãç»é²ãããé åºã«ãã£ãŠæ±ºãŸããŸãã
æåã«ç»é²ãããããã«ãŠã§ã¢ã® `next` ããåã®åŠçãæåã«å®è¡ããã
`next` 以éã®åŠçãæåŸã«å®è¡ãããŸãã
å®äŸãèŠãŠãã ããã
```ts
app.use(async (_, next) => {
console.log('middleware 1 start')
await next()
console.log('middleware 1 end')
})
app.use(async (_, next) => {
console.log('middleware 2 start')
await next()
console.log('middleware 2 end')
})
app.use(async (_, next) => {
console.log('middleware 3 start')
await next()
console.log('middleware 3 end')
})
app.get('/', (c) => {
console.log('handler')
return c.text('Hello!')
})
```
ãã®ãããªçµæã«ãªããŸãã
```
middleware 1 start
middleware 2 start
middleware 3 start
handler
middleware 3 end
middleware 2 end
middleware 1 end
```
## ãã«ãã€ã³ããã«ãŠã§ã¢
Hono ã«ã¯ãã«ãã€ã³ããã«ãŠã§ã¢ããããŸãã
```ts
import { Hono } from 'hono'
import { poweredBy } from 'hono/powered-by'
import { logger } from 'hono/logger'
import { basicAuth } from 'hono/basic-auth'
const app = new Hono()
app.use(poweredBy())
app.use(logger())
app.use(
'/auth/*',
basicAuth({
username: 'hono',
password: 'acoolproject',
})
)
```
::: warning
In Deno, it is possible to use a different version of middleware than the Hono version, but this can lead to bugs.
For example, this code is not working because the version is different.
```ts
import { Hono } from 'jsr:@hono/hono@4.4.0'
import { upgradeWebSocket } from 'jsr:@hono/hono@4.4.5/deno'
const app = new Hono()
app.get(
'/ws',
upgradeWebSocket(() => ({
// ...
}))
)
```
:::
## ã«ã¹ã¿ã ããã«ãŠã§ã¢
ç¬èªã®ããã«ãŠã§ã¢ãäœæã§ããŸãã
```ts
// Custom logger
app.use(async (c, next) => {
console.log(`[${c.req.method}] ${c.req.url}`)
await next()
})
// Add a custom header
app.use('/message/*', async (c, next) => {
await next()
c.header('x-message', 'This is middleware!')
})
app.get('/message/hello', (c) => c.text('Hello Middleware!'))
```
However, embedding middleware directly within `app.use()` can limit its reusability. Therefore, we can separate our
middleware into different files.
To ensure we don't lose type definitions for `context` and `next`, when separating middleware, we can use
[`createMiddleware()`](/docs/helpers/factory#createmiddleware) from Hono's factory.
```ts
import { createMiddleware } from 'hono/factory'
const logger = createMiddleware(async (c, next) => {
console.log(`[${c.req.method}] ${c.req.url}`)
await next()
})
```
:::info
Type generics can be used with `createMiddleware`:
```ts
createMiddleware<{Bindings: Bindings}>(async (c, next) =>
```
:::
### Modify the Response After Next
Additionally, middleware can be designed to modify responses if necessary:
```ts
const stripRes = createMiddleware(async (c, next) => {
await next()
c.res = undefined
c.res = new Response('New Response')
})
```
## Context access inside Middleware arguments
To access the context inside middleware arguments, directly use the context parameter provided by `app.use`. See the example below for clarification.
```ts
import { cors } from 'hono/cors'
app.use('*', async (c, next) => {
const middleware = cors({
origin: c.env.CORS_ORIGIN,
})
return middleware(c, next)
})
```
### Extending the Context in Middleware
To extend the context inside middleware, use `c.set`. You can make this type-safe by passing a `{ Variables: { yourVariable: YourVariableType } }` generic argument to the `createMiddleware` function.
```ts
import { createMiddleware } from 'hono/factory'
const echoMiddleware = createMiddleware<{
Variables: {
echo: (str: string) => string
}
}>(async (c, next) => {
c.set('echo', (str) => str)
await next()
})
app.get('/echo', echoMiddleware, (c) => {
return c.text(c.var.echo('Hello!'))
})
```
## ãµãŒãããŒãã£ãŒããã«ãŠã§ã¢
ãã«ãã€ã³ããã«ãŠã§ã¢ã¯å€éšã¢ãžã¥ãŒã«ã«äŸåããŸãããããããµãŒãããŒãã£ãŒããã«ãŠã§ã¢ã¯ãµãŒãããŒãã£ãŒè£œã©ã€ãã©ãªã«äŸåããŠããå¯èœæ§ããããŸãã
ãããã£ãŠããããã䜿çšããŠããè€éãªã¢ããªã±ãŒã·ã§ã³ãäœæã§ãããšæããŸãã
äŸãã°ã GraphQL ãµãŒããŒããã«ãŠã§ã¢ã Sentry ããã«ãŠã§ã¢ã Firebase Auth ããã«ãŠã§ã¢ç...
# Miscellaneous
## Contributing
Contributions Welcome! You can contribute in the following ways.
- Create an Issue - Propose a new feature. Report a bug.
- Pull Request - Fix a bug and typo. Refactor the code.
- Create third-party middleware - Instruct below.
- Share - Share your thoughts on the Blog, X(Twitter), and others.
- Make your application - Please try to use Hono.
For more details, see [Contribution Guide](https://github.com/honojs/hono/blob/main/docs/CONTRIBUTING.md).
## Sponsoring
You can sponsor Hono authors via the GitHub sponsor program.
- [Sponsor @yusukebe on GitHub Sponsors](https://github.com/sponsors/yusukebe)
- [Sponsor @usualoma on GitHub Sponsors](https://github.com/sponsors/usualoma)
## Other Resources
- GitHub repository: https://github.com/honojs
- npm registry: https://www.npmjs.com/package/hono
- JSR: https://jsr.io/@hono/hono
# RPC
The RPC feature allows sharing of the API specifications between the server and the client.
You can export the types of input type specified by the Validator and the output type emitted by `json()`. And Hono Client will be able to import it.
> [!NOTE]
> For the RPC types to work properly in a monorepo, in both the Client's and Server's tsconfig.json files, set `"strict": true` in `compilerOptions`. [Read more.](https://github.com/honojs/hono/issues/2270#issuecomment-2143745118)
## Server
All you need to do on the server side is to write a validator and create a variable `route`. The following example uses [Zod Validator](https://github.com/honojs/middleware/tree/main/packages/zod-validator).
```ts{1}
const route = app.post(
'/posts',
zValidator(
'form',
z.object({
title: z.string(),
body: z.string(),
})
),
(c) => {
// ...
return c.json(
{
ok: true,
message: 'Created!',
},
201
)
}
)
```
Then, export the type to share the API spec with the Client.
```ts
export type AppType = typeof route
```
## Client
On the Client side, import `hc` and `AppType` first.
```ts
import { AppType } from '.'
import { hc } from 'hono/client'
```
`hc` is a function to create a client. Pass `AppType` as Generics and specify the server URL as an argument.
```ts
const client = hc('http://localhost:8787/')
```
Call `client.{path}.{method}` and pass the data you wish to send to the server as an argument.
```ts
const res = await client.posts.$post({
form: {
title: 'Hello',
body: 'Hono is a cool project',
},
})
```
The `res` is compatible with the "fetch" Response. You can retrieve data from the server with `res.json()`.
```ts
if (res.ok) {
const data = await res.json()
console.log(data.message)
}
```
::: warning File Upload
Currently, the client does not support file uploading.
:::
## Status code
If you explicitly specify the status code, such as `200` or `404`, in `c.json()`. It will be added as a type for passing to the client.
```ts
// server.ts
const app = new Hono().get(
'/posts',
zValidator(
'query',
z.object({
id: z.string(),
})
),
async (c) => {
const { id } = c.req.valid('query')
const post: Post | undefined = await getPost(id)
if (post === undefined) {
return c.json({ error: 'not found' }, 404) // Specify 404
}
return c.json({ post }, 200) // Specify 200
}
)
export type AppType = typeof app
```
You can get the data by the status code.
```ts
// client.ts
const client = hc('http://localhost:8787/')
const res = await client.posts.$get({
query: {
id: '123',
},
})
if (res.status === 404) {
const data: { error: string } = await res.json()
console.log(data.error)
}
if (res.ok) {
const data: { post: Post } = await res.json()
console.log(data.post)
}
// { post: Post } | { error: string }
type ResponseType = InferResponseType
// { post: Post }
type ResponseType200 = InferResponseType<
typeof client.posts.$get,
200
>
```
## Not Found
If you want to use a client, you should not use `c.notFound()` for the Not Found response. The data that the client gets from the server cannot be inferred correctly.
```ts
// server.ts
export const routes = new Hono().get(
'/posts',
zValidator(
'query',
z.object({
id: z.string(),
})
),
async (c) => {
const { id } = c.req.valid('query')
const post: Post | undefined = await getPost(id)
if (post === undefined) {
return c.notFound() // âïž
}
return c.json({ post })
}
)
// client.ts
import { hc } from 'hono/client'
const client = hc('/')
const res = await client.posts[':id'].$get({
param: {
id: '123',
},
})
const data = await res.json() // ð data is unknown
```
Please use `c.json()` and specify the status code for the Not Found Response.
```ts
export const routes = new Hono().get(
'/posts',
zValidator(
'query',
z.object({
id: z.string(),
})
),
async (c) => {
const { id } = c.req.valid('query')
const post: Post | undefined = await getPost(id)
if (post === undefined) {
return c.json({ error: 'not found' }, 404) // Specify 404
}
return c.json({ post }, 200) // Specify 200
}
)
```
## Path parameters
You can also handle routes that include path parameters.
```ts
const route = app.get(
'/posts/:id',
zValidator(
'query',
z.object({
page: z.string().optional(),
})
),
(c) => {
// ...
return c.json({
title: 'Night',
body: 'Time to sleep',
})
}
)
```
Specify the string you want to include in the path with `param`.
```ts
const res = await client.posts[':id'].$get({
param: {
id: '123',
},
query: {},
})
```
## Headers
You can append the headers to the request.
```ts
const res = await client.search.$get(
{
//...
},
{
headers: {
'X-Custom-Header': 'Here is Hono Client',
'X-User-Agent': 'hc',
},
}
)
```
To add a common header to all requests, specify it as an argument to the `hc` function.
```ts
const client = hc('/api', {
headers: {
Authorization: 'Bearer TOKEN',
},
})
```
## `init` option
You can pass the fetch's `RequestInit` object to the request as the `init` option. Below is an example of aborting a Request.
```ts
import { hc } from 'hono/client'
const client = hc('http://localhost:8787/')
const abortController = new AbortController()
const res = await client.api.posts.$post(
{
json: {
// Request body
},
},
{
// RequestInit object
init: {
signal: abortController.signal,
},
}
)
// ...
abortController.abort()
```
::: info
A `RequestInit` object defined by `init` takes the highest priority. It could be used to overwrite things set by other options like `body | method | headers`.
:::
## `$url()`
You can get a `URL` object for accessing the endpoint by using `$url()`.
::: warning
You have to pass in an absolute URL for this to work. Passing in a relative URL `/` will result in the following error.
`Uncaught TypeError: Failed to construct 'URL': Invalid URL`
```ts
// â Will throw error
const client = hc('/')
client.api.post.$url()
// â
Will work as expected
const client = hc('http://localhost:8787/')
client.api.post.$url()
```
:::
```ts
const route = app
.get('/api/posts', (c) => c.json({ posts }))
.get('/api/posts/:id', (c) => c.json({ post }))
const client = hc('http://localhost:8787/')
let url = client.api.posts.$url()
console.log(url.pathname) // `/api/posts`
url = client.api.posts[':id'].$url({
param: {
id: '123',
},
})
console.log(url.pathname) // `/api/posts/123`
```
## Custom `fetch` method
You can set the custom `fetch` method.
In the following example script for Cloudflare Worker, the Service Bindings' `fetch` method is used instead of the default `fetch`.
```toml
# wrangler.toml
services = [
{ binding = "AUTH", service = "auth-service" },
]
```
```ts
// src/client.ts
const client = hc('/', {
fetch: c.env.AUTH.fetch.bind(c.env.AUTH),
})
```
## Infer
Use `InferRequestType` and `InferResponseType` to know the type of object to be requested and the type of object to be returned.
```ts
import type { InferRequestType, InferResponseType } from 'hono/client'
// InferRequestType
const $post = client.todo.$post
type ReqType = InferRequestType['form']
// InferResponseType
type ResType = InferResponseType
```
## Using SWR
You can also use a React Hook library such as [SWR](https://swr.vercel.app).
```tsx
import useSWR from 'swr'
import { hc } from 'hono/client'
import type { InferRequestType } from 'hono/client'
import { AppType } from '../functions/api/[[route]]'
const App = () => {
const client = hc('/api')
const $get = client.hello.$get
const fetcher =
(arg: InferRequestType) => async () => {
const res = await $get(arg)
return await res.json()
}
const { data, error, isLoading } = useSWR(
'api-hello',
fetcher({
query: {
name: 'SWR',
},
})
)
if (error) return failed to load
if (isLoading) return loading...
return {data?.message}
}
export default App
```
## Using RPC with larger applications
In the case of a larger application, such as the example mentioned in [Building a larger application](/docs/guides/best-practices#building-a-larger-application), you need to be careful about the type of inference.
A simple way to do this is to chain the handlers so that the types are always inferred.
```ts
// authors.ts
import { Hono } from 'hono'
const app = new Hono()
.get('/', (c) => c.json('list authors'))
.post('/', (c) => c.json('create an author', 201))
.get('/:id', (c) => c.json(`get ${c.req.param('id')}`))
export default app
```
```ts
// books.ts
import { Hono } from 'hono'
const app = new Hono()
.get('/', (c) => c.json('list books'))
.post('/', (c) => c.json('create a book', 201))
.get('/:id', (c) => c.json(`get ${c.req.param('id')}`))
export default app
```
You can then import the sub-routers as you usually would, and make sure you chain their handlers as well, since this is the top level of the app in this case, this is the type we'll want to export.
```ts
// index.ts
import { Hono } from 'hono'
import authors from './authors'
import books from './books'
const app = new Hono()
const routes = app.route('/authors', authors).route('/books', books)
export default app
export type AppType = typeof routes
```
You can now create a new client using the registered AppType and use it as you would normally.
## Known issues
### IDE performance
When using RPC, the more routes you have, the slower your IDE will become. One of the main reasons for this is that massive amounts of type instantiations are executed to infer the type of your app.
For example, suppose your app has a route like this:
```ts
// app.ts
export const app = new Hono().get('foo/:id', (c) =>
c.json({ ok: true }, 200)
)
```
Hono will infer the type as follows:
```ts
export const app = Hono().get<
'foo/:id',
'foo/:id',
JSONRespondReturn<{ ok: boolean }, 200>,
BlankInput,
BlankEnv
>('foo/:id', (c) => c.json({ ok: true }, 200))
```
This is a type instantiation for a single route. While the user doesn't need to write these type arguments manually, which is a good thing, it's known that type instantiation takes much time. `tsserver` used in your IDE does this time consuming task every time you use the app. If you have a lot of routes, this can slow down your IDE significantly.
However, we have some tips to mitigate this issue.
#### Hono version mismatch
If your backend is separate from the frontend and lives in a different directory, you need to ensure that the Hono versions match. If you use one Hono version on the backend and another on the frontend, you'll run into issues such as "_Type instantiation is excessively deep and possibly infinite_".

#### TypeScript project references
Like in the case of [Hono version mismatch](#hono-version-mismatch), you'll run into issues if your backend and frontend are separate. If you want to access code from the backend (`AppType`, for example) on the frontend, you need to use [project references](https://www.typescriptlang.org/docs/handbook/project-references.html). TypeScript's project references allow one TypeScript codebase to access and use code from another TypeScript codebase. _(source: [Hono RPC And TypeScript Project References](https://catalins.tech/hono-rpc-in-monorepos/))_.
#### Compile your code before using it (recommended)
`tsc` can do heavy tasks like type instantiation at compile time! Then, `tsserver` doesn't need to instantiate all the type arguments every time you use it. It will make your IDE a lot faster!
Compiling your client including the server app gives you the best performance. Put the following code in your project:
```ts
import { app } from './app'
import { hc } from 'hono/client'
// this is a trick to calculate the type when compiling
const client = hc('')
export type Client = typeof client
export const hcWithType = (...args: Parameters): Client =>
hc(...args)
```
After compiling, you can use `hcWithType` instead of `hc` to get the client with the type already calculated.
```ts
const client = hcWithType('http://localhost:8787/')
const res = await client.posts.$post({
form: {
title: 'Hello',
body: 'Hono is a cool project',
},
})
```
If your project is a monorepo, this solution does fit well. Using a tool like [`turborepo`](https://turbo.build/repo/docs), you can easily separate the server project and the client project and get better integration managing dependencies between them. Here is [a working example](https://github.com/m-shaka/hono-rpc-perf-tips-example).
You can also coordinate your build process manually with tools like `concurrently` or `npm-run-all`.
#### Specify type arguments manually
This is a bit cumbersome, but you can specify type arguments manually to avoid type instantiation.
```ts
const app = new Hono().get<'foo/:id'>('foo/:id', (c) =>
c.json({ ok: true }, 200)
)
```
Specifying just single type argument make a difference in performance, while it may take you a lot of time and effort if you have a lot of routes.
#### Split your app and client into multiple files
As described in [Using RPC with larger applications](#using-rpc-with-larger-applications), you can split your app into multiple apps. You can also create a client for each app:
```ts
// authors-cli.ts
import { app as authorsApp } from './authors'
import { hc } from 'hono/client'
const authorsClient = hc('/authors')
// books-cli.ts
import { app as booksApp } from './books'
import { hc } from 'hono/client'
const booksClient = hc('/books')
```
This way, `tsserver` doesn't need to instantiate types for all routes at once.
# Testing
Testing is important.
In actuality, it is easy to test Hono's applications.
The way to create a test environment differs from each runtime, but the basic steps are the same.
In this section, let's test with Cloudflare Workers and Jest.
## Request and Response
All you do is create a Request and pass it to the Hono application to validate the Response.
And, you can use `app.request` the useful method.
::: tip
For a typed test client see the [testing helper](/docs/helpers/testing).
:::
For example, consider an application that provides the following REST API.
```ts
app.get('/posts', (c) => {
return c.text('Many posts')
})
app.post('/posts', (c) => {
return c.json(
{
message: 'Created',
},
201,
{
'X-Custom': 'Thank you',
}
)
})
```
Make a request to `GET /posts` and test the response.
```ts
describe('Example', () => {
test('GET /posts', async () => {
const res = await app.request('/posts')
expect(res.status).toBe(200)
expect(await res.text()).toBe('Many posts')
})
})
```
To make a request to `POST /posts`, do the following.
```ts
test('POST /posts', async () => {
const res = await app.request('/posts', {
method: 'POST',
})
expect(res.status).toBe(201)
expect(res.headers.get('X-Custom')).toBe('Thank you')
expect(await res.json()).toEqual({
message: 'Created',
})
})
```
To make a request to `POST /posts` with `JSON` data, do the following.
```ts
test('POST /posts', async () => {
const res = await app.request('/posts', {
method: 'POST',
body: JSON.stringify({ message: 'hello hono' }),
headers: new Headers({ 'Content-Type': 'application/json' }),
})
expect(res.status).toBe(201)
expect(res.headers.get('X-Custom')).toBe('Thank you')
expect(await res.json()).toEqual({
message: 'Created',
})
})
```
To make a request to `POST /posts` with `multipart/form-data` data, do the following.
```ts
test('POST /posts', async () => {
const formData = new FormData()
formData.append('message', 'hello')
const res = await app.request('/posts', {
method: 'POST',
body: formData,
})
expect(res.status).toBe(201)
expect(res.headers.get('X-Custom')).toBe('Thank you')
expect(await res.json()).toEqual({
message: 'Created',
})
})
```
You can also pass an instance of the Request class.
```ts
test('POST /posts', async () => {
const req = new Request('http://localhost/posts', {
method: 'POST',
})
const res = await app.request(req)
expect(res.status).toBe(201)
expect(res.headers.get('X-Custom')).toBe('Thank you')
expect(await res.json()).toEqual({
message: 'Created',
})
})
```
In this way, you can test it as like an End-to-End.
## Env
To set `c.env` for testing, you can pass it as the 3rd parameter to `app.request`. This is useful for mocking values like [Cloudflare Workers Bindings](https://hono.dev/getting-started/cloudflare-workers#bindings):
```ts
const MOCK_ENV = {
API_HOST: 'example.com',
DB: {
prepare: () => {
/* mocked D1 */
},
},
}
test('GET /posts', async () => {
const res = await app.request('/posts', {}, MOCK_ENV)
})
```
# Validation
Hono provides only a very thin Validator.
But, it can be powerful when combined with a third-party Validator.
In addition, the RPC feature allows you to share API specifications with your clients through types.
## Manual validator
First, introduce a way to validate incoming values without using the third-party Validator.
Import `validator` from `hono/validator`.
```ts
import { validator } from 'hono/validator'
```
To validate form data, specify `form` as the first argument and a callback as the second argument.
In the callback, validates the value and return the validated values at the end.
The `validator` can be used as middleware.
```ts
app.post(
'/posts',
validator('form', (value, c) => {
const body = value['body']
if (!body || typeof body !== 'string') {
return c.text('Invalid!', 400)
}
return {
body: body,
}
}),
//...
```
Within the handler you can get the validated value with `c.req.valid('form')`.
```ts
, (c) => {
const { body } = c.req.valid('form')
// ... do something
return c.json(
{
message: 'Created!',
},
201
)
}
```
Validation targets include `json`, `query`, `header`, `param` and `cookie` in addition to `form`.
::: warning
When you validate `json`, the request _must_ contain a `Content-Type: application/json` header
otherwise the request body will not be parsed and you will receive a warning.
It is important to set the `content-type` header when testing using
[`app.request()`](../api/request.md).
Given an application like this.
```ts
const app = new Hono()
app.post(
'/testing',
validator('json', (value, c) => {
// pass-through validator
return value
}),
(c) => {
const body = c.req.valid('json')
return c.json(body)
}
)
```
Your tests can be written like this.
```ts
// â this will not work
const res = await app.request('/testing', {
method: 'POST',
body: JSON.stringify({ key: 'value' }),
})
const data = await res.json()
console.log(data) // undefined
// â
this will work
const res = await app.request('/testing', {
method: 'POST',
body: JSON.stringify({ key: 'value' }),
headers: new Headers({ 'Content-Type': 'application/json' }),
})
const data = await res.json()
console.log(data) // { key: 'value' }
```
:::
::: warning
When you validate `header`, you need to use **lowercase** name as the key.
If you want to validate the `Idempotency-Key` header, you need to use `idempotency-key` as the key.
```ts
// â this will not work
app.post(
'/api',
validator('header', (value, c) => {
// idempotencyKey is always undefined
// so this middleware always return 400 as not expected
const idempotencyKey = value['Idempotency-Key']
if (idempotencyKey == undefined || idempotencyKey === '') {
throw HTTPException(400, {
message: 'Idempotency-Key is required',
})
}
return { idempotencyKey }
}),
(c) => {
const { idempotencyKey } = c.req.valid('header')
// ...
}
)
// â
this will work
app.post(
'/api',
validator('header', (value, c) => {
// can retrieve the value of the header as expected
const idempotencyKey = value['idempotency-key']
if (idempotencyKey == undefined || idempotencyKey === '') {
throw HTTPException(400, {
message: 'Idempotency-Key is required',
})
}
return { idempotencyKey }
}),
(c) => {
const { idempotencyKey } = c.req.valid('header')
// ...
}
)
```
:::
## Multiple validators
You can also include multiple validators to validate different parts of request:
```ts
app.post(
'/posts/:id',
validator('param', ...),
validator('query', ...),
validator('json', ...),
(c) => {
//...
}
```
## With Zod
You can use [Zod](https://zod.dev), one of third-party validators.
We recommend using a third-party validator.
Install from the Npm registry.
::: code-group
```sh [npm]
npm i zod
```
```sh [yarn]
yarn add zod
```
```sh [pnpm]
pnpm add zod
```
```sh [bun]
bun add zod
```
:::
Import `z` from `zod`.
```ts
import { z } from 'zod'
```
Write your schema.
```ts
const schema = z.object({
body: z.string(),
})
```
You can use the schema in the callback function for validation and return the validated value.
```ts
const route = app.post(
'/posts',
validator('form', (value, c) => {
const parsed = schema.safeParse(value)
if (!parsed.success) {
return c.text('Invalid!', 401)
}
return parsed.data
}),
(c) => {
const { body } = c.req.valid('form')
// ... do something
return c.json(
{
message: 'Created!',
},
201
)
}
)
```
## Zod Validator Middleware
You can use the [Zod Validator Middleware](https://github.com/honojs/middleware/tree/main/packages/zod-validator) to make it even easier.
::: code-group
```sh [npm]
npm i @hono/zod-validator
```
```sh [yarn]
yarn add @hono/zod-validator
```
```sh [pnpm]
pnpm add @hono/zod-validator
```
```sh [bun]
bun add @hono/zod-validator
```
:::
And import `zValidator`.
```ts
import { zValidator } from '@hono/zod-validator'
```
And write as follows.
```ts
const route = app.post(
'/posts',
zValidator(
'form',
z.object({
body: z.string(),
})
),
(c) => {
const validated = c.req.valid('form')
// ... use your validated data
}
)
```
# Alibaba Cloud Function Compute
[Alibaba Cloud Function Compute](https://www.alibabacloud.com/en/product/function-compute) ã¯ãã«ãããŒãžããªãã€ãã³ãã»ããªãã³ãªã³ã³ãã¥ãŒãã£ã³ã°ãµãŒãã¹ã§ãã Function Compute ã䜿çšãããšãµãŒããŒã®ãããªã€ã³ãã©ã管çããããšãªããã³ãŒããæžããŠã¢ããããŒãããããšã«éäžã§ããŸãã
ãã®ã¬ã€ãã§ã¯ãµãŒãããŒãã£ã¢ããã¿ã® [rwv/hono-alibaba-cloud-fc3-adapter](https://github.com/rwv/hono-alibaba-cloud-fc3-adapter) ã䜿ã£ãŠ Alibaba Cloud Function Compute 㧠Hono ãåãããŸãã
## 1. ã»ããã¢ãã
::: code-group
```sh [npm]
mkdir my-app
cd my-app
npm i hono hono-alibaba-cloud-fc3-adapter
npm i -D @serverless-devs/s esbuild
mkdir src
touch src/index.ts
```
```sh [yarn]
mkdir my-app
cd my-app
yarn add hono hono-alibaba-cloud-fc3-adapter
yarn add -D @serverless-devs/s esbuild
mkdir src
touch src/index.ts
```
```sh [pnpm]
mkdir my-app
cd my-app
pnpm add hono hono-alibaba-cloud-fc3-adapter
pnpm add -D @serverless-devs/s esbuild
mkdir src
touch src/index.ts
```
```sh [bun]
mkdir my-app
cd my-app
bun add hono hono-alibaba-cloud-fc3-adapter
bun add -D esbuild @serverless-devs/s
mkdir src
touch src/index.ts
```
:::
## 2. Hello World
`src/index.ts` ãæžããŸãã
```ts
import { Hono } from 'hono'
import { handle } from 'hono-alibaba-cloud-fc3-adapter'
const app = new Hono()
app.get('/', (c) => c.text('Hello Hono!'))
export const handler = handle(app)
```
## 3. serverless-devs ãã»ããã¢ãããã
> [serverless-devs](https://github.com/Serverless-Devs/Serverless-Devs) is an open source and open serverless developer platform dedicated to providing developers with a powerful tool chain system. Through this platform, developers can not only experience multi cloud serverless products with one click and rapidly deploy serverless projects, but also manage projects in the whole life cycle of serverless applications, and combine serverless devs with other tools / platforms very simply and quickly to further improve the efficiency of R & D, operation and maintenance.
Alibaba Cloud AccessKeyID ãš AccessKeySecret ãè¿œå ããŸãã
```sh
npx s config add
# Please select a provider: Alibaba Cloud (alibaba)
# Input your AccessKeyID & AccessKeySecret
```
`s.yaml` ãæžããŸãã
```yaml
edition: 3.0.0
name: my-app
access: 'default'
vars:
region: 'us-west-1'
resources:
my-app:
component: fc3
props:
region: ${vars.region}
functionName: 'my-app'
description: 'Hello World by Hono'
runtime: 'nodejs20'
code: ./dist
handler: index.handler
memorySize: 1024
timeout: 300
```
`package.json` ã® `scripts` ãè¿œå ããŸã:
```json
{
"scripts": {
"build": "esbuild --bundle --outfile=./dist/index.js --platform=node --target=node20 ./src/index.ts",
"deploy": "s deploy -y"
}
}
```
## 4. ãããã€
æåŸã«ãã³ãã³ãã§ãããã€ããŸããã:
```sh
npm run build # Compile the TypeScript code to JavaScript
npm run deploy # Deploy the function to Alibaba Cloud Function Compute
```
# AWS Lambda
AWS Lambda 㯠Amazon Web Services ã®ãµãŒããŒã¬ã¹ãã©ãããã©ãŒã ã§ãã
ã€ãã³ãã«å¿ããŠã³ãŒããå®è¡ããã³ã³ãã¥ãŒããªãœãŒã¹ã¯èªåã§ç®¡çãããŸãã
Hono 㯠AWS Lambda ã® Node.js 18 以äžã®ç°å¢ã§åäœããŸãã
## 1. ã»ããã¢ãã
AWS Lambda ã§ã¢ããªã±ãŒã·ã§ã³ãäœæããå Žåã
[CDK](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-cdk.html)
ã IAM ããŒã«ã API ã²ãŒããŠã§ã€ãªã©ãèšå®ããã®ã«åœ¹ç«ã¡ãŸãã
`cdl` CLI ã䜿ã£ãŠãããžã§ã¯ããåæåããŸãã
::: code-group
```sh [npm]
mkdir my-app
cd my-app
cdk init app -l typescript
npm i hono
mkdir lambda
touch lambda/index.ts
```
```sh [yarn]
mkdir my-app
cd my-app
cdk init app -l typescript
yarn add hono
mkdir lambda
touch lambda/index.ts
```
```sh [pnpm]
mkdir my-app
cd my-app
cdk init app -l typescript
pnpm add hono
mkdir lambda
touch lambda/index.ts
```
```sh [bun]
mkdir my-app
cd my-app
cdk init app -l typescript
bun add hono
mkdir lambda
touch lambda/index.ts
```
:::
## 2. Hello World
`lambda/index.ts` ãç·šéããŸãã
```ts
import { Hono } from 'hono'
import { handle } from 'hono/aws-lambda'
const app = new Hono()
app.get('/', (c) => c.text('Hello Hono!'))
export const handler = handle(app)
```
## 3. ãããã€
`lib/cdk-stack.ts` ãç·šéããŠãã ããã
```ts
import * as cdk from 'aws-cdk-lib'
import { Construct } from 'constructs'
import * as lambda from 'aws-cdk-lib/aws-lambda'
import * as apigw from 'aws-cdk-lib/aws-apigateway'
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs'
export class MyAppStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props)
const fn = new NodejsFunction(this, 'lambda', {
entry: 'lambda/index.ts',
handler: 'handler',
runtime: lambda.Runtime.NODEJS_20_X,
})
fn.addFunctionUrl({
authType: lambda.FunctionUrlAuthType.NONE,
})
new apigw.LambdaRestApi(this, 'myapi', {
handler: fn,
})
}
}
```
æåŸã«ãã®ã³ãã³ãã§ãããã€ããŸã:
```sh
cdk deploy
```
## ãã€ããªããŒã¿ãè¿ã
Hono ã¯ãã€ããªããŒã¿ã®ã¬ã¹ãã³ã¹ããµããŒãããŸãã
Lambda ã§ã¯ãã€ããªããŒã¿ã base64 ã§ãšã³ã³ãŒãããŠè¿ãå¿
èŠããããŸãã
`Content-Type` ãããã«ãã€ããªã¿ã€ããæå®ããããšã Hono ã¯èªå㧠base64 ãšã³ã³ãŒããè¡ããŸãã
```ts
app.get('/binary', async (c) => {
// ...
c.status(200)
c.header('Content-Type', 'image/png') // means binary data
return c.body(buffer) // supports `ArrayBufferLike` type, encoded to base64.
})
```
## AWS Lambda ãªããžã§ã¯ããžã®ã¢ã¯ã»ã¹
Hono ã§ã¯ã `LambdaEvent` ã `LambdaContext` ã¿ã€ãããã€ã³ãã `c.env` 㧠AWS Lambda ã€ãã³ããšã³ã³ããã¹ãã«ã¢ã¯ã»ã¹ã§ããŸãã
```ts
import { Hono } from 'hono'
import type { LambdaEvent, LambdaContext } from 'hono/aws-lambda'
import { handle } from 'hono/aws-lambda'
type Bindings = {
event: LambdaEvent
lambdaContext: LambdaContext
}
const app = new Hono<{ Bindings: Bindings }>()
app.get('/aws-lambda-info/', (c) => {
return c.json({
isBase64Encoded: c.env.event.isBase64Encoded,
awsRequestId: c.env.lambdaContext.awsRequestId,
})
})
export const handler = handle(app)
```
## RequestContext ãžã®ã¢ã¯ã»ã¹
Hono ã¯ã `LambdaEvent` ã¿ã€ãããã€ã³ããã`c.env.event.requestContext` 㧠AWS Lambda ã®ãªã¯ãšã¹ãã³ã³ããã¹ãã«ã¢ã¯ã»ã¹ã§ããŸãã
```ts
import { Hono } from 'hono'
import type { LambdaEvent } from 'hono/aws-lambda'
import { handle } from 'hono/aws-lambda'
type Bindings = {
event: LambdaEvent
}
const app = new Hono<{ Bindings: Bindings }>()
app.get('/custom-context/', (c) => {
const lambdaContext = c.env.event.requestContext
return c.json(lambdaContext)
})
export const handler = handle(app)
```
### v3.10.0 以å (éæšå¥š)
`ApiGatewayRequestContext` ã¿ã€ãããã€ã³ããã `c.env.` ã䜿çšããããšã§ AWS Lambda ãªã¯ãšã¹ãã«ã¢ã¯ã»ã¹ã§ããŸãã
```ts
import { Hono } from 'hono'
import type { ApiGatewayRequestContext } from 'hono/aws-lambda'
import { handle } from 'hono/aws-lambda'
type Bindings = {
requestContext: ApiGatewayRequestContext
}
const app = new Hono<{ Bindings: Bindings }>()
app.get('/custom-context/', (c) => {
const lambdaContext = c.env.requestContext
return c.json(lambdaContext)
})
export const handler = handle(app)
```
## Lambda ã¬ã¹ãã³ã¹ã¹ããªãŒãã³ã°
AWS Lambda ã®åŒã³åºãã¢ãŒããå€æŽããããšã§[ã¹ããªãŒãã³ã°ã¬ã¹ãã³ã¹](https://aws.amazon.com/blogs/compute/introducing-aws-lambda-response-streaming/)ãå®çŸã§ããŸãã
```diff
fn.addFunctionUrl({
authType: lambda.FunctionUrlAuthType.NONE,
+ invokeMode: lambda.InvokeMode.RESPONSE_STREAM,
})
```
éåžž awslambda.streamifyResponse ã䜿çšã㊠NodeJS.WritableStream ã«ãã£ã³ã¯ãæã蟌ãå¿
èŠããããŸããã AWS Lambda ã¢ããã¿ã䜿çšãããšããã³ãã«ã®ä»£ããã« streamHandle ã䜿çšããããšã§ Hono ã®ã¹ããªãŒãã³ã°ã¬ã¹ãã³ã¹ãå®çŸã§ããŸãã
```ts
import { Hono } from 'hono'
import { streamHandle } from 'hono/aws-lambda'
const app = new Hono()
app.get('/stream', async (c) => {
return streamText(c, async (stream) => {
for (let i = 0; i < 3; i++) {
await stream.writeln(`${i}`)
await stream.sleep(1)
}
})
})
const handler = streamHandle(app)
```
# Azure Functions
[Azure Functions](https://azure.microsoft.com/en-us/products/functions) 㯠Microsoft Azure ã®ãµãŒããŒã¬ã¹ãã©ãããã©ãŒã ã§ãã ã€ãã³ãã«å¿ããŠã³ãŒããå®è¡ã§ããèªåã§ã³ã³ãã¥ãŒãã£ã³ã°ãªãœãŒã¹ã管çããŸãã
Hono 㯠Azure Functions ã®ããã«äœãããããã§ã¯ãããŸãããã [Azure Functions Adapter](https://github.com/Marplex/hono-azurefunc-adapter) ã䜿ãããšã§ããŸãåããããšãã§ããŸãã
Node.js 18 以äžã® Azure Functions **V4** ã§åäœããŸãã
## 1. CLI ãã€ã³ã¹ããŒã«ãã
Azure Function ãäœãããã«ã [Azure Functions Core Tools](https://learn.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-typescript?pivots=nodejs-model-v4#install-the-azure-functions-core-tools) ãã€ã³ã¹ããŒã«ããå¿
èŠããããŸãã
macOS ã§ã¯:
```sh
brew tap azure/functions
brew install azure-functions-core-tools@4
```
ä»ã® OS ã§ã¯:
- [Install the Azure Functions Core Tools | Microsoft Learn](https://learn.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-typescript?pivots=nodejs-model-v4#install-the-azure-functions-core-tools)
## 2. ã»ããã¢ãã
TypeScript Node.js V4 ãããžã§ã¯ããã«ã¬ã³ããã£ã¬ã¯ããªã«äœããŸãã
```sh
func init --typescript
```
ãã¹ãã®ããã©ã«ããã¬ãã£ãã¯ã¹ãå€æŽããã«ã¯ã `host.json` ã®ã«ãŒãã«ä»¥äžã®ããããã£ãè¿œå ããŸã:
```json
"extensions": {
"http": {
"routePrefix": ""
}
}
```
::: info
Azure Functions ã§ããã©ã«ãã®ã«ãŒããã¬ãã£ãã¯ã¹ã¯ `/api` ã§ãã äžã®ããã«å€æŽããªãå Žåã¯ã Hono ã®å
šãŠã®ã«ãŒãã `/api` ããåããŠãã ããã
:::
Hono ãš Azure Functions Adapter ãã€ã³ã¹ããŒã«ããæºåãã§ããŸãã:
::: code-group
```sh [npm]
npm i @marplex/hono-azurefunc-adapter hono
```
```sh [yarn]
yarn add @marplex/hono-azurefunc-adapter hono
```
```sh [pnpm]
pnpm add @marplex/hono-azurefunc-adapter hono
```
```sh [bun]
bun add @marplex/hono-azurefunc-adapter hono
```
:::
## 3. Hello World
`src/app.ts` ãäœããŸã:
```ts
// src/app.ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Azure Functions!'))
export default app
```
`src/functions/httpTrigger.ts` ãäœããŸã:
```ts
// src/functions/httpTrigger.ts
import { app } from '@azure/functions'
import { azureHonoHandler } from '@marplex/hono-azurefunc-adapter'
import honoApp from '../app'
app.http('httpTrigger', {
methods: [
//Add all your supported HTTP methods here
'GET',
'POST',
'DELETE',
'PUT',
],
authLevel: 'anonymous',
route: '{*proxy}',
handler: azureHonoHandler(honoApp.fetch),
})
```
## 4. å®è¡
éçºãµãŒããŒãããŒã«ã«ã§å®è¡ãã `http://localhost:7071` ã Web ãã©ãŠã¶ã§éããŸãã
::: code-group
```sh [npm]
npm run start
```
```sh [yarn]
yarn start
```
```sh [pnpm]
pnpm start
```
```sh [bun]
bun run start
```
:::
## 5. ãããã€
::: info
Azure ã«ãããã€ããåã«ãã¯ã©ãŠãã€ã³ãã©ã¹ãã©ã¯ãã£ãªãœãŒã¹ãäœãå¿
èŠããããŸãã Microsoft ã®ããã¥ã¡ã³ããèªãã§ãã ããã [Create supporting Azure resources for your function](https://learn.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-typescript?pivots=nodejs-model-v4&tabs=windows%2Cazure-cli%2Cbrowser#create-supporting-azure-resources-for-your-function)
:::
ãããžã§ã¯ãããã«ãããŠãããã€ããŸã:
::: code-group
```sh [npm]
npm run build
```
```sh [yarn]
yarn build
```
```sh [pnpm]
pnpm build
```
```sh [bun]
bun run build
```
:::
ãããžã§ã¯ããèªåã® Azure Cloud ã®ãã¡ã³ã¯ã·ã§ã³ã¢ããªã±ãŒã·ã§ã³ã«ãããã€ããããã«ã `` ãèªåã®ã¢ããªã®ååã«å€ããŸãã
```sh
func azure functionapp publish
```
# å§ããŸããã!
Hono ã䜿ãã®ã¯ãšãŠãç°¡åã§ãã ãããžã§ã¯ããäœæããã³ãŒããæžããéçºãµãŒããŒãç«ã¡äžããçŽ æ©ããããã€åºæ¥ãŸãã ãšã³ããªãã€ã³ããéãã ãã®åãã³ãŒããå
šãŠã®ã©ã³ã¿ã€ã ã§åäœããŸãã ã§ã¯åºæ¬ç㪠Hono ã®äœ¿ãæ¹ãèŠãŠãããŸããã!
## ã¹ã¿ãŒã¿ãŒ
ããããã®ãã©ãããã©ãŒã ã§ã¹ã¿ãŒã¿ãŒãã³ãã¬ãŒããçšæãããŠããã "create-hono" ã³ãã³ãã§äœ¿çšã§ããŸãã
::: code-group
```sh [npm]
npm create hono@latest my-app
```
```sh [yarn]
yarn create hono my-app
```
```sh [pnpm]
pnpm create hono@latest my-app
```
```sh [bun]
bun create hono@latest my-app
```
```sh [deno]
deno init --npm hono@latest my-app
```
:::
次ã«ã©ã®ãã³ãã¬ãŒãã䜿çšããã質åããããŸãã
ããã§ã¯ Cloudflare Workers åãã®ãµã³ãã«ãéžã³ãŸããã
```
? Which template do you want to use?
aws-lambda
bun
cloudflare-pages
⯠cloudflare-workers
deno
fastly
nextjs
nodejs
vercel
```
ãã³ãã¬ãŒãã `my-app` ã«å±éãããã®ã§äŸåé¢ä¿ãã€ã³ã¹ããŒã«ããŸãã
::: code-group
```sh [npm]
cd my-app
npm i
```
```sh [yarn]
cd my-app
yarn
```
```sh [pnpm]
cd my-app
pnpm i
```
```sh [bun]
cd my-app
bun i
```
:::
ããã±ãŒãžã®ã€ã³ã¹ããŒã«ãå®äºããããéçºãµãŒããŒãèµ·åããŠã¿ãŸãããã
::: code-group
```sh [npm]
npm run dev
```
```sh [yarn]
yarn dev
```
```sh [pnpm]
pnpm dev
```
```sh [bun]
bun run dev
```
:::
## Hello World
Cloudflare Workers éçºããŒã«ã® "Wrangler" ã Deno ã Bun ãªã©ã䜿çšããã©ã³ã¹ãã€ã«ãæèããããšãªãã³ãŒããæžããŸãã
`src/index.ts` ã« Hono ã䜿çšããæåã®ã¢ããªã±ãŒã·ã§ã³ãäœã£ãŠãããŸãã 以äžã®äŸã¯ Hono ã¹ã¿ãŒã¿ãŒã¢ããªã±ãŒã·ã§ã³ã§ãã
`import` ãš æåŸã® `export default` ã¯ã©ã³ã¿ã€ã ã«ãã£ãŠéãããšããããŸãã
ããããå
šãŠã®ã¢ããªã±ãŒã·ã§ã³ã®ã³ãŒãã¯ã©ãã§ãåãã§ãã
```ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => {
return c.text('Hello Hono!')
})
export default app
```
éçºãµãŒããŒãèµ·åãããã©ãŠã¶ã§ `http://localhost:8787` ã«ã¢ã¯ã»ã¹ããŸãã
::: code-group
```sh [npm]
npm run dev
```
```sh [yarn]
yarn dev
```
```sh [pnpm]
pnpm dev
```
```sh [bun]
bun run dev
```
:::
## JSON ãè¿ã
JSON ãè¿ãã®ã¯éåžžã«ç°¡åã§ãã 以äžã¯ `/api/hello` ã® GET ãªã¯ãšã¹ããåŠçã㊠`application/json` ã® Response ãè¿ãäŸã§ãã
```ts
app.get('/api/hello', (c) => {
return c.json({
ok: true,
message: 'Hello Hono!',
})
})
```
## Request / Response
ãã¹ãã©ã¡ãŒã¿ã URL ã¯ãšãªãååŸããã¬ã¹ãã³ã¹ããããè¿œå ããäŸã§ãã
```ts
app.get('/posts/:id', (c) => {
const page = c.req.query('page')
const id = c.req.param('id')
c.header('X-Message', 'Hi!')
return c.text(`You want to see ${page} of ${id}`)
})
```
GET ã®ã¿ãªãã POST ã PUT ã DELETE ãç°¡åã«åŠçã§ããŸãã
```ts
app.post('/posts', (c) => c.text('Created!', 201))
app.delete('/posts/:id', (c) =>
c.text(`${c.req.param('id')} is deleted!`)
)
```
## HTML ãè¿ã
[html ãã«ããŒ](/docs/helpers/html) ã [JSX](/docs/guides/jsx) ã䜿ã£ãŠ HTML ãæžãããšãã§ããŸãã JSX ã䜿ãããå Žåã ãã¡ã€ã«åã `src/index.tsx` ã«å€ããŠãèšå® (ã©ã³ã¿ã€ã ã«ãã£ãŠéã) ãè¡ããŸãã 以äžã« JSX ã䜿ãäŸã瀺ããŸãã
```tsx
const View = () => {
return (
Hello Hono!
)
}
app.get('/page', (c) => {
return c.html( )
})
```
## çã® Response ãè¿ã
çã® [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) ãè¿ãããšãåºæ¥ãŸãã
```ts
app.get('/', () => {
return new Response('Good morning!')
})
```
## ããã«ãŠã§ã¢ã䜿ã
ããã«ãŠã§ã¢ãé¢åãªäœæ¥ãè©ä»£ãããŠãããŸãã
äŸãã°ã Basic èªèšŒãè¿œå ããäŸããã¡ãã§ãã
```ts
import { basicAuth } from 'hono/basic-auth'
// ...
app.use(
'/admin/*',
basicAuth({
username: 'admin',
password: 'secret',
})
)
app.get('/admin', (c) => {
return c.text('You are authorized!')
})
```
Bearer ã JWT èªèšŒã CORS ã ETag ãªã©ã®äŸ¿å©ãªããã«ãŠã§ã¢ãå«ãŸããŠããŸãã
ãŸãã GraphQL ãµãŒããŒã Firebase Auth ãªã©ã®å€éšã©ã€ãã©ãªã䜿çšãããµãŒãããŒãã£ã®ããã«ãŠã§ã¢ãæäŸããŸãã
ãããŠãããã«ãŠã§ã¢ãäœãããšãåºæ¥ãŸãã
## ã¢ããã¿
éçãã¡ã€ã«ã WebSocket ã®åŠçãªã©ããã©ãããã©ãŒã ã«ãã£ãŠç°ãªãæ©èœãå®è£
ããããã®ã¢ããã¿ããããŸãã
äŸãã°ã WebSocket ã Cloudflare Workers ã§æ±ãããã«ã¯ `hono/cloudflare-workers` ãã€ã³ããŒãããŸã
```ts
import { upgradeWebSocket } from 'hono/cloudflare-workers'
app.get(
'/ws',
upgradeWebSocket((c) => {
// ...
})
)
```
## 次ã®ã¹ããã
ã»ãšãã©ã®ã³ãŒãã¯ã©ã®ãã©ãããã©ãŒã ã§ãåããŸãããããããã®ã¬ã€ãããããŸãã
äŸãã°ããããžã§ã¯ãã®ã»ããã¢ããæ¹æ³ããããã€æ¹æ³ã§ãã
ã¢ããªã±ãŒã·ã§ã³ã®äœæã«äœ¿çšããããã©ãããã©ãŒã ãåç
§ããŠãã ããã
# Bun
[Bun](https://bun.sh) ã¯ããäžã€ã® JavaScript ã©ã³ã¿ã€ã ã§ãã Node.js ã§ã Deno ã§ããããŸããã Bun ã¯ãã©ã³ã¹ã³ã³ãã€ã©ãå
èµãããŠããã TypeScript ã§ã³ãŒããæžãããšãåºæ¥ãŸãã
Hono ã¯ãã¡ãã Bun ã§ãåäœããŸãã
## 1. Bun ã®ã€ã³ã¹ããŒã«
`bun` ã³ãã³ããã€ã³ã¹ããŒã«ããããã«[å
¬åŒãµã€ã](https://bun.sh)ã確èªããŠãã ããã
## 2. ã»ããã¢ãã
### 2.1. æ°ãããããžã§ã¯ããã»ããã¢ãããã
ã¹ã¿ãŒã¿ãŒã¯ Bun ã§ã䜿çšã§ããŸãã "bun create" ã³ãã³ãã§ãããžã§ã¯ããäœæããŠãã ããã
éžæè¢ã¯ `bun` ãéžãã§ãã ããã
```sh
bun create hono@latest my-app
```
`my-app` ã«ç§»åããäŸåé¢ä¿ãã€ã³ã¹ããŒã«ããŸãã
```sh
cd my-app
bun install
```
### 2.2. ãã§ã«ãããããžã§ã¯ãã«ã»ããã¢ãããã
ãã§ã«ãã Bun ãããžã§ã¯ãã§ã¯ããããžã§ã¯ãã«ãŒãã®ãã£ã¬ã¯ããªã§ `hono` ãäŸåé¢ä¿ãšããŠã€ã³ã¹ããŒã«ããã ãã§ãã
```sh
bun add hono
```
## 3. Hello World
"Hello World" ã¹ã¯ãªããã¯ä»¥äžã®éãã§ãã ä»ã®ãã©ãããã©ãŒã ãšè¯ã䌌ãŠããŸããã
```ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Bun!'))
export default app
```
## 4. Run
以äžã®ã³ãã³ããå®è¡ããŸãã
```sh
bun run dev
```
次ã«ããã©ãŠã¶ã§ `http://localhost:3000` ãžã¢ã¯ã»ã¹ããŸãã
## ããŒããå€ãã
ãšã¯ã¹ããŒãæã« `port` ãæå®ã§ããŸãã
```ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Bun!'))
export default app // [!code --]
export default { // [!code ++]
port: 3000, // [!code ++]
fetch: app.fetch, // [!code ++]
} // [!code ++]
```
## éçãã¡ã€ã«ã®æäŸ
éçãã¡ã€ã«ãæäŸããããã« `hono/bun` ãã `serveStatic` ãã€ã³ããŒãããŠäœ¿çšããŠãã ããã
```ts
import { serveStatic } from 'hono/bun'
const app = new Hono()
app.use('/static/*', serveStatic({ root: './' }))
app.use('/favicon.ico', serveStatic({ path: './favicon.ico' }))
app.get('/', (c) => c.text('You can access: /static/hello.txt'))
app.get('*', serveStatic({ path: './static/fallback.txt' }))
```
äžã®ã³ãŒãã¯ãã®ãããªãã£ã¬ã¯ããªæ§æã§æ©èœããŸãã
```
./
âââ favicon.ico
âââ src
âââ static
âââ demo
â âââ index.html
âââ fallback.txt
âââ hello.txt
âââ images
âââ dinotocat.png
```
### `rewriteRequestPath`
`http://localhost:3000/static/*` ã `./statics` ã«ããããããå Žåã `rewriteRequestPath` ãªãã·ã§ã³ã䜿çšã§ããŸã:
```ts
app.get(
'/static/*',
serveStatic({
root: './',
rewriteRequestPath: (path) =>
path.replace(/^\/static/, '/statics'),
})
)
```
### `mimes`
MIME ã¿ã€ãã `mimes` ã§è¿œå ã§ããŸã:
```ts
app.get(
'/static/*',
serveStatic({
mimes: {
m3u8: 'application/vnd.apple.mpegurl',
ts: 'video/mp2t',
},
})
)
```
### `onFound`
ãªã¯ãšã¹ãããããã¡ã€ã«ãèŠã€ãã£ãå Žåã®åŠçã `onFound` ã§å®è£
ã§ããŸã:
```ts
app.get(
'/static/*',
serveStatic({
// ...
onFound: (_path, c) => {
c.header('Cache-Control', `public, immutable, max-age=31536000`)
},
})
)
```
### `onNotFound`
ãªã¯ãšã¹ãããããã¡ã€ã«ãèŠã€ãããªãå Žåã®åŠçã `onNotFound` ã§èšè¿°ã§ããŸã:
```ts
app.get(
'/static/*',
serveStatic({
onNotFound: (path, c) => {
console.log(`${path} is not found, you access ${c.req.path}`)
},
})
)
```
### `precompressed`
`precompressed` ãªãã·ã§ã³ã§ã¯ `.br` ã `.gz` ã®ãããªæ¡åŒµåãæã£ãŠããã¡ã€ã«ãããã確èªããŠã `Accept-Encoding` ãããã«åºã¥ããŠããããè¿ããŸãã Brotli ã Zstd ã Gzip ã®é ã§åªå
ãããŸãã ãããããªããã°å
ã®ãã¡ã€ã«ãè¿ããŸãã
```ts
app.get(
'/static/*',
serveStatic({
precompressed: true,
})
)
```
## ãã¹ã
`bun:test` ã䜿çšãã Bun ã§ãã¹ãã§ããŸãã
```ts
import { describe, expect, it } from 'bun:test'
import app from '.'
describe('My first test', () => {
it('Should return 200 Response', async () => {
const req = new Request('http://localhost/')
const res = await app.fetch(req)
expect(res.status).toBe(200)
})
})
```
次ã«ããã®ã³ãã³ããå®è¡ããŸãã
```sh
bun test index.test.ts
```
# Cloudflare Pages
[Cloudflare Pages](https://pages.cloudflare.com) ã¯ãã«ã¹ã¿ãã¯ã¢ããªã±ãŒã·ã§ã³ã®ããã®ãšããžãã©ãããã©ãŒã ã§ãã
éçãªãã¡ã€ã«ãš Cloudflare Workes ãçšããåçãªã³ã³ãã³ããæäŸããŸãã
Hono ã¯å®ç§ã« Cloudflare Pages ããµããŒãããŸãã
楜ããéçºäœéšãã§ããŸãã Vite ã®éçºãµãŒããŒã¯é«éã§ã Wrangler ã䜿ã£ããããã€ã¯çéã§ãã
## 1. Setup
Cloudflare Pages åãã®ã¹ã¿ãŒã¿ãŒãæºåãããŠããŸãã
"create-hono" ã³ãã³ãã§ãããžã§ã¯ããéå§ã§ããŸãã
ãã®äŸã§ã¯ã `cloudflare-pages` ãã³ãã¬ãŒããéžæããŸãã
::: code-group
```sh [npm]
npm create hono@latest my-app
```
```sh [yarn]
yarn create hono my-app
```
```sh [pnpm]
pnpm create hono my-app
```
```sh [bun]
bun create hono@latest my-app
```
```sh [deno]
deno init --npm hono my-app
```
:::
`my-app` ã«ç§»åããŠäŸåé¢ä¿ãã€ã³ã¹ããŒã«ããŸãã
::: code-group
```sh [npm]
cd my-app
npm i
```
```sh [yarn]
cd my-app
yarn
```
```sh [pnpm]
cd my-app
pnpm i
```
```sh [bun]
cd my-app
bun i
```
:::
ãããåºæ¬çãªãã£ã¬ã¯ããªæ§æã§ãã
```text
./
âââ package.json
âââ public
â  âââ static // Put your static files.
â  âââ style.css // You can refer to it as `/static/style.css`.
âââ src
â  âââ index.tsx // The entry point for server-side.
â  âââ renderer.tsx
âââ tsconfig.json
âââ vite.config.ts
```
## 2. Hello World
`src/index.tsx` ããã®ããã«å€æŽããŸã:
```tsx
import { Hono } from 'hono'
import { renderer } from './renderer'
const app = new Hono()
app.get('*', renderer)
app.get('/', (c) => {
return c.render(Hello, Cloudflare Pages! )
})
export default app
```
## 3. å®è¡
éçºãµãŒããŒãããŒã«ã«ã§å®è¡ããŠã Web ãã©ãŠã¶ã§ `http://localhost:5173` ã«ã¢ã¯ã»ã¹ããŸãã
::: code-group
```sh [npm]
npm run dev
```
```sh [yarn]
yarn dev
```
```sh [pnpm]
pnpm dev
```
```sh [bun]
bun run dev
```
:::
## 4. ãããã€
Cloudflare ã¢ã«ãŠã³ããæã£ãŠããå Žå㯠Cloudflare ã«ãããã€ã§ããŸãã `package.json` ã® `$npm_execpath` ã¯éžãã ããã±ãŒãžãããŒãžã£ã«å€æŽããå¿
èŠããããŸãã
::: code-group
```sh [npm]
npm run deploy
```
```sh [yarn]
yarn deploy
```
```sh [pnpm]
pnpm run deploy
```
```sh [bun]
bun run deploy
```
:::
### Cloudflare ããã·ã¥ããŒããã GitHub é£æºã§ãããã€ãã
1. [Cloudflare dashboard](https://dash.cloudflare.com) ã«ãã°ã€ã³ããŠã¢ã«ãŠã³ããéžæããŸãã
2. Account Home 㧠Workers & Pages > Create application > Pages > Connect to Git ãéžæããŸãã
3. GitHub ã¢ã«ãŠã³ããèªå¯ããŠããªããžããªãéžæããŸãã ãã«ããšãããã€ã®èšå®ã¯æ¬¡ã®ããã«ãªããŸã:
| Configuration option | Value |
| -------------------- | --------------- |
| Production branch | `main` |
| Build command | `npm run build` |
| Build directory | `dist` |
## ãã€ã³ãã£ã³ã°
Variables ã KV ã D1 ãªã©ã® Cloudflare ãã€ã³ãã£ã³ã°ã䜿ãããšãã§ããŸãã
ãã®ã»ã¯ã·ã§ã³ã§ã¯ Variables ãš KV ã®ã»ããã¢ããã解説ããŸãã
### `wrangler.toml` ãäœã
ãŸãã¯ãããŒã«ã«ãã€ã³ãã£ã³ã°çšã« `wrangler.toml` ãäœæããŸã:
```sh
touch wrangler.toml
```
`wrangler.toml` ãç·šéããŠã `MY_NAME` ã® Variable ãèšå®ããŸãã
```toml
[vars]
MY_NAME = "Hono"
```
### KV ãäœã
次ã«ã KV ãäœããŸãã 以äžã® `wrangler` ã³ãã³ããå®è¡ããŸã:
```sh
wrangler kv namespace create MY_KV --preview
```
ãã®ãããªåºåãããã®ã§ `preview_id` ãã¡ã¢ããŠãã ãã:
```
{ binding = "MY_KV", preview_id = "abcdef" }
```
`MY_KV` ã®ãã€ã³ãã£ã³ã°ã§ `preview_id` ãèšå®ããŸã:
```toml
[[kv_namespaces]]
binding = "MY_KV"
id = "abcdef"
```
### `vite.config.ts` ãå€æŽãã
`vite.config.ts` ãå€æŽããŸã:
```ts
import devServer from '@hono/vite-dev-server'
import adapter from '@hono/vite-dev-server/cloudflare'
import build from '@hono/vite-cloudflare-pages'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
devServer({
entry: 'src/index.tsx',
adapter, // Cloudflare Adapter
}),
build(),
],
})
```
### ã¢ããªã±ãŒã·ã§ã³ã§ãã€ã³ãã£ã³ã°ã䜿çšãã
Variable ãš KV ãã¢ããªã±ãŒã·ã§ã³ã§äœ¿çšããŸãã ãŸãã¯ãåãèšå®ããŸãã
```ts
type Bindings = {
MY_NAME: string
MY_KV: KVNamespace
}
const app = new Hono<{ Bindings: Bindings }>()
```
䜿ããŸã:
```tsx
app.get('/', async (c) => {
await c.env.MY_KV.put('name', c.env.MY_NAME)
const name = await c.env.MY_KV.get('name')
return c.render(Hello! {name} )
})
```
### æ¬çªç°å¢ã§ã¯
Cloudflare Pages ã§ã¯ãããŒã«ã«éçºçšã«ã¯ `wrangler.toml` ã䜿çšããŸãããæ¬çªç°å¢ã§ã¯ããã·ã¥ããŒãã§ãã€ã³ãã£ã³ã°ãèšå®ããŸãã
## ã¯ã©ã€ã¢ã³ããµã€ã
Vite ã®æ©èœã䜿ã£ãŠã¯ã©ã€ã¢ã³ããµã€ãã®ã¹ã¯ãªãããæžããŠã¢ããªã±ãŒã·ã§ã³ã«çµã¿èŸŒãããšãã§ããŸãã
`/src/client.ts` ãã¯ã©ã€ã¢ã³ãã®ãšã³ããªãã€ã³ãã®ãšãã script ã¿ã°ã«æžãã ãã§ãã
æŽã«ã `import.meta.env.PROD` ã¯åäœç°å¢ãéçºãµãŒããŒããã«ãäžããæ€åºããããã«æçšã§ãã
```tsx
app.get('/', (c) => {
return c.html(
{import.meta.env.PROD ? (
) : (
)}
Hello
)
})
```
ã¹ã¯ãªãããããŸããã«ãããããã«ãäžã«ç€ºããããªèšå®ã®äŸã `vite.config.ts` ã«äœ¿ãããšãã§ããŸãã
```ts
import pages from '@hono/vite-cloudflare-pages'
import devServer from '@hono/vite-dev-server'
import { defineConfig } from 'vite'
export default defineConfig(({ mode }) => {
if (mode === 'client') {
return {
build: {
rollupOptions: {
input: './src/client.ts',
output: {
entryFileNames: 'static/client.js',
},
},
},
}
} else {
return {
plugins: [
pages(),
devServer({
entry: 'src/index.tsx',
}),
],
}
}
})
```
次ã®ã³ãã³ããå®è¡ããŠããµãŒããŒãšã¯ã©ã€ã¢ã³ãã¹ã¯ãªããããã«ãããŸãã
```sh
vite build --mode client && vite build
```
## Cloudflare Pages ã®ããã«ãŠã§ã¢
Cloudflare Pages 㯠Hono ãšã¯éã[ããã«ãŠã§ã¢](https://developers.cloudflare.com/pages/functions/middleware/)ã·ã¹ãã ãæã£ãŠããŸãã `_middleware.ts` 㧠`onRequest` ã export ããããšã§æå¹åã§ããŸã:
```ts
// functions/_middleware.ts
export async function onRequest(pagesContext) {
console.log(`You are accessing ${pagesContext.request.url}`)
return await pagesContext.next()
}
```
`handleMiddleware` ã䜿ãããšã§ã Hono ã®ããã«ãŠã§ã¢ã Cloudflare Pages ã®ããã«ãŠã§ã¢ãšããŠäœ¿ãããšãã§ããŸãã
```ts
// functions/_middleware.ts
import { handleMiddleware } from 'hono/cloudflare-pages'
export const onRequest = handleMiddleware(async (c, next) => {
console.log(`You are accessing ${c.req.url}`)
await next()
})
```
Hono ã®ãã«ãã€ã³ããµãŒãããŒãã£ããã«ãŠã§ã¢ã䜿ãããšãã§ããŸãã äŸãã°ã Basic èªèšŒãè¿œå ããããã« [Hono ã® Basic èªèšŒããã«ãŠã§ã¢](/docs/middleware/builtin/basic-auth) ã䜿ãããšãã§ããŸãã
```ts
// functions/_middleware.ts
import { handleMiddleware } from 'hono/cloudflare-pages'
import { basicAuth } from 'hono/basic-auth'
export const onRequest = handleMiddleware(
basicAuth({
username: 'hono',
password: 'acoolproject',
})
)
```
ãã®ããã«è€æ°ã®ããã«ãŠã§ã¢ã䜿ãããšãã§ããŸã:
```ts
import { handleMiddleware } from 'hono/cloudflare-pages'
// ...
export const onRequest = [
handleMiddleware(middleware1),
handleMiddleware(middleware2),
handleMiddleware(middleware3),
]
```
### `EventContext` ãžã®ã¢ã¯ã»ã¹
[`EventContext`](https://developers.cloudflare.com/pages/functions/api-reference/#eventcontext) ãªããžã§ã¯ãã«ã¯ `handleMiddleware` ã® `c.env` ããã¢ã¯ã»ã¹ã§ããŸãã
```ts
// functions/_middleware.ts
import { handleMiddleware } from 'hono/cloudflare-pages'
export const onRequest = [
handleMiddleware(async (c, next) => {
c.env.eventContext.data.user = 'Joe'
await next()
}),
]
```
次ã«ããã³ãã©ã§ã¯ `c.env.eventContext` ããã»ããããããŒã¿ã«ã¢ã¯ã»ã¹ã§ããŸã:
```ts
// functions/api/[[route]].ts
import type { EventContext } from 'hono/cloudflare-pages'
import { handle } from 'hono/cloudflare-pages'
// ...
type Env = {
Bindings: {
eventContext: EventContext
}
}
const app = new Hono()
app.get('/hello', (c) => {
return c.json({
message: `Hello, ${c.env.eventContext.data.user}!`, // 'Joe'
})
})
export const onRequest = handle(app)
```
# Cloudflare Workers
[Cloudflare Workers](https://workers.cloudflare.com) 㯠Cloudflare ã® CDN 㧠JavaScript ãå®è¡ãããšããžã©ã³ã¿ã€ã ã§ãã
ã¢ããªã±ãŒã·ã§ã³ãããŒã«ã«ã§éçºãã [Wrangler](https://developers.cloudflare.com/workers/wrangler/) ã®ããã€ãã®ã³ãã³ãã䜿çšããŠå
¬éããŸãã
Wrangler ã¯ã³ã³ãã€ã©ãå
èµããŠããããã TypeScript ã§ã³ãŒããæžããŸãã
Hono ã䜿ã£ã Cloudflare Workers æåã®ã¢ããªã±ãŒã·ã§ã³ãäœããŸãããã
## 1. ã»ããã¢ãã
Cloudflare Workers åãã®ã¹ã¿ãŒã¿ãŒã䜿çšã§ããŸãã
"create-hono" ã³ãã³ãã§ãããžã§ã¯ããäœæããŠãã ããã
`cloudflare-workers` ãã³ãã¬ãŒããéžæããŸãã
::: code-group
```sh [npm]
npm create hono@latest my-app
```
```sh [yarn]
yarn create hono my-app
```
```sh [pnpm]
pnpm create hono my-app
```
```sh [bun]
bun create hono@latest my-app
```
```sh [deno]
deno init --npm hono my-app
```
:::
`my-app` ã«ç§»åããäŸåé¢ä¿ãã€ã³ã¹ããŒã«ããŸãã
::: code-group
```sh [npm]
cd my-app
npm i
```
```sh [yarn]
cd my-app
yarn
```
```sh [pnpm]
cd my-app
pnpm i
```
```sh [bun]
cd my-app
bun i
```
:::
## 2. Hello World
`src/index.ts` ããã®ããã«å€æŽããŸãã
```ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Cloudflare Workers!'))
export default app
```
## 3. Run
ããŒã«ã«éçºãµãŒããŒãèµ·åãããã©ãŠã¶ã§ `http://localhost:8787` ã«ã¢ã¯ã»ã¹ããŸãã
::: code-group
```sh [npm]
npm run dev
```
```sh [yarn]
yarn dev
```
```sh [pnpm]
pnpm dev
```
```sh [bun]
bun run dev
```
:::
### ããŒãçªå·ãå€ãã
ããŒãçªå·ããå¿
èŠãããå Žåã¯ã `wrangler.toml` / `wrangler.json` / `wrangler.jsonc` ã以äžã®ããã¥ã¡ã³ãã«åŸã£ãŠå€æŽããŠãã ãã:
[Wrangler Configuration](https://developers.cloudflare.com/workers/wrangler/configuration/#local-development-settings)
ããã㯠CLI ãªãã·ã§ã³ã§èšå®ããããšãã§ããŸã:
[Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/commands/#dev)
## 4. ãããã€
Cloudflare ã¢ã«ãŠã³ããæã£ãŠããå Žåã Cloudflare ã«ãããã€åºæ¥ãŸãã `package.json` ã® `$npm_execpath` ãéžæããããã±ãŒãžãããŒãžã£ã«çœ®ãæããå¿
èŠããããŸãã
::: code-group
```sh [npm]
npm run deploy
```
```sh [yarn]
yarn deploy
```
```sh [pnpm]
pnpm run deploy
```
```sh [bun]
bun run deploy
```
:::
ããã ãã§ã!
## Service Worker ã¢ãŒã / Module Worker ã¢ãŒã
Cloudflare Workers ã«ã¯2éãã®èšæ³ããããŸãã _Module Worker ã¢ãŒã_ ãš _Service Worker ã¢ãŒã_ ã§ãã Hono ã䜿ããšãã©ã¡ãã®èšæ³ã§ãæžãããšãã§ããŸããããã€ã³ãã£ã³ã°å€æ°ãããŒã«ã©ã€ãºããããã Module Worker ã¢ãŒããæšå¥šããŸãã
```ts
// Module Worker
export default app
```
```ts
// Service Worker
app.fire()
```
## ä»ã®ã€ãã³ããã³ãã©ãšãšãã« Hono ã䜿ã
_Module Worker ã¢ãŒã_ ã§ä»ã®ã€ãã³ããã³ãã©( `scheduled` ãªã©)ãçµ±åã§ããŸãã
ãã®ããã«ã `app.fetch` ã `fetch` ãã³ãã©ãšããŠãšã¯ã¹ããŒãããå¿
èŠã«å¿ããŠä»ã®ãã³ãã©ãå®è£
ããŸã:
```ts
const app = new Hono()
export default {
fetch: app.fetch,
scheduled: async (batch, env) => {},
}
```
## éçãã¡ã€ã«ã®æäŸ
éçãã¡ã€ã«ãæäŸãããå Žåã Cloudflare Workers ã® [Static Assets æ©èœ](https://developers.cloudflare.com/workers/static-assets/) ã䜿ãããšãã§ããŸãã `wrangler.toml` ã§ãã¡ã€ã«ã眮ããã£ã¬ã¯ããªãæå®ããŸã:
```toml
assets = { directory = "public" }
```
次ã«Â `public` ãã£ã¬ã¯ããªãäœæãããã¡ã€ã«ãèšçœ®ããŸã. äŸãã°ã `./public/static/hello.txt` 㯠`/static/hello.txt` ãšããŠæäŸãããŸãã
```
.
âââ package.json
âââ public
â  âââ favicon.ico
â  âââ static
â  âââ hello.txt
âââ src
â  âââ index.ts
âââ wrangler.toml
```
## å
Workers ã®åã欲ããå Žå㯠`@cloudflare/workers-types` ãã€ã³ã¹ããŒã«ããå¿
èŠããããŸãã
::: code-group
```sh [npm]
npm i --save-dev @cloudflare/workers-types
```
```sh [yarn]
yarn add -D @cloudflare/workers-types
```
```sh [pnpm]
pnpm add -D @cloudflare/workers-types
```
```sh [bun]
bun add --dev @cloudflare/workers-types
```
:::
## ãã¹ã
ãã¹ãã®ããã« `@cloudflare/vitest-pool-workers` ãæšå¥šãããŸãã
[äŸ](https://github.com/honojs/examples)ãèªãã§èšå®ããŠãã ããã
ãã®ãããªã¢ããªã±ãŒã·ã§ã³ã«å¯ŸããŠ
```ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Please test me!'))
```
ãã®ã³ãŒãã䜿çšã㊠"_200 OK_" ã¬ã¹ãã³ã¹ãè¿ããããããã¹ãããŸãã
```ts
describe('Test the application', () => {
it('Should return 200 response', async () => {
const res = await app.request('http://localhost/')
expect(res.status).toBe(200)
})
})
```
## ãã€ã³ãã£ã³ã°
Cloudflare Workers ã§ã¯ç°å¢å€æ°ã KV ããŒã ã¹ããŒã¹ã R2 ãã±ããã Durable Object ãªã©ããã€ã³ããã `c.env` ããã¢ã¯ã»ã¹ã§ããŸãã `Hono` ã®ãžã§ããªã¯ã¹ãšããŠãã€ã³ãã£ã³ã°ã®åããŒã¿ãæž¡ããŸãã
```ts
type Bindings = {
MY_BUCKET: R2Bucket
USERNAME: string
PASSWORD: string
}
const app = new Hono<{ Bindings: Bindings }>()
// Access to environment values
app.put('/upload/:key', async (c, next) => {
const key = c.req.param('key')
await c.env.MY_BUCKET.put(key, c.req.body)
return c.text(`Put ${key} successfully!`)
})
```
## ããã«ãŠã§ã¢ã§ç°å¢å€æ°ã䜿çšãã
Module Worker ã¢ãŒãã®ã¿ã§äœ¿çšã§ããŸãã
Basic èªèšŒã®ãŠãŒã¶ãŒåããã¹ã¯ãŒããªã©ãããã«ãŠã§ã¢å
ã§ç°å¢å€æ°ãã·ãŒã¯ã¬ããã䜿çšãããå Žåã¯ãã®ããã«æžããŸãã
```ts
import { basicAuth } from 'hono/basic-auth'
type Bindings = {
USERNAME: string
PASSWORD: string
}
const app = new Hono<{ Bindings: Bindings }>()
//...
app.use('/auth/*', async (c, next) => {
const auth = basicAuth({
username: c.env.USERNAME,
password: c.env.PASSWORD,
})
return auth(c, next)
})
```
åãããã« Bearer èªèšŒã JWT èªèšŒãªã©ãã§ããŸãã
## Github Actions ãããããã€ãã
CI 㧠Cloudflare ã«ãããã€ããåã«ã Cloudflare ã®ããŒã¯ã³ãå¿
èŠã§ãã [User API Tokens](https://dash.cloudflare.com/profile/api-tokens) ã§ç®¡çã§ããŸãã
æ°ããäœãããããŒã¯ã³ã§ã¯ã **Edit Cloudflare Workers** ãã³ãã¬ãŒããéžæããŸãã ãã§ã«ããŒã¯ã³ãããå Žåã¯ãããŒã¯ã³ã察å¿ããæš©éãæã£ãŠããããšã確èªããŠãã ããã ( Cloudflare Pages ãš Cloudflare Workers ã®éã§æš©éãå
±æãããªãããšã«æ³šæããŠãã ããã
次㫠GitHub ãªããžããªã®èšå®ããã·ã¥ããŒã㧠`Settings->Secrets and variables->Actions->Repository secrets` ãéãã `CLOUDFLARE_API_TOKEN` ãšããååã®ã·ãŒã¯ã¬ãããäœæããŸãã
`.github/workflows/deploy.yml` ã Hono ãããžã§ã¯ãã®ã«ãŒããã©ã«ãã«äœæãã以äžã®ã³ãŒãã貌ãä»ããŸã:
```yml
name: Deploy
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
name: Deploy
steps:
- uses: actions/checkout@v4
- name: Deploy
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
```
then edit `wrangler.toml`, and add this code after `compatibility_date` line.
```toml
main = "src/index.ts"
minify = true
```
æºåãæŽããŸãã! åŸã¯ã³ãŒãã push ããŠæ¥œããã§ãã ããã
## ããŒã«ã«éçºç°å¢ã§ç°å¢å€æ°ãããŒããã
ããŒã«ã«éçºç°å¢ã§ç°å¢å€æ°ãèšå®ããã«ã¯ã `.dev.vars` ãã¡ã€ã«ããããžã§ã¯ãã®ã«ãŒããã£ã¬ã¯ããªã«äœæããŸãã
ãããŠç°å¢å€æ°ãæ®éã® `.env` ãã¡ã€ã«ã®ããã«èšå®ããŸãã
```
SECRET_KEY=value
API_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
```
> 詳ãã㯠Cloudflare ã®ããã¥ã¡ã³ããã芧ãã ãã:
> https://developers.cloudflare.com/workers/wrangler/configuration/#secrets
ã³ãŒãã®äžã§ `c.env.*` ããç°å¢å€æ°ã«ã¢ã¯ã»ã¹ããŸãã
**Cloudflare Workers ã§ã¯ãç°å¢å€æ°ã«ã¯ `c` ããã¢ã¯ã»ã¹ããŸãã `process.env` ã§ã¯ãããŸããã**
```ts
type Bindings = {
SECRET_KEY: string
}
const app = new Hono<{ Bindings: Bindings }>()
app.get('/env', (c) => {
const SECRET_KEY = c.env.SECRET_KEY
return c.text(SECRET_KEY)
})
```
Cloudflare ã«ãããžã§ã¯ãããããã€ããåã«ãç°å¢å€æ°ãã·ãŒã¯ã¬ããã Cloudflare Workers ãããžã§ã¯ãã®èšå®ã§è¿œå ããããšãå¿ããªãã§ãã ããã
> 詳ãã㯠Cloudflare ã®ããã¥ã¡ã³ããã芧ãã ãã:
> https://developers.cloudflare.com/workers/configuration/environment-variables/#add-environment-variables-via-the-dashboard
# Deno
[Deno](https://deno.com/) 㯠V8 äžã«æ§ç¯ããã JavaScript ã©ã³ã¿ã€ã ã§ãã Node.js ã§ã¯ãããŸããã
Hono 㯠Deno ã§ãåäœããŸãã
Hono ã䜿çšã㊠TypeScript ã§ã³ãŒããæžãã `deno` ã³ãã³ãã§ã¢ããªã±ãŒã·ã§ã³ãèµ·åããŸãã ãã㊠"Deno Deploy" ã«ãããã€åºæ¥ãŸãã
## 1. Deno ã®ã€ã³ã¹ããŒã«
ãŸã `deno` ã³ãã³ããã€ã³ã¹ããŒã«ããŸãã
[å
¬åŒããã¥ã¡ã³ã](https://docs.deno.com/runtime/manual/getting_started/installation) ãåç
§ããŠãã ããã
## 2. ã»ããã¢ãã
Deno ã§ãã¹ã¿ãŒã¿ãŒã䜿çšã§ããŸãã
"create-hono" ã³ãã³ãã§ãããžã§ã¯ããäœæããŠãã ããã
```sh
deno init --npm hono my-app
```
Select `deno` template for this example.
`my-app` ã«ç§»åããŸããã Deno ã§ã¯ Hono ãæ瀺çã«ã€ã³ã¹ããŒã«ããå¿
èŠã¯ãããŸããã
```sh
cd my-app
```
## 3. Hello World
æåã®ã¢ããªã±ãŒã·ã§ã³ãæžããŠãããŸãããã
```ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Deno!'))
Deno.serve(app.fetch)
```
## 4. Run
ãã®ã³ãã³ãã ãã§ã:
```sh
deno task start
```
## ããŒããå€ãã
`main.ts` ã® `Deno.serve` ã®åŒæ°ãå€æŽããããšã§ããŒãçªå·ãæå®ã§ããŸã:
```ts
Deno.serve(app.fetch) // [!code --]
Deno.serve({ port: 8787 }, app.fetch) // [!code ++]
```
## éçãã¡ã€ã«ã®æäŸ
éçãã¡ã€ã«ãæäŸããã«ã¯ `hono/middleware.ts` ãã `serveStatic` ãã€ã³ããŒãããŠäœ¿çšããŸãã
```ts
import { Hono } from 'hono'
import { serveStatic } from 'hono/deno'
const app = new Hono()
app.use('/static/*', serveStatic({ root: './' }))
app.use('/favicon.ico', serveStatic({ path: './favicon.ico' }))
app.get('/', (c) => c.text('You can access: /static/hello.txt'))
app.get('*', serveStatic({ path: './static/fallback.txt' }))
Deno.serve(app.fetch)
```
äžã®ã³ãŒãã¯ããã®ãããªãã£ã¬ã¯ããªæ§æã§æ©èœããŸãã
```
./
âââ favicon.ico
âââ index.ts
âââ static
âââ demo
â âââ index.html
âââ fallback.txt
âââ hello.txt
âââ images
âââ dinotocat.png
```
### `rewriteRequestPath`
`http://localhost:8000/static/*` ã `./statics` ã«ããããããå Žåã `rewriteRequestPath` ããªãã·ã§ã³ã«è¿œå ããŠãã ãã:
```ts
app.get(
'/static/*',
serveStatic({
root: './',
rewriteRequestPath: (path) =>
path.replace(/^\/static/, '/statics'),
})
)
```
### `mimes`
MIME ã¿ã€ããè¿œå ããããã«ã¯ `mimes` ã䜿çšããŸã:
```ts
app.get(
'/static/*',
serveStatic({
mimes: {
m3u8: 'application/vnd.apple.mpegurl',
ts: 'video/mp2t',
},
})
)
```
### `onFound`
èŠæ±ããããã¡ã€ã«ãèŠã€ãã£ããšãã®åŠçã `onFound` ã§æå®ã§ããŸã:
```ts
app.get(
'/static/*',
serveStatic({
// ...
onFound: (_path, c) => {
c.header('Cache-Control', `public, immutable, max-age=31536000`)
},
})
)
```
### `onNotFound`
`onNotFound` ã䜿çšããŠãèŠæ±ããããã¡ã€ã«ãèŠã€ãããªãå Žåã®åŠçãèšè¿°ã§ããŸã:
```ts
app.get(
'/static/*',
serveStatic({
onNotFound: (path, c) => {
console.log(`${path} is not found, you access ${c.req.path}`)
},
})
)
```
### `precompressed`
`precompressed` ãªãã·ã§ã³ã䜿ããš `Accept-Encoding` ãããã«åºã¥ã㊠`.br` ã `.gz` ãšãã£ãæ¡åŒµåãæã£ãŠãããã¡ã€ã«ãæãã確èªããæäŸããŸãã Brotli ã Zstd ã Gzip ã®é ã§åªå
ãããŸãã ããããç¡ããã°å
ã®ãã¡ã€ã«ãæäŸãããŸãã
```ts
app.get(
'/static/*',
serveStatic({
precompressed: true,
})
)
```
## Deno Deploy
Deno Deploy 㯠Deno ã®ããã®ãšããžã©ã³ã¿ã€ã ãã©ãããã©ãŒã ã§ãã
Deno Deploy ã§ã¯ãŒã«ãã¯ã€ãã«ã¢ããªã±ãŒã·ã§ã³ãå
¬éã§ããŸãã
Hono 㯠Deno Deploy ããµããŒãããŠããŸãã [å
¬åŒããã¥ã¡ã³ã](https://docs.deno.com/deploy/manual/)ãåç
§ããŠãã ããã
## ãã¹ã
Deno ã§ã¢ããªã±ãŒã·ã§ã³ããã¹ãããã®ã¯ç°¡åã§ãã
`Deno.test` ãšãå
¬åŒã©ã€ãã©ãªã® `assert` ã `assertEquals` ã [@std/assert](https://jsr.io/@std/assert) ããã€ã³ããŒãããŠæžããŠãã ããã
```sh
deno add jsr:@std/assert
```
```ts
import { Hono } from 'hono'
import { assertEquals } from '@std/assert'
Deno.test('Hello World', async () => {
const app = new Hono()
app.get('/', (c) => c.text('Please test me'))
const res = await app.request('http://localhost/')
assertEquals(res.status, 200)
})
```
次ã«ãã®ã³ãã³ããå®è¡ããŸã:
```sh
deno test hello.ts
```
## `npm:` æå®å
`npm:hono` ã䜿ããŸãã ããã䜿ãããã«ã¯ `deno.json` ãä¿®æ£ããŸã:
```json
{
"imports": {
"hono": "jsr:@hono/hono" // [!code --]
"hono": "npm:hono" // [!code ++]
}
}
```
`npm:hono` ã `jsr:@hono/hono` ã®ã©ã¡ããã䜿ãããšãã§ããŸãã
`npm:@hono/zod-validator` ãšãã£ããµãŒãããŒãã£ããã«ãŠã§ã¢ã TypeScript ã®åæšè«ä»ãã§äœ¿çšãããå Žåã¯ã `npm:` æå®åãå¿
èŠã§ãã
```json
{
"imports": {
"hono": "npm:hono",
"zod": "npm:zod",
"@hono/zod-validator": "npm:@hono/zod-validator"
}
}
```
# Fastly Compute
[Fastly's Compute](https://www.fastly.com/products/edge-compute) ãªãã¡ãªã³ã°ã«ãã£ãŠã倧èŠæš¡ãªã§ã°ããŒãã«ã¹ã±ãŒã«ã®ã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ãã Fastry CDN ã®ãšããžã§åããããšãåºæ¥ãŸãã
Hono 㯠Fastly Compute ã§ãåããŸãã
## 1. CLI ãã€ã³ã¹ããŒã«ãã
Fastly Compute ã䜿çšããããã«ã [Fastly ã¢ã«ãŠã³ããäœæ](https://www.fastly.com/jp/signup/)ããå¿
èŠããããŸãã
次㫠[Fastly CLI](https://github.com/fastly/cli) ãã€ã³ã¹ããŒã«ããŸãã
macOS
```sh
brew install fastly/tap/fastly
```
ä»ã® OS ã§ã¯æ¬¡ã®ããã¥ã¡ã³ããåç
§ããŠãã ãã:
- [Compute services | Fastly Developer Hub](https://developer.fastly.com/learning/compute/#download-and-install-the-fastly-cli)
## 2. ã»ããã¢ãã
ã¹ã¿ãŒã¿ãŒã¯ Fastly Compute ã§ã䜿çšã§ããŸãã
"create-hono" ã³ãã³ãã§ãããžã§ã¯ããéå§ããŸãããã
`fastly` ãã³ãã¬ãŒããéžæããŸãã
::: code-group
```sh [npm]
npm create hono@latest my-app
```
```sh [yarn]
yarn create hono my-app
```
```sh [pnpm]
pnpm create hono my-app
```
```sh [bun]
bun create hono@latest my-app
```
```sh [deno]
deno init --npm hono my-app
```
:::
`my-app` ã«ç§»åããŠäŸåé¢ä¿ãã€ã³ã¹ããŒã«ããŸãã
::: code-group
```sh [npm]
cd my-app
npm i
```
```sh [yarn]
cd my-app
yarn
```
```sh [pnpm]
cd my-app
pnpm i
```
```sh [bun]
cd my-app
bun i
```
:::
## 3. Hello World
`src/index.ts` ãå€æŽããŸã:
```ts
// src/index.ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Fastly!'))
app.fire()
```
## 4. Run
ããŒã«ã«ã§éçºãµãŒããŒãèµ·åãããã©ãŠã¶ã§ `http://localhost:7676` ã«ã¢ã¯ã»ã¹ããŠãã ããã
::: code-group
```sh [npm]
npm run dev
```
```sh [yarn]
yarn dev
```
```sh [pnpm]
pnpm dev
```
```sh [bun]
bun run dev
```
:::
## 4. ãããã€
::: code-group
```sh [npm]
npm run deploy
```
```sh [yarn]
yarn deploy
```
```sh [pnpm]
pnpm deploy
```
```sh [bun]
bun run deploy
```
:::
ããã ãã§ã!!!
# Lambda@Edge
[Lambda@Edge](https://aws.amazon.com/lambda/edge/) 㯠Amazon Web Services ã®ãµãŒããŒã¬ã¹ãã©ãããã©ãŒã ã§ãã ããã«ããã Amazon CloudFront ã®ãšããžãã±ãŒã·ã§ã³ã§ Lambda é¢æ°ãå®è¡ã§ããããã«ãªãã HTTP ãªã¯ãšã¹ã/ã¬ã¹ãã³ã¹ãã«ã¹ã¿ãã€ãºã§ããããã«ãªããŸãã
Hono 㯠Node.js 18 以äžã® Lambda@Edge ããµããŒãããŸãã
## 1. ã»ããã¢ãã
Lambda@Edge ã§ã¢ããªã±ãŒã·ã§ã³ãäœæããæã«
[CDK](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-cdk.html)
㯠CloudFront ã IAM ããŒã«ã API ã²ãŒããŠã§ã€ãªã©ã®æ©èœãèšå®ããã®ã«åœ¹ç«ã¡ãŸãã
`cdk` CLI ã䜿çšããŠãããžã§ã¯ããåæåããŸãã
::: code-group
```sh [npm]
mkdir my-app
cd my-app
cdk init app -l typescript
npm i hono
mkdir lambda
```
```sh [yarn]
mkdir my-app
cd my-app
cdk init app -l typescript
yarn add hono
mkdir lambda
```
```sh [pnpm]
mkdir my-app
cd my-app
cdk init app -l typescript
pnpm add hono
mkdir lambda
```
```sh [bun]
mkdir my-app
cd my-app
cdk init app -l typescript
bun add hono
mkdir lambda
```
:::
## 2. Hello World
`lambda/index_edge.ts` ãç·šéããŸãã
```ts
import { Hono } from 'hono'
import { handle } from 'hono/lambda-edge'
const app = new Hono()
app.get('/', (c) => c.text('Hello Hono on Lambda@Edge!'))
export const handler = handle(app)
```
## 3. ãããã€
`bin/my-app.ts` ãå€æŽããŸãã
```ts
#!/usr/bin/env node
import 'source-map-support/register'
import * as cdk from 'aws-cdk-lib'
import { MyAppStack } from '../lib/my-app-stack'
const app = new cdk.App()
new MyAppStack(app, 'MyAppStack', {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: 'us-east-1',
},
})
```
`lambda/cdk-stack.ts` ãå€æŽããŸãã
```ts
import { Construct } from 'constructs'
import * as cdk from 'aws-cdk-lib'
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'
import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'
import * as lambda from 'aws-cdk-lib/aws-lambda'
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs'
import * as s3 from 'aws-cdk-lib/aws-s3'
export class MyAppStack extends cdk.Stack {
public readonly edgeFn: lambda.Function
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props)
const edgeFn = new NodejsFunction(this, 'edgeViewer', {
entry: 'lambda/index_edge.ts',
handler: 'handler',
runtime: lambda.Runtime.NODEJS_20_X,
})
// Upload any html
const originBucket = new s3.Bucket(this, 'originBucket')
new cloudfront.Distribution(this, 'Cdn', {
defaultBehavior: {
origin: new origins.S3Origin(originBucket),
edgeLambdas: [
{
functionVersion: edgeFn.currentVersion,
eventType: cloudfront.LambdaEdgeEventType.VIEWER_REQUEST,
},
],
},
})
}
}
```
æåŸã«ã以äžã®ã³ãã³ãããå®è¡ããŠãããã€ããŸã:
```sh
cdk deploy
```
## ã³ãŒã«ããã¯
Basic èªèšŒåŸã«ãªã¯ãšã¹ããåŠçãããå Žå㯠`c.env.callback()` ã䜿çšã§ããŸãã
```ts
import { Hono } from 'hono'
import { basicAuth } from 'hono/basic-auth'
import type { Callback, CloudFrontRequest } from 'hono/lambda-edge'
import { handle } from 'hono/lambda-edge'
type Bindings = {
callback: Callback
request: CloudFrontRequest
}
const app = new Hono<{ Bindings: Bindings }>()
app.get(
'*',
basicAuth({
username: 'hono',
password: 'acoolproject',
})
)
app.get('/', async (c, next) => {
await next()
c.env.callback(null, c.env.request)
})
export const handler = handle(app)
```
# Netlify
Netlify ã¯éçãµã€ããã¹ãã£ã³ã°ãšãµãŒããŒã¬ã¹ããã¯ãšã³ããµãŒãã¹ãæäŸããŸãã [Edge Functions](https://docs.netlify.com/edge-functions/overview/) ã䜿çšãããš Web ããŒãžãåçã«ããããšãåºæ¥ãŸãã
Edge Functions 㯠Deno ãš TypeScript ããµããŒãããŠããã [Netlify CLI](https://docs.netlify.com/cli/get-started/) ã䜿çšããããšã§ç°¡åã«ãããã€åºæ¥ãŸãã Hono ã䜿çšã㊠Netlify Edge Functions åãã®ã¢ããªã±ãŒã·ã§ã³ãäœæã§ããŸãã
## 1. ã»ããã¢ãã
Netlify åãã®ã¹ã¿ãŒã¿ãŒããã¡ããçšæãããŠããŸãã
"create-hono" ã³ãã³ãã§ãããžã§ã¯ããå§ããŸãããã
ãã®äŸã§ã¯ `netlify` ãã³ãã¬ãŒããéžã³ãŸãã
::: code-group
```sh [npm]
npm create hono@latest my-app
```
```sh [yarn]
yarn create hono my-app
```
```sh [pnpm]
pnpm create hono my-app
```
```sh [bun]
bun create hono@latest my-app
```
```sh [deno]
deno init --npm hono my-app
```
:::
`my-app` ã«ç§»åããŸãã
## 2. Hello World
`netlify/edge-functions/index.ts` ãå€æŽããŸã:
```ts
import { Hono } from 'jsr:@hono/hono'
import { handle } from 'jsr:@hono/hono/netlify'
const app = new Hono()
app.get('/', (c) => {
return c.text('Hello Hono!')
})
export default handle(app)
```
## 3. Run
Netlify CLI ã§éçºãµãŒããŒãèµ·åãã Web ãã©ãŠã¶ã§ `http://localhost:8888` ã«ã¢ã¯ã»ã¹ããŸãã
```sh
netlify dev
```
## 4. ãããã€
`netlify deploy` ã³ãã³ãã§ãããã€åºæ¥ãŸãã
```sh
netlify deploy --prod
```
## `Context`
Netlify åãã® `Context` 㯠`c.env` ã䜿çšã§ããŸã:
```ts
import { Hono } from 'jsr:@hono/hono'
import { handle } from 'jsr:@hono/hono/netlify'
// Import the type definition
import type { Context } from 'https://edge.netlify.com/'
export type Env = {
Bindings: {
context: Context
}
}
const app = new Hono()
app.get('/country', (c) =>
c.json({
'You are in': c.env.context.geo.country?.name,
})
)
export default handle(app)
```
# Node.js
[Node.js](https://nodejs.org/) ã¯ãªãŒãã³ãœãŒã¹ã§ã¯ãã¹ãã©ãããã©ãŒã ã® JavaScript ã©ã³ã¿ã€ã ç°å¢ã§ãã
Hono 㯠Node.js åãã«èšèšãããããã§ã¯ãããŸãããã [Node.js Adapter](https://github.com/honojs/node-server) ã䜿ããš Node.js ã§ãå®è¡ã§ããŸãã
::: info
Node.js 18.x 以äžã§åäœããŸãã å
·äœçã«å¿
èŠãª Node.js ã®ããŒãžã§ã³ã¯ä»¥äžã®éãã§ã:
- 18.x => 18.14.1+
- 19.x => 19.7.0+
- 20.x => 20.0.0+
å
·äœçã«ã¯ãåã¡ãžã£ãŒãªãªãŒã¹ã®ææ°ããŒãžã§ã³ã䜿çšããã ãã§ãã
:::
## 1. ã»ããã¢ãã
ã¹ã¿ãŒã¿ãŒã¯ Node.js ããµããŒãããŠããŸãã
"create-hono" ã³ãã³ãã§éçºãéå§ããŸãããã
ãã®äŸã§ã¯ `nodejs` ãã³ãã¬ãŒããéžã³ãŸãã
::: code-group
```sh [npm]
npm create hono@latest my-app
```
```sh [yarn]
yarn create hono my-app
```
```sh [pnpm]
pnpm create hono my-app
```
```sh [bun]
bun create hono@latest my-app
```
```sh [deno]
deno init --npm hono my-app
```
:::
`my-app` ã«ç§»åããŠäŸåããã±ãŒãžãã€ã³ã¹ããŒã«ããŸãã
::: code-group
```sh [npm]
cd my-app
npm i
```
```sh [yarn]
cd my-app
yarn
```
```sh [pnpm]
cd my-app
pnpm i
```
```sh [bun]
cd my-app
bun i
```
:::
## 2. Hello World
`src/index.ts` ãç·šéããŸã:
```ts
import { serve } from '@hono/node-server'
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello Node.js!'))
serve(app)
```
## 3. Run
éçºãµãŒããŒãããŒã«ã«ã§èµ·åãããã©ãŠã¶ã§ `http://localhost:3000` ã«ã¢ã¯ã»ã¹ããŸãã
::: code-group
```sh [npm]
npm run dev
```
```sh [yarn]
yarn dev
```
```sh [pnpm]
pnpm dev
```
:::
## ããŒããå€ãã
`port` ãªãã·ã§ã³ã§ããŒãçªå·ãæå®ã§ããŸãã
```ts
serve({
fetch: app.fetch,
port: 8787,
})
```
## çã® Node.js API ã«ã¢ã¯ã»ã¹ãã
Node.js API 㯠`c.env.incoming` ãš `c.env.outgoing` ã§äœ¿çšã§ããŸãã
```ts
import { Hono } from 'hono'
import { serve, type HttpBindings } from '@hono/node-server'
// or `Http2Bindings` if you use HTTP2
type Bindings = HttpBindings & {
/* ... */
}
const app = new Hono<{ Bindings: Bindings }>()
app.get('/', (c) => {
return c.json({
remoteAddress: c.env.incoming.socket.remoteAddress,
})
})
serve(app)
```
## éçãã¡ã€ã«ã®é
ä¿¡
`serveStatic` ã䜿ãããšã§ããŒã«ã«ãã¡ã€ã«ã·ã¹ãã ããéçãã¡ã€ã«ãé
ä¿¡ã§ããŸãã 以äžã®ãããªãã£ã¬ã¯ããªæ§æã®å ŽåãèããŠã¿ãŸããã:
```sh
./
âââ favicon.ico
âââ index.ts
âââ static
âââ hello.txt
âââ image.png
```
`/static/*` ã«ã¢ã¯ã»ã¹ããã£ããšãã« `./static` ã«ãããã¡ã€ã«ãè¿ãããã«ã¯ãäžã®ããã«æžããŸã:
```ts
import { serveStatic } from '@hono/node-server/serve-static'
app.use('/static/*', serveStatic({ root: './' }))
```
`path` ãªãã·ã§ã³ã䜿ã£ãŠãã«ãŒãã«ãã `favicon.ico` ãé
ä¿¡ããŸã:
```ts
app.use('/favicon.ico', serveStatic({ path: './favicon.ico' }))
```
`/hello.txt` ã `/image.png` ã«ã¢ã¯ã»ã¹ããããšãã«ã `./static/hello.txt` ã `./static/image.png` ãšãã£ããã¡ã€ã«åã®ãã¡ã€ã«ãè¿ãã«ã¯ã以äžã®ããã«æžããŸã:
```ts
app.use('*', serveStatic({ root: './static' }))
```
### `rewriteRequestPath`
`http://localhost:3000/static/*` ã `./statics` ã«ããããããå Žå㯠`rewriteRequestPath` ãªãã·ã§ã³ã䜿çšã§ããŸã:
```ts
app.get(
'/static/*',
serveStatic({
root: './',
rewriteRequestPath: (path) =>
path.replace(/^\/static/, '/statics'),
})
)
```
## http2
Hono ã [Node.js http2 Server](https://nodejs.org/api/http2.html) ã§ãå®è¡ã§ããŸãã
### unencrypted http2
```ts
import { createServer } from 'node:http2'
const server = serve({
fetch: app.fetch,
createServer,
})
```
### encrypted http2
```ts
import { createSecureServer } from 'node:http2'
import { readFileSync } from 'node:fs'
const server = serve({
fetch: app.fetch,
createServer: createSecureServer,
serverOptions: {
key: readFileSync('localhost-privkey.pem'),
cert: readFileSync('localhost-cert.pem'),
},
})
```
## Dockerfile
Dockerfile ã®äŸ:
```Dockerfile
FROM node:20-alpine AS base
FROM base AS builder
RUN apk add --no-cache gcompat
WORKDIR /app
COPY package*json tsconfig.json src ./
RUN npm ci && \
npm run build && \
npm prune --production
FROM base AS runner
WORKDIR /app
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 hono
COPY --from=builder --chown=hono:nodejs /app/node_modules /app/node_modules
COPY --from=builder --chown=hono:nodejs /app/dist /app/dist
COPY --from=builder --chown=hono:nodejs /app/package.json /app/package.json
USER hono
EXPOSE 3000
CMD ["node", "/app/dist/index.js"]
```
次ã«ä»¥äžã®äœæ¥ãããŠãã ããã
1. `"outDir": "./dist"` ã `tsconfig.json` ã® `compilerOptions` ã«è¿œå ããã
2. `tsconfig.json` ã« `"exclude": ["node_modules"]` ãè¿œå ããã
3. `package.json` ã® `script` ã« `"build": "tsc"` ãè¿œå ããã
4. `npm install typescript --save-dev` ãå®è¡ããã
5. `"type": "module"` ã `package.json` ã«è¿œå ããã
# Service Worker
[Service Worker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) ã¯ãã£ãã·ã¥ãããã·ã¥éç¥ãªã©ãåŠçããããã« Web ãã©ãŠã¶ã®ããã¯ã°ã©ãŠã³ãã§åãã¹ã¯ãªããã§ãã Service Worker ã¢ããã¿ã䜿çšããããšã§ã Hono ã§äœã£ãã¢ããªã±ãŒã·ã§ã³ã [FetchEvent](https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent) ãã³ãã©ãšããŠãã©ãŠã¶å
ã§åããããšãã§ããŸãã
ãã®ããŒãžã§ã¯ [Vite](https://vitejs.dev/) ã䜿ã£ãŠãããžã§ã¯ããäœãäŸã解説ããŸãã
## 1. ã»ããã¢ãã
ãŸãã¯ããããžã§ã¯ãã®ãã£ã¬ã¯ããªãäœã£ãŠç§»åããŸã:
```sh
mkdir my-app
cd my-app
```
ãããžã§ã¯ãã«å¿
èŠãªãã¡ã€ã«ãäœæããŸãã 以äžã®ããã« `package.json` ãæžããŠãã ãã:
```json
{
"name": "my-app",
"private": true,
"scripts": {
"dev": "vite dev"
},
"type": "module"
}
```
åæ§ã«ã `tsconfig.json` ãäœã£ãŠãã ãã:
```json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM", "WebWorker"],
"moduleResolution": "bundler"
},
"include": ["./"],
"exclude": ["node_modules"]
}
```
次ã«ãå¿
èŠãªããã±ãŒãžãã€ã³ã¹ããŒã«ããŸãã
::: code-group
```sh [npm]
npm i hono
npm i -D vite
```
```sh [yarn]
yarn add hono
yarn add -D vite
```
```sh [pnpm]
pnpm add hono
pnpm add -D vite
```
```sh [bun]
bun add hono
bun add -D vite
```
:::
## 2. Hello World
`index.html` ãæžããŸã:
```html
Hello World by Service Worker
```
`main.ts` 㯠Service Worker ãç»é²ããããã®ã¹ã¯ãªããã§ã:
```ts
function register() {
navigator.serviceWorker
.register('/sw.ts', { scope: '/sw', type: 'module' })
.then(
function (_registration) {
console.log('Register Service Worker: Success')
},
function (_error) {
console.log('Register Service Worker: Error')
}
)
}
function start() {
navigator.serviceWorker
.getRegistrations()
.then(function (registrations) {
for (const registration of registrations) {
console.log('Unregister Service Worker')
registration.unregister()
}
register()
})
}
start()
```
`sw.ts` ã§ã Hono ã䜿ã£ãŠã¢ããªã±ãŒã·ã§ã³ãäœãã Service Worker ã¢ããã¿ã® `handle` é¢æ°ã䜿ã£ãŠ `fetch` ã€ãã³ãã«ç»é²ããŸãã ããã«ããã Hono ã¢ããªã±ãŒã·ã§ã³ã `/sw` 以äžã®ã¢ã¯ã»ã¹ãååã§ããããã«ãªããŸãã
```ts
// To support types
// https://github.com/microsoft/TypeScript/issues/14877
declare const self: ServiceWorkerGlobalScope
import { Hono } from 'hono'
import { handle } from 'hono/service-worker'
const app = new Hono().basePath('/sw')
app.get('/', (c) => c.text('Hello World'))
self.addEventListener('fetch', handle(app))
```
## 3. å®è¡
éçºãµãŒããŒãèµ·åããŸã
::: code-group
```sh [npm]
npm run dev
```
```sh [yarn]
yarn dev
```
```sh [pnpm]
pnpm run dev
```
```sh [bun]
bun run dev
```
:::
ããã©ã«ãã§ã¯ãéçºãµãŒããŒã¯ `5173` çªããŒãã§èµ·åããŸãã ãã©ãŠã¶ã§ `http://localhost:5173/` ã«ã¢ã¯ã»ã¹ããããšã§ Service Worker ã®ç»é²ãå®äºããŸãã 次ã«ã `/sw` ã«ã¢ã¯ã»ã¹ã㊠Hono ã¢ããªã±ãŒã·ã§ã³ããã®ã¬ã¹ãã³ã¹ãèŠãããšãã§ããŸãã
# Supabase Edge Functions
[Supabase](https://supabase.com/) ã¯ãªãŒãã³ãœãŒã¹ã® Firebase 代æ¿ã§ãããŒã¿ããŒã¹ãèªèšŒãã¹ãã¬ãŒãžãä»å䜿ããµãŒããŒã¬ã¹é¢æ°ãªã©ã® Firebase ã®æ©èœãšåæ§ãªããŒã«ã¹ã€ãŒããæäŸããŸãã
Supabase Edge Functions ã¯ã°ããŒãã«ã«åæ£ããããµãŒããŒãµã€ã TypeScript é¢æ°ã§ãé«ãããã©ãŒãã³ã¹ã®ããã«ãŠãŒã¶ãŒã®è¿ãã§å®è¡ãããŸãã [Deno](https://deno.com/) ãçšããŠãããã»ãã¥ãªãã£ã®åäžãã¢ãã³ JavaScript/TypeScript ãªã©ã®ã¡ãªããããããŸãã
Supabase Edge Functions ãå§ããæ¹æ³ã¯ä»¥äžã®éãã§ã:
## 1. ã»ããã¢ãã
### åææ¡ä»¶
å§ããåã«ã Supabase CLI ãã€ã³ã¹ããŒã«ãããŠããããšã確èªããŠãã ããã ãŸã ã€ã³ã¹ããŒã«ããŠããªãå Žåã¯[å
¬åŒããã¥ã¡ã³ã](https://supabase.com/docs/guides/cli/getting-started)ã«ãããã£ãŠã€ã³ã¹ããŒã«ããŠãã ããã
### æ°ãããããžã§ã¯ããäœæãã
1. ã¿ãŒããã«/ã³ãã³ãããã³ãããéããŠãã ããã
2. 次ã®ã³ãã³ããå®è¡ããŠãããŒã«ã«ãã·ã³ã®ãã£ã¬ã¯ããªã«æ°ãã Supabase ãããžã§ã¯ããäœæããŸãã
```bash
supabase init
```
ãã®ã³ãã³ãã¯çŸåšã®ãã£ã¬ã¯ããªã«æ°ãã Supabase ãããžã§ã¯ããåæåããŸãã
### ãšããžé¢æ°ã®è¿œå
3. Supabase ãããžã§ã¯ãã®äžã§æ°ãããšããžé¢æ°ã `hello-world` ãšããååã§äœæããŸã:
```bash
supabase functions new hello-world
```
ãã®ã³ãã³ãã§ååãæå®ããŠæ°ãããšããžé¢æ°ããããžã§ã¯ãã«äœæããŸãã
## 2. Hello World
`supabase/functions/hello-world/index.ts` ã«ãã `hello-world` é¢æ°ãç·šéããŠãã ãã:
```ts
import { Hono } from 'jsr:@hono/hono'
// change this to your function name
const functionName = 'hello-world'
const app = new Hono().basePath(`/${functionName}`)
app.get('/hello', (c) => c.text('Hello from hono-server!'))
Deno.serve(app.fetch)
```
## 3. Run
ããŒã«ã«ã§é¢æ°ãåããã«ã¯æ¬¡ã®ã³ãã³ããå®è¡ããŸã:
1. 以äžã®ã³ãã³ããå®è¡ããŠé¢æ°ãéå§ããŸã:
```bash
supabase start # start the supabase stack
supabase functions serve --no-verify-jwt # start the Functions watcher
```
`--no-verify-jwt` ãã©ã°ã¯ããŒã«ã«éçºäžã« JWT æ€èšŒããã€ãã¹ã§ããŸãã
2. cURL ã Postman ã䜿çšã㊠`http://127.0.0.1:54321/functions/v1/hello-world/hello` ã« GET ãªã¯ãšã¹ããéä¿¡ããŸã:
```bash
curl --location 'http://127.0.0.1:54321/functions/v1/hello-world/hello'
```
"Hello from hono-server!" ãšããæååãè¿ãããã¯ãã§ãã
## 4. ãããã€
å
šãŠã®ãšããžé¢æ°ãã²ãšã€ã®ã³ãã³ã㧠Supabase ã«ãããã€åºæ¥ãŸã:
```bash
supabase functions deploy
```
ããã㯠deploy ã³ãã³ãã§é¢æ°åãæå®ããŠããããã®ãšããžé¢æ°ããããã€åºæ¥ãŸã:
```bash
supabase functions deploy hello-world
```
ãã®ä»ã®ãããã€æ¹æ³ã«ã€ããŠã¯ Supabase ããã¥ã¡ã³ã [Deploying to Production](https://supabase.com/docs/guides/functions/deploy) ããèªã¿ãã ããã
# Vercel
Vercel ã¯ããã³ããšã³ãéçºè
ã®ããã®ãã©ãããã©ãŒã ã§ãã€ãããŒã¿ãŒãã€ã³ã¹ãã¬ãŒã·ã§ã³ã®ç¬éã«å¶äœãããããã«å¿
èŠãªã¹ããŒããšä¿¡é Œæ§ãæäŸããŸãã ãã®ã»ã¯ã·ã§ã³ã§ã¯ Vercel äžã§å®è¡ããã Next.js 玹ä»ããŸãã
Next.js ã¯ãé«éãªãWeb ã¢ããªã±ãŒã·ã§ã³ãäœæããããã®èŠçŽ ãæäŸããæè»ãª React ãã¬ãŒã ã¯ãŒã¯ã§ãã
Next.js ã§ã¯ [Edge Functions](https://vercel.com/docs/concepts/functions/edge-functions) ã䜿çšã㊠Vercel ãªã©ã®ãšããžã©ã³ã¿ã€ã ã«åç API ãäœæã§ããŸãã
Hono ã䜿çšãããšãä»ã®ã©ã³ã¿ã€ã ãšåãæ§æã§ãAPI ãèšè¿°ããå€ãã®ããã«ãŠã§ã¢ã䜿çšã§ããŸãã
## 1. ã»ããã¢ãã
Next.js åãã®ã¹ã¿ãŒã¿ãŒããããŸãã
"create-hono" ã³ãã³ãã§å§ããŸãããã
`nextjs` ãã³ãã¬ãŒããéžæããŸãã
::: code-group
```sh [npm]
npm create hono@latest my-app
```
```sh [yarn]
yarn create hono my-app
```
```sh [pnpm]
pnpm create hono my-app
```
```sh [bun]
bun create hono@latest my-app
```
```sh [deno]
deno init --npm hono my-app
```
:::
`my-app` ã«ç§»åããäŸåé¢ä¿ãã€ã³ã¹ããŒã«ããŸãã
::: code-group
```sh [npm]
cd my-app
npm i
```
```sh [yarn]
cd my-app
yarn
```
```sh [pnpm]
cd my-app
pnpm i
```
```sh [bun]
cd my-app
bun i
```
:::
## 2. Hello World
App Router ã䜿çšããŠããå Žå `app/api/[[...route]]/route.ts` ã«æžããŠãã ããã [Supported HTTP Methods](https://nextjs.org/docs/app/building-your-application/routing/route-handlers#supported-http-methods) ãåç
§ããŠãã ããã
```ts
import { Hono } from 'hono'
import { handle } from 'hono/vercel'
export const runtime = 'edge'
const app = new Hono().basePath('/api')
app.get('/hello', (c) => {
return c.json({
message: 'Hello Next.js!',
})
})
export const GET = handle(app)
export const POST = handle(app)
```
Pages Router ã䜿çšããŠããå Žå㯠`pages/api/[[...route]].ts` ã«èšè¿°ããŸãã
```ts
import { Hono } from 'hono'
import { handle } from 'hono/vercel'
import type { PageConfig } from 'next'
export const config: PageConfig = {
runtime: 'edge',
}
const app = new Hono().basePath('/api')
app.get('/hello', (c) => {
return c.json({
message: 'Hello Next.js!',
})
})
export default handle(app)
```
## 3. Run
éçºãµãŒããŒãããŒã«ã«ã§åããããã©ãŠã¶ã§ `http://localhost:3000` ã«ã¢ã¯ã»ã¹ããŸãããã
::: code-group
```sh [npm]
npm run dev
```
```sh [yarn]
yarn dev
```
```sh [pnpm]
pnpm dev
```
```sh [bun]
bun run dev
```
:::
ä»ã¯ `/api/hello` 㧠JSON ãè¿ãã ãã§ããã React 㧠UI ãäœæããã° Hono ã§ãã«ã¹ã¿ãã¯ã¢ããªã±ãŒã·ã§ã³ãäœæã§ããŸãã
## 4. ãããã€
Vercel ã¢ã«ãŠã³ããæã£ãŠããå Žå㯠Git é£æºã§ãããã€åºæ¥ãŸãã
## Node.js
Node.js ã©ã³ã¿ã€ã äžã® Next.js 㧠Hono ã䜿ãããšãåºæ¥ãŸãã
### App Router
App Router ã§ã¯ãã«ãŒããã³ãã©ã®ã©ã³ã¿ã€ã ã `nodejs` ã«èšå®ããã ãã§äœ¿çšã§ããŸã:
```ts
import { Hono } from 'hono'
import { handle } from 'hono/vercel'
export const runtime = 'nodejs'
const app = new Hono().basePath('/api')
app.get('/hello', (c) => {
return c.json({
message: 'Hello from Hono!',
})
})
export const GET = handle(app)
export const POST = handle(app)
```
### Pages Router
Pages Router ã§ã¯ããŸã Node.js ã¢ããã¿ãã€ã³ã¹ããŒã«ããå¿
èŠããããŸã:
::: code-group
```sh [npm]
npm i @hono/node-server
```
```sh [yarn]
yarn add @hono/node-server
```
```sh [pnpm]
pnpm add @hono/node-server
```
```sh [bun]
bun add @hono/node-server
```
:::
次ã«ã `@hono/node-server/vercel` ããã€ã³ããŒããã `handle` ã䜿çšããŸã:
```ts
import { Hono } from 'hono'
import { handle } from '@hono/node-server/vercel'
import type { PageConfig } from 'next'
export const config: PageConfig = {
api: {
bodyParser: false,
},
}
const app = new Hono().basePath('/api')
app.get('/hello', (c) => {
return c.json({
message: 'Hello from Hono!',
})
})
export default handle(app)
```
ããã Pages Router ã§åããããã«ã¯ããããžã§ã¯ãããã·ã¥ããŒãã `.env` ãã¡ã€ã«ã§ç°å¢å€æ°ãèšå®ã㊠Vercel ã® Node.js ãã«ããŒãç¡å¹åããããšãéèŠã§ã:
```text
NODEJS_HELPERS=0
```
# Context
Request / Response ãåŠçããã«ã¯ã `Context` ãªããžã§ã¯ãã䜿çšããŸãã
## req
`req` 㯠HonoRequest ã®ã€ã³ã¹ã¿ã³ã¹ã§ãã 詳ãã㯠[HonoRequest](/docs/api/request) ãã芧ãã ããã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/hello', (c) => {
const userAgent = c.req.header('User-Agent')
// ...
// ---cut-start---
return c.text(`Hello, ${userAgent}`)
// ---cut-end---
})
```
## body()
HTTP ã¬ã¹ãã³ã¹ãè¿ããŸãã
`c.header()` ã§ããããã»ãããã `c.status` 㧠HTTP ã¹ããŒã¿ã¹ã³ãŒããæå®ããŸãã
ãã㯠`c.text()` ã `c.json()` ãªã©ã§ãåãããã«èšå®ã§ããŸãã
::: info
**Note**: ããã¹ãã HTML ãè¿ãå Žåã¯ã `c.text()` ã `c.html()` ã䜿ã£ãŠãã ããã
:::
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/welcome', (c) => {
// Set headers
c.header('X-Message', 'Hello!')
c.header('Content-Type', 'text/plain')
// Set HTTP status code
c.status(201)
// Return the response body
return c.body('Thank you for coming')
})
```
ãã®ããã«æžãããšãã§ããŸãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/welcome', (c) => {
return c.body('Thank you for coming', 201, {
'X-Message': 'Hello!',
'Content-Type': 'text/plain',
})
})
```
以äžãšåãã§ãã
```ts twoslash
new Response('Thank you for coming', {
status: 201,
headers: {
'X-Message': 'Hello!',
'Content-Type': 'text/plain',
},
})
```
## text()
`Content-Type:text/plain` ã§ããã¹ããã¬ã³ããªã³ã°ããŸãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/say', (c) => {
return c.text('Hello!')
})
```
## json()
`Content-Type:application/json` 㧠JSON ãã¬ã³ããªã³ã°ããŸãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/api', (c) => {
return c.json({ message: 'Hello!' })
})
```
## html()
`Content-Type:text/html` 㧠HTML ãã¬ã³ããªã³ã°ããŸãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/', (c) => {
return c.html('Hello! Hono! ')
})
```
## notFound()
`Not Found` ã¬ã¹ãã³ã¹ãè¿ããŸãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/notfound', (c) => {
return c.notFound()
})
```
## redirect()
ãªãã€ã¬ã¯ãããŸãã ããã©ã«ãã®ã¹ããŒã¿ã¹ã³ãŒã㯠`302` ã§ãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/redirect', (c) => {
return c.redirect('/')
})
app.get('/redirect-permanently', (c) => {
return c.redirect('/', 301)
})
```
## res
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
// Response object
app.use('/', async (c, next) => {
await next()
c.res.headers.append('X-Debug', 'Debug message')
})
```
## set() / get()
ãªã¯ãšã¹ãã®éã®å¯¿åœãæã€ä»»æã®ããŒããªã¥ãŒã®ãã¢ãèšå®ãååŸã§ããŸãã ããã«ãããããã«ãŠã§ã¢éãããã«ãŠã§ã¢ãã«ãŒããã³ãã©éã§ããŒã¿ãæž¡ãããšãã§ããŸãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono<{ Variables: { message: string } }>()
// ---cut---
app.use(async (c, next) => {
c.set('message', 'Hono is cool!!')
await next()
})
app.get('/', (c) => {
const message = c.get('message')
return c.text(`The message is "${message}"`)
})
```
`Variables` ãžã§ããªã¯ã¹ã `Hono` ã«æž¡ããšåå®å
šã«ãªããŸãã
```ts twoslash
import { Hono } from 'hono'
// ---cut---
type Variables = {
message: string
}
const app = new Hono<{ Variables: Variables }>()
```
`c.set` / `c.get` ã¯åããªã¯ãšã¹ãå
ã§ã®ã¿ä¿æãããŸãã éããªã¯ãšã¹ãã®éã§ã¯å
±æãããŸããã
## var
`c.var` ã䜿çšããŠãå€æ°ã®å€ã«ã¢ã¯ã»ã¹ã§ããŸãã
```ts twoslash
import type { Context } from 'hono'
declare const c: Context
// ---cut---
const result = c.var.client.oneMethod()
```
ã«ã¹ã¿ã ã¡ãœãããæäŸããããã«ãŠã§ã¢ãäœæãããå Žåã¯ã
ãã®ããã«æžããŸã:
```ts twoslash
import { Hono } from 'hono'
import { createMiddleware } from 'hono/factory'
// ---cut---
type Env = {
Variables: {
echo: (str: string) => string
}
}
const app = new Hono()
const echoMiddleware = createMiddleware(async (c, next) => {
c.set('echo', (str) => str)
await next()
})
app.get('/echo', echoMiddleware, (c) => {
return c.text(c.var.echo('Hello!'))
})
```
è€æ°ã®ãã³ãã©ã§ããã«ãŠã§ã¢ã䜿ãããå Žåã `app.use()` ã䜿ããŸãã
次ã«ã `Env` ãžã§ããªã¯ã¹ã `Hono` ã³ã³ã¹ãã©ã¯ã¿ã«æž¡ããŠåå®å
šã«ããã¹ãã§ãã
```ts twoslash
import { Hono } from 'hono'
import type { MiddlewareHandler } from 'hono/types'
declare const echoMiddleware: MiddlewareHandler
type Env = {
Variables: {
echo: (str: string) => string
}
}
// ---cut---
const app = new Hono()
app.use(echoMiddleware)
app.get('/echo', (c) => {
return c.text(c.var.echo('Hello!'))
})
```
## render() / setRenderer()
ã«ã¹ã¿ã ããã«ãŠã§ã¢å
㧠`c.setRenderer()` ã䜿çšããŠã¬ã€ã¢ãŠããèšå®ã§ããŸãã
```tsx twoslash
/** @jsx jsx */
/** @jsxImportSource hono/jsx */
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.use(async (c, next) => {
c.setRenderer((content) => {
return c.html(
{content}
)
})
await next()
})
```
次ã«ã `c.render()` ã䜿çšããŠãã®ã¬ã€ã¢ãŠãã§ã¬ã¹ãã³ã¹ãäœæããŸãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/', (c) => {
return c.render('Hello!')
})
```
ãã®ãããªåºåã«ãªããŸã:
```html
Hello!
```
ãŸãããã®æ©èœã¯æè»ã«åŒæ°ãèšå®ããããšãã§ããŸãã
åå®å
šã®ããã«ãåã次ã®ããã«èšå®ã§ããŸã:
```ts
declare module 'hono' {
interface ContextRenderer {
(
content: string | Promise,
head: { title: string }
): Response | Promise
}
}
```
䜿çšäŸã以äžã«ç€ºããŸã:
```ts
app.use('/pages/*', async (c, next) => {
c.setRenderer((content, head) => {
return c.html(
{head.title}
{content}
)
})
await next()
})
app.get('/pages/my-favorite', (c) => {
return c.render(Ramen and Sushi
, {
title: 'My favorite',
})
})
app.get('/pages/my-hobbies', (c) => {
return c.render(Watching baseball
, {
title: 'My hobbies',
})
})
```
## executionCtx
```ts twoslash
import { Hono } from 'hono'
const app = new Hono<{
Bindings: {
KV: any
}
}>()
declare const key: string
declare const data: string
// ---cut---
// ExecutionContext object
app.get('/foo', async (c) => {
c.executionCtx.waitUntil(c.env.KV.put(key, data))
// ...
})
```
## event
```ts twoslash
import { Hono } from 'hono'
declare const key: string
declare const data: string
type KVNamespace = any
// ---cut---
// Type definition to make type inference
type Bindings = {
MY_KV: KVNamespace
}
const app = new Hono<{ Bindings: Bindings }>()
// FetchEvent object (only set when using Service Worker syntax)
app.get('/foo', async (c) => {
c.event.waitUntil(c.env.MY_KV.put(key, data))
// ...
})
```
## env
Cloudflare Workers ã®ç°å¢å€æ°ãã·ãŒã¯ã¬ããã KV ããŒã ã¹ããŒã¹ã D1 ããŒã¿ããŒã¹ã R2 ãã±ããç... ããã€ã³ãã£ã³ã°ãšåŒã³ãŸãã
çš®é¡ã«é¢ä¿ãªãããã€ã³ãã£ã³ã°ã¯åžžã«ã°ããŒãã«å€æ°ãšããŠå©çšã§ãã `c.env.BINDING_KEY` ããã¢ã¯ã»ã¹ã§ããŸãã
```ts twoslash
import { Hono } from 'hono'
type KVNamespace = any
// ---cut---
// Type definition to make type inference
type Bindings = {
MY_KV: KVNamespace
}
const app = new Hono<{ Bindings: Bindings }>()
// Environment object for Cloudflare Workers
app.get('/', async (c) => {
c.env.MY_KV.get('my-key')
// ...
})
```
## error
ãã³ãã©ã§ãšã©ãŒãçºçããå Žåããšã©ãŒãªããžã§ã¯ã㯠`c.error` ã«æ ŒçŽãããŸãã
ããã«ãŠã§ã¢ããã¢ã¯ã»ã¹ã§ããŸãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.use(async (c, next) => {
await next()
if (c.error) {
// do something...
}
})
```
## ContextVariableMap
äŸãã°ãç¹å®ã®ããã«ãŠã§ã¢ã䜿ããšãã«å€æ°ãžåå®çŸ©ãè¿œå ãããå Žåããã®ããã« `ContextVariableMap` ã䜿çšã§ããŸã:
```ts
declare module 'hono' {
interface ContextVariableMap {
result: string
}
}
```
ãããããã«ãŠã§ã¢ã§å©çšã§ããŸã:
```ts twoslash
import { createMiddleware } from 'hono/factory'
// ---cut---
const mw = createMiddleware(async (c, next) => {
c.set('result', 'some values') // result is a string
await next()
})
```
ãã³ãã©ã§ãå€æ°ã¯é©åãªåãæšè«ãããŸã:
```ts twoslash
import { Hono } from 'hono'
const app = new Hono<{ Variables: { result: string } }>()
// ---cut---
app.get('/', (c) => {
const val = c.get('result') // val is a string
// ...
return c.json({ result: val })
})
```
# äŸå€
èªèšŒã«å€±æãããªã©ãèŽåœçãªãšã©ãŒãçºçããå Žå㯠HTTPException ãæããå¿
èŠããããŸãã
## throw HTTPException
HTTPException ãããã«ãŠã§ã¢ããæããäŸã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
declare const authorized: boolean
// ---cut---
import { HTTPException } from 'hono/http-exception'
// ...
app.post('/auth', async (c, next) => {
// authentication
if (authorized === false) {
throw new HTTPException(401, { message: 'Custom error message' })
}
await next()
})
```
ãŠãŒã¶ãŒã«è¿ãã¬ã¹ãã³ã¹ãæå®ã§ããŸãã
```ts twoslash
import { HTTPException } from 'hono/http-exception'
const errorResponse = new Response('Unauthorized', {
status: 401,
headers: {
Authenticate: 'error="invalid_token"',
},
})
throw new HTTPException(401, { res: errorResponse })
```
## HTTPException ã®ãã³ãã«
æãããã HTTPException ã«ã¯ `app.onError` ã§åŠçã§ããŸãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
import { HTTPException } from 'hono/http-exception'
// ...
app.onError((err, c) => {
if (err instanceof HTTPException) {
// Get the custom response
return err.getResponse()
}
// ...
// ---cut-start---
return c.text('Error')
// ---cut-end---
})
```
## `cause`
`cause` ãªãã·ã§ã³ã¯ [`cause`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) ã®ããŒã¿ãè¿œå ããããã«äœ¿ããŸãã
```ts twoslash
import { Hono, Context } from 'hono'
import { HTTPException } from 'hono/http-exception'
const app = new Hono()
declare const message: string
declare const authorize: (c: Context) => void
// ---cut---
app.post('/auth', async (c, next) => {
try {
authorize(c)
} catch (e) {
throw new HTTPException(401, { message, cause: e })
}
await next()
})
```
# App - Hono
`Hono` ã¯äžå¿çãªãªããžã§ã¯ãã§ãã
æåã«ã€ã³ããŒããããæåŸãŸã§äœ¿çšãããŸãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
//...
export default app // for Cloudflare Workers or Bun
```
## ã¡ãœãã
`Hono` ã®ã€ã³ã¹ã¿ã³ã¹ã«ã¯ä»¥äžã®ã¡ãœããããããŸãã
- app.**HTTP_METHOD**(\[path,\]handler|middleware...)
- app.**all**(\[path,\]handler|middleware...)
- app.**on**(method|method[], path|path[], handler|middleware...)
- app.**use**(\[path,\]middleware)
- app.**route**(path, \[app\])
- app.**basePath**(path)
- app.**notFound**(handler)
- app.**onError**(err, handler)
- app.**mount**(path, anotherApp)
- app.**fire**()
- app.**fetch**(request, env, event)
- app.**request**(path, options)
ãããã®ååã®éšåã¯ã«ãŒãã£ã³ã°ã§äœ¿çšãããŸãã [ã«ãŒãã£ã³ã°](/docs/api/routing)ãèªãã§ãã ããã
## Not Found
`app.notFound` ã䜿çšã㊠Not Found ã¬ã¹ãã³ã¹ãã«ã¹ã¿ãã€ãºã§ããŸãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.notFound((c) => {
return c.text('Custom 404 Message', 404)
})
```
## ãšã©ãŒãã³ããªã³ã°
`app.onError` ã¯ãšã©ãŒããã³ãã«ããã«ã¹ã¿ãã€ãºããã¬ã¹ãã³ã¹ãè¿ãããšãåºæ¥ãŸãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.onError((err, c) => {
console.error(`${err}`)
return c.text('Custom Error Message', 500)
})
```
## fire()
`app.fire()` ã¯èªå㧠`fetch` ã€ãã³ããªã¹ããŒãè¿œå ããŸãã
ãã㯠[Service Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) ã䜿çšããŠãã [non-ES module Cloudflare Workers](https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/) ã®ãããªç°å¢ã§äžæãæ©èœããŸãã
`app.fire()` ã¯ããªãã®ä»£ããã«ä»¥äžã®äœæ¥ãè¡ããŸã:
```ts
addEventListener('fetch', (event: FetchEventLike): void => {
event.respondWith(this.dispatch(...))
})
```
## fetch()
`app.fetch` ã¯ã¢ããªã±ãŒã·ã§ã³ã®ãšã³ããªãã€ã³ãã§ãã
Cloudflare Workers ã§ã¯ãã®ããã«äœ¿çšã§ããŸã:
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
type Env = any
type ExecutionContext = any
// ---cut---
export default {
fetch(request: Request, env: Env, ctx: ExecutionContext) {
return app.fetch(request, env, ctx)
},
}
```
ãããã¯:
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
export default app
```
Bun:
```ts
export default app // [!code --]
export default { // [!code ++]
port: 3000, // [!code ++]
fetch: app.fetch, // [!code ++]
} // [!code ++]
```
## request()
`request` ã¯ãã¹ãã«äŸ¿å©ãªã¡ãœããã§ãã
URL ããã¹ãæž¡ã㊠GET ãªã¯ãšã¹ããéä¿¡ããŸãã
`app` 㯠`Response` ãªããžã§ã¯ããè¿ããŸãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
declare const test: (name: string, fn: () => void) => void
declare const expect: (value: any) => any
// ---cut---
test('GET /hello is ok', async () => {
const res = await app.request('/hello')
expect(res.status).toBe(200)
})
```
ãŸãã `Request` ãªããžã§ã¯ããæž¡ãããšãåºæ¥ãŸã:
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
declare const test: (name: string, fn: () => void) => void
declare const expect: (value: any) => any
// ---cut---
test('POST /message is ok', async () => {
const req = new Request('Hello!', {
method: 'POST',
})
const res = await app.request(req)
expect(res.status).toBe(201)
})
```
## mount()
`mount()` ã¯ä»ã®ãã¬ãŒã ã¯ãŒã¯ã§æžãããã¢ããªã±ãŒã·ã§ã³ã Hono ã®ã¢ããªã±ãŒã·ã§ã³ã«ããŠã³ãã§ããŸãã
```ts
import { Router as IttyRouter } from 'itty-router'
import { Hono } from 'hono'
// Create itty-router application
const ittyRouter = IttyRouter()
// Handle `GET /itty-router/hello`
ittyRouter.get('/hello', () => new Response('Hello from itty-router'))
// Hono application
const app = new Hono()
// Mount!
app.mount('/itty-router', ittyRouter.handle)
```
## strict mode
strict mode ã¯ããã©ã«ã㧠`true` ã§ã以äžã®ã«ãŒããåºå¥ãããŸãã
- `/hello`
- `/hello/`
`app.get('/hello')` 㯠`GET /hello/` ã«ãããããŸããã
strict mode ã `false` ã«èšå®ããå Žåã2ã€ã®ã«ãŒãã¯çãããªããŸãã
```ts twoslash
import { Hono } from 'hono'
// ---cut---
const app = new Hono({ strict: false })
```
## ã«ãŒã¿ãŒãªãã·ã§ã³
`router` ãªãã·ã§ã³ã¯ã©ã®ã«ãŒã¿ãŒã䜿ããæå®ã§ããŸãã ããã©ã«ãã§ã¯ `SmartRouter` ã䜿ãããŸãã `RegExpRouter` ã䜿ãããå Žåããã®ããã« `Hono` ã®ã€ã³ã¹ã¿ã³ã¹ãçæããŠãã ããã
```ts twoslash
import { Hono } from 'hono'
// ---cut---
import { RegExpRouter } from 'hono/router/reg-exp-router'
const app = new Hono({ router: new RegExpRouter() })
```
## ãžã§ããªã¯ã¹
ãžã§ããªã¯ã¹ã䜿çšããŠã `c.set` / `c.get` ã§äœ¿çšããã Cloudflare Workers ãã€ã³ãã£ã³ã°ãšå€æ°ãè¿œå ããŸãã
```ts twoslash
import { Hono } from 'hono'
type User = any
declare const user: User
// ---cut---
type Bindings = {
TOKEN: string
}
type Variables = {
user: User
}
const app = new Hono<{
Bindings: Bindings
Variables: Variables
}>()
app.use('/auth/*', async (c, next) => {
const token = c.env.TOKEN // token is `string`
// ...
c.set('user', user) // user should be `User`
await next()
})
```
# API
Hono ã® API ã¯ã·ã³ãã«ã§ãã
Web Standard API ãæ¡åŒµãããªããžã§ã¯ãã§æ§æãããŠããŸãã
ãã®ãããç°¡åã«ç解ã§ããŸãã
ãã®ç« ã§ã¯ä»¥äžã®ãã㪠Hono ã® API ã説æããŸãã
- Hono ãªããžã§ã¯ã
- ã«ãŒãã£ã³ã°
- Context ãªããžã§ã¯ã
- ããã«ãŠã§ã¢
# ããªã»ãã
Hono ã«ã¯ããã€ãã®ã«ãŒã¿ãŒãããããããããç¹å®ã®ç®çã®ããã«äœãããŠããŸãã
Hono ã®ã³ã³ã¹ãã©ã¯ã¿ã§äœ¿çšããã«ãŒã¿ãŒãæå®ã§ããŸãã
**ããªã»ãã** ã¯äžè¬çãªä»æ§ã±ãŒã¹ã«åãããŠæäŸããããããæ¯åã«ãŒã¿ãŒãæå®ããå¿
èŠã¯ãããŸããã
ãã¹ãŠã®ããªã»ããããã€ã³ããŒãããã `Hono` ã¯ã©ã¹ã¯åããã®ã§ãããã«ãŒã¿ãŒã ããéããŸãã
ãã®ããã亀æãå¯èœã§ãã
## `hono`
䜿ãæ¹:
```ts twoslash
import { Hono } from 'hono'
```
ã«ãŒã¿ãŒ:
```ts
this.router = new SmartRouter({
routers: [new RegExpRouter(), new TrieRouter()],
})
```
## `hono/quick`
䜿ãæ¹ :
```ts twoslash
import { Hono } from 'hono/quick'
```
ã«ãŒã¿ãŒ:
```ts
this.router = new SmartRouter({
routers: [new LinearRouter(), new TrieRouter()],
})
```
## `hono/tiny`
䜿ãæ¹:
```ts twoslash
import { Hono } from 'hono/tiny'
```
ã«ãŒã¿ãŒ:
```ts
this.router = new PatternRouter()
```
## ã©ã®ããªã»ããã䜿ãã¹ãã§ãã?
| ããªã»ãã | é©åãªãã©ãããã©ãŒã |
| ------------ | -------------------------------- |
| `hono` | ã»ãšãã©ã®ãŠãŒã¹ã±ãŒã¹ã§ç¹ã«ãããããããŸãã ã«ãŒãç»é²ã¯ `hono/quick` ããé
ããããããŸããããèµ·åããŠããŸãã°é«éã§ãã **Deno** ã **Bun** ã **Node.js** ã§æ§ç¯ãããåžžæèµ·åãµãŒããŒã«æé©ã§ãã V8 Isolates ã䜿çšããã **Cloudflare Workers** ã **Deno Deploy** ã«ãé©ããŠããŸãã åé¢ç°å¢ã¯èµ·ååŸãäžå®æéæç¶ããããã§ãã |
| `hono/quick` | ãªã¯ãšã¹ãããšã«ã¢ããªã±ãŒã·ã§ã³ãåæåãããç°å¢åãã«èšèšãããŠããŸãã **Fastly Compute** ã¯ãã®ããã«åäœããããããã®ããªã»ãããé©ããŠããŸãã |
| `hono/tiny` | æãå°ããã«ãŒã¿ãŒããã±ãŒãžã§ããªãœãŒã¹ãéãããŠããç°å¢ã«é©ããŠããŸãã |
# HonoRequest
`HonoRequest` 㯠[Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) ãã©ãããããªããžã§ã¯ãã§ã `c.req` ããã¢ã¯ã»ã¹ã§ããŸãã
## param()
ãã¹ãã©ã¡ãŒã¿ã®å€ãååŸããŸãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
// Captured params
app.get('/entry/:id', async (c) => {
const id = c.req.param('id')
// ^?
// ...
})
// Get all params at once
app.get('/entry/:id/comment/:commentId', async (c) => {
const { id, commentId } = c.req.param()
// ^?
})
```
## query()
ã¯ãšãªãã©ã¡ãŒã¿ãååŸããŸãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
// Query params
app.get('/search', async (c) => {
const query = c.req.query('q')
// ^?
})
// Get all params at once
app.get('/search', async (c) => {
const { q, limit, offset } = c.req.query()
// ^?
})
```
## queries()
è€æ°ã®ã¯ãšãªãã©ã¡ãŒã¿ãååŸããŸãã äŸ: `/search?tags=A&tags=B`
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/search', async (c) => {
// tags will be string[]
const tags = c.req.queries('tags')
// ^?
// ...
})
```
## header()
ãªã¯ãšã¹ãã®ããããååŸããŸãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/', (c) => {
const userAgent = c.req.header('User-Agent')
// ^?
return c.text(`Your user agent is ${userAgent}`)
})
```
::: warning
åŒæ°ç¡ã㧠`c.req.header()` ãåŒã°ããå Žåããã¹ãŠã®ã¬ã³ãŒãã®ããŒã¯ **å°æå** ã«ãªã£ãŠè¿ãããŸãã
倧æåã䜿ã£ããããåã§å€ãååŸãããå Žåã¯
`c.req.header(âX-Fooâ)` ã®ããã«äœ¿ããŸãã
```ts
// â Will not work
const headerRecord = c.req.header()
const foo = headerRecord['X-Foo']
// â
Will work
const foo = c.req.header('X-Foo')
```
:::
## parseBody()
`multipart/form-data` ãŸã㯠`application/x-www-form-urlencoded` ã®ãªã¯ãšã¹ãããã£ãããŒã¹ããŸãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.post('/entry', async (c) => {
const body = await c.req.parseBody()
// ...
})
```
`parseBody()` ã¯æ¬¡ã®åäœããµããŒãããŸãã
**åäžãã¡ã€ã«**
```ts twoslash
import { Context } from 'hono'
declare const c: Context
// ---cut---
const body = await c.req.parseBody()
const data = body['foo']
// ^?
```
`body['foo']` is `(string | File)`.
è€æ°ã®ãã¡ã€ã«ãã¢ããããŒããããå ŽåãæåŸã®ãã¡ã€ã«ãååŸãããŸãã
### è€æ°ãã¡ã€ã«
```ts twoslash
import { Context } from 'hono'
declare const c: Context
// ---cut---
const body = await c.req.parseBody()
body['foo[]']
```
`body['foo[]']` ã¯åžžã« `(string | File)[]` ã§ãã
`[]` ãã¹ããã£ãã¯ã¹ãå¿
èŠã§ãã
### åãååã®è€æ°ãã¡ã€ã«
```ts twoslash
import { Context } from 'hono'
declare const c: Context
// ---cut---
const body = await c.req.parseBody({ all: true })
body['foo']
```
`all` ãªãã·ã§ã³ã¯ããã©ã«ãã§ç¡å¹ã§ãã
- `body['foo']` ãè€æ°ãã¡ã€ã«ã ã£ãå Žåã `(string | File)[]` ã«ããŒã¹ãããŸãã
- `body['foo']` ãåäžãã¡ã€ã«ã ã£ãå Žåã `(string | File)` ã«ããŒã¹ãããŸãã
### ãããè¡šèš
`dot` ãªãã·ã§ã³ã `true` ã«ããå Žåãæ»ãå€ã¯ãããè¡šèšåºã¥ããŠæ§é åãããŸãã
ãã®ãããªããŒã¿ãåãåãããšãèããŠãã ãã:
```ts twoslash
const data = new FormData()
data.append('obj.key1', 'value1')
data.append('obj.key2', 'value2')
```
`dot` ãªãã·ã§ã³ã `true` ã«ããããšã§æ§é åãããå€ãååŸããããšãã§ããŸã:
```ts twoslash
import { Context } from 'hono'
declare const c: Context
// ---cut---
const body = await c.req.parseBody({ dot: true })
// body is `{ obj: { key1: 'value1', key2: 'value2' } }`
```
## json()
`application/json` ã®ãªã¯ãšã¹ãããã£ãããŒã¹ããŸãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.post('/entry', async (c) => {
const body = await c.req.json()
// ...
})
```
## text()
`text/plain` ã®ãªã¯ãšã¹ãããã£ãããŒã¹ããŸãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.post('/entry', async (c) => {
const body = await c.req.text()
// ...
})
```
## arrayBuffer()
ãªã¯ãšã¹ãããã£ã `ArrayBuffer` ãšããŠããŒã¹ããŸãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.post('/entry', async (c) => {
const body = await c.req.arrayBuffer()
// ...
})
```
## blob()
ãªã¯ãšã¹ãããã£ã `Blob` ãšããŠããŒã¹ããŸãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.post('/entry', async (c) => {
const body = await c.req.blob()
// ...
})
```
## formData()
ãªã¯ãšã¹ãããã£ã `FormData` ãšããŠããŒã¹ããŸãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.post('/entry', async (c) => {
const body = await c.req.formData()
// ...
})
```
## valid()
ããªããŒã·ã§ã³ãããããŒã¿ãååŸããŸãã
```ts
app.post('/posts', async (c) => {
const { title, body } = c.req.valid('form')
// ...
})
```
ãããã®ããŒã¿ã«å¯ŸããŠå©çšå¯èœã§ãã
- `form`
- `json`
- `query`
- `header`
- `cookie`
- `param`
[ããªããŒã·ã§ã³ã»ã¯ã·ã§ã³](/docs/guides/validation)ã§å©çšäŸãèŠãŠãã ããã
## routePath()
ãã³ãã©å
ã§å®çŸ©ããããã¹ããã®ããã«ååŸã§ããŸã:
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/posts/:id', (c) => {
return c.json({ path: c.req.routePath })
})
```
`/posts/123` ã«ã¢ã¯ã»ã¹ãããšãã `/posts/:id` ãè¿ãããŸã:
```json
{ "path": "/posts/:id" }
```
## matchedRoutes()
ãã³ãã©ã§äžèŽããã«ãŒããè¿ããŸãããããã°ã«é©ããŠããŸãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.use(async function logger(c, next) {
await next()
c.req.matchedRoutes.forEach(({ handler, method, path }, i) => {
const name =
handler.name ||
(handler.length < 2 ? '[handler]' : '[middleware]')
console.log(
method,
' ',
path,
' '.repeat(Math.max(10 - path.length, 0)),
name,
i === c.req.routeIndex ? '<- respond from here' : ''
)
})
})
```
## path
ãªã¯ãšã¹ãã®ãã¹ã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/about/me', async (c) => {
const pathname = c.req.path // `/about/me`
// ...
})
```
## url
ãªã¯ãšã¹ãã® URL æååã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/about/me', async (c) => {
const url = c.req.url // `http://localhost:8787/about/me`
// ...
})
```
## method
ãªã¯ãšã¹ãã® HTTP ã¡ãœããåã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/about/me', async (c) => {
const method = c.req.method // `GET`
// ...
})
```
## raw
çã® [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) ãªããžã§ã¯ãã
```ts
// For Cloudflare Workers
app.post('/', async (c) => {
const metadata = c.req.raw.cf?.hostMetadata?
// ...
})
```
# ã«ãŒãã£ã³ã°
Hono ã®ã«ãŒãã£ã³ã°ã¯æè»ã§çŽæçã§ãã
ã§ã¯èŠãŠã¿ãŸãããã
## åºæ¬
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
// HTTP Methods
app.get('/', (c) => c.text('GET /'))
app.post('/', (c) => c.text('POST /'))
app.put('/', (c) => c.text('PUT /'))
app.delete('/', (c) => c.text('DELETE /'))
// Wildcard
app.get('/wild/*/card', (c) => {
return c.text('GET /wild/*/card')
})
// Any HTTP methods
app.all('/hello', (c) => c.text('Any Method /hello'))
// Custom HTTP method
app.on('PURGE', '/cache', (c) => c.text('PURGE Method /cache'))
// Multiple Method
app.on(['PUT', 'DELETE'], '/post', (c) =>
c.text('PUT or DELETE /post')
)
// Multiple Paths
app.on('GET', ['/hello', '/ja/hello', '/en/hello'], (c) =>
c.text('Hello')
)
```
## ãã¹ãã©ã¡ãŒã¿
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/user/:name', async (c) => {
const name = c.req.param('name')
// ^?
// ...
})
```
ãŸãã¯äžåºŠã«å€ãã®ãã©ã¡ãŒã¿ã䜿çšã§ããŸã:
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/posts/:id/comment/:comment_id', async (c) => {
const { id, comment_id } = c.req.param()
// ^?
// ...
})
```
## ãªãã·ã§ã³ã®ãã©ã¡ãŒã¿
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
// Will match `/api/animal` and `/api/animal/:type`
app.get('/api/animal/:type?', (c) => c.text('Animal!'))
```
## æ£èŠè¡šçŸ
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/post/:date{[0-9]+}/:title{[a-z]+}', async (c) => {
const { date, title } = c.req.param()
// ^?
// ...
})
```
## ã¹ã©ãã·ã¥ãå«ã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/posts/:filename{.+\\.png}', async (c) => {
//...
})
```
## ã¡ãœãããã§ãŒã³ã§ã«ãŒãã£ã³ã°ãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app
.get('/endpoint', (c) => {
return c.text('GET /endpoint')
})
.post((c) => {
return c.text('POST /endpoint')
})
.delete((c) => {
return c.text('DELETE /endpoint')
})
```
## ã°ã«ãŒãå
Hono ã€ã³ã¹ã¿ã³ã¹ã䜿çšããŠã«ãŒããã°ã«ãŒãåããã¡ã€ã³ã®ã¢ããªã±ãŒã·ã§ã³ã§ route ã¡ãœããã䜿çšããŠè¿œå ããŸãã
```ts twoslash
import { Hono } from 'hono'
// ---cut---
const book = new Hono()
book.get('/', (c) => c.text('List Books')) // GET /book
book.get('/:id', (c) => {
// GET /book/:id
const id = c.req.param('id')
return c.text('Get Book: ' + id)
})
book.post('/', (c) => c.text('Create Book')) // POST /book
const app = new Hono()
app.route('/book', book)
```
## ããŒã¹ãã¹ãå€æŽããã«ã°ã«ãŒãåãã
ããŒã¹ãã¹ããã®ãŸãŸã«è€æ°ã®ã€ã³ã¹ã¿ã³ã¹ãã°ã«ãŒãåããããšãã§ããŸãã
```ts twoslash
import { Hono } from 'hono'
// ---cut---
const book = new Hono()
book.get('/book', (c) => c.text('List Books')) // GET /book
book.post('/book', (c) => c.text('Create Book')) // POST /book
const user = new Hono().basePath('/user')
user.get('/', (c) => c.text('List Users')) // GET /user
user.post('/', (c) => c.text('Create User')) // POST /user
const app = new Hono()
app.route('/', book) // Handle /book
app.route('/', user) // Handle /user
```
## ããŒã¹ãã¹
ããŒã¹ãã¹ãæå®ã§ããŸãã
```ts twoslash
import { Hono } from 'hono'
// ---cut---
const api = new Hono().basePath('/api')
api.get('/book', (c) => c.text('List Books')) // GET /api/book
```
## ãã¹ãåã§ã«ãŒãã£ã³ã°ãã
ãã¹ãåãã«ãŒãã«å«ããŠãåäœããŸãã
```ts twoslash
import { Hono } from 'hono'
// ---cut---
const app = new Hono({
getPath: (req) => req.url.replace(/^https?:\/([^?]+).*$/, '$1'),
})
app.get('/www1.example.com/hello', (c) => c.text('hello www1'))
app.get('/www2.example.com/hello', (c) => c.text('hello www2'))
```
## `host` ãããã䜿çšããã«ãŒãã£ã³ã°
Hono ã³ã³ã¹ãã©ã¯ã¿ã« `getPath()` é¢æ°ãå®è£
ãããš `host` ãããã®å€ãåŠçã§ããŸãã
```ts twoslash
import { Hono } from 'hono'
// ---cut---
const app = new Hono({
getPath: (req) =>
'/' +
req.headers.get('host') +
req.url.replace(/^https?:\/\/[^/]+(\/[^?]*)/, '$1'),
})
app.get('/www1.example.com/hello', (c) => c.text('hello www1'))
// A following request will match the route:
// new Request('http://www1.example.com/hello', {
// headers: { host: 'www1.example.com' },
// })
```
ãããå¿çšããã°ã `User-Agent` ãããã§ã«ãŒãã£ã³ã°ãå€æŽãããããªããšãåºæ¥ãŸãã
## ã«ãŒãã£ã³ã°ã®åªå
é åº
ãã³ãã©ãããã«ãŠã§ã¢ã¯ç»é²é ã«å®è¡ãããŸãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/book/a', (c) => c.text('a')) // a
app.get('/book/:slug', (c) => c.text('common')) // common
```
```
GET /book/a ---> `a`
GET /book/b ---> `common`
```
ãã³ãã©ãå®è¡ããããšåŠçãåæ¢ããŸãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('*', (c) => c.text('common')) // common
app.get('/foo', (c) => c.text('foo')) // foo
```
```
GET /foo ---> `common` // foo will not be dispatched
```
å®è¡ãããããã«ãŠã§ã¢ãããå Žåã¯ããã³ãã©ãããåã«èšè¿°ããŸãã
```ts twoslash
import { Hono } from 'hono'
import { logger } from 'hono/logger'
const app = new Hono()
// ---cut---
app.use(logger())
app.get('/foo', (c) => c.text('foo'))
```
â_ãã©ãŒã«ããã¯_" ãã³ãã©ãå¿
èŠãªå Žåã¯ãä»ã®ãã³ãã©ã®äžã«ã³ãŒããæžããŸãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/bar', (c) => c.text('bar')) // bar
app.get('*', (c) => c.text('fallback')) // fallback
```
```
GET /bar ---> `bar`
GET /foo ---> `fallback`
```
## ã°ã«ãŒãã®é åº
ã«ãŒãã£ã³ã°ã®ã°ã«ãŒãåã®ééãã¯æ°ãä»ãã«ããã®ã§æ°ãã€ããŠãã ããã
`rooute()` é¢æ°ã¯2çªç®ã®åŒæ° ( `three` ã `two` ã®ãããª) ããä¿åãããã«ãŒãã£ã³ã°ãååŸããèªåèªèº« ( `two` ã `app` ) ã®ã«ãŒãã«è¿œå ããŸãã
```ts
three.get('/hi', (c) => c.text('hi'))
two.route('/three', three)
app.route('/two', two)
export default app
```
ãã㯠200 ã¬ã¹ãã³ã¹ãè¿ããŸããã
```
GET /two/three/hi ---> `hi`
```
é åºãééã£ãŠããå Žå㯠404 ãè¿ããŸãã
```ts twoslash
import { Hono } from 'hono'
const app = new Hono()
const two = new Hono()
const three = new Hono()
// ---cut---
three.get('/hi', (c) => c.text('hi'))
app.route('/two', two) // `two` does not have routes
two.route('/three', three)
export default app
```
```
GET /two/three/hi ---> 404 Not Found
```