Hono とは
Hono は、Cloudflare Workers で使える Web アプリ用のフレームワークで、軽量な Web API を実装するときに便利です。 Node.js の Express に似た API を提供しており、ルーティングやミドルウェアを少ないコードで実装することができます。 Cloudflare Workers の Runtime API だけでも Web API を実装できますが、Hono を使うとよりシンプルなコードで実装できます。
Hono で Hello World
Hono プロジェクトの作成
Hono 用のプロジェクトは npm create
で作れるようになっているので、基本的にはこれを使ってサクッと作ります(参考: Hono - Getting Started)。
$ npm create hono@latest my-hono-app # npm の場合
$ pnpm create hono@latest my-hono-app # pnpm の場合
ウィザード形式で想定環境を聞かれるので、Cloudflare Workers で動かしたい場合は次のような感じで答えていきます。
- Which template do you want to use?
cloudflare-workers
- Do you want to install project dependencies?
yes
- Which package manager do you want to use?
pnpm
Cloudflare Workers 用の wrangler
コマンドの設定ファイル (wrangler.toml
) も自動生成されます。
$ tree -L 1
.
├── README.md
├── node_modules
├── package.json
├── pnpm-lock.yaml
├── src
├── tsconfig.json
└── wrangler.toml
開発サーバーで動作確認
プロジェクトを作成したら、開発用サーバーを立ち上げて動作確認します(内部的に wrangler dev
が実行されます)。
$ npm run dev # npm の場合
$ pnpm dev # pnpm の場合
続けて b キーを押すと、Web ブラウザが開きます。
Hello Hono!
と表示されれば成功です。
Cloudflare Workers へデプロイ
動作確認が終わったら Cloudflare Workers にデプロイしてみます(内部的に wrangler deploy --minify
が実行されます)。
$ npm run deploy # npm の場合
$ pnpm run deploy # pnpm の場合(`pnpm deploy` じゃないので注意)
自動的に https://my-hono-app.<ACCOUNT>.workers.dev
といった URL が発行されるので、Web ブラウザでアクセスできれば成功です。
Cloudflare のダッシュボード を開くと、デプロイされていることを確認できます。
必要なくなったら wrangler delete
でサクッと削除できます。
実装例
import { Hono } from "hono";
const app = new Hono();
app.get("/", (c) => {
return c.text("Hello Hono!");
});
export default app;
上記は自動生成された最小限の実装です。 あとはこのコードをベースにして、Hono のドキュメント(例: Examples / API / Guides)を参考にして少しずつ機能を追加していけば OK です。 以下にいくつか実装例を抜粋しておきます。
テキスト/JSONデータを返す
app.get("/text", (c) => {
// 自動でレスポンスヘッダー `Content-Type: text/plain` が付与される
return c.text("Hello");
});
app.get("/json", (c) => {
// 自動でレスポンスヘッダー `Content-Type: application/json` が付与される
return c.json({ ok: true, message: "Hello" });
});
パスパラメーター/クエリパラメーターを取得
// Path params
app.get("/books/:id", (c) => {
const id = c.req.param("id");
return c.text(`Book-${id}`);
});
// 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();
});
POST メソッドでデータを受け取る
app.post("/posts", async (c) => {
// Body で送られてきたデータをパースする(型パラメーターの指定も可能)
const body = await c.req.parseBody();
const title = body.title;
const message = body.message;
if (!title || !message) {
return c.json({ message: "Title and message are required" }, 400);
}
console.log(`Received: ${title}, ${message}`);
return c.json({ message: "Created" }, 201);
});
$ curl -d 'title=Title-1' -d 'message=Message-1' localhost:8787/posts
{"message":"Created"}
$ curl -X POST localhost:8787/posts
{"message":"Title and message are required"}
JSON 形式で送られてきたデータを扱うには次のようにします。
interface PostBody {
title: string;
message: string;
}
app.post("/posts", async (c) => {
const body = await c.req.json<PostBody>();
const title = body.title;
const message = body.message;
// ...
});
$ curl --json '{"title":"Title-1", "message":"Message-1"}' localhost:8787/posts
{"message":"Created"}
外部 API を呼び出す (fetch)
// ポケモンの情報を取得するエンドポイント(例: /pokemon/pikachu)
app.get("/pokemon/:id", async (c) => {
// Get the `id` path parameter and encode it to prevent injection
const id = c.req.param("id");
const safeId = encodeURIComponent(id);
// Call the PokeAPI
const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${safeId}`);
if (!res.ok) {
const errorText = await res.text();
console.log(`Pokemon API error: ${errorText}`);
return c.json({ message: errorText }, 500);
}
const data = (await res.json()) as Record<string, unknown>;
return c.json(data);
});
Header をセットする
c.header("X-Message", "Hello!");
シークレット情報を設定&参照する
Cloudflare Workers では、Bindings という仕組みでシークレット情報(外部 API のキーなど)を参照できるようになっています。
下記は、HOGE_API_KEY
というシークレット情報を定義して、Hono アプリからその値を参照する方法です。
開発環境 (wrangler dev
) で使うシークレット情報は、.dev.vars
というファイルで定義しておきます。
このファイルは .gitignore
に登録されていて Git にコミットされないようになっています。
HOGE_API_KEY="NI6IkpXeiI...省略...leiOiJUzI1"
本番環境 (Cloudflare Workers) にシークレット情報を登録するには、wrangler secret
コマンドを使います。
Cloudflare のダッシュボードでも設定できますが、コマンドを使った方が楽です。
$ wrangler secret put HOGE_API_KEY
Enter a secret value: **********************
シークレット情報は下記のように c.env.変数名
で参照できます。
ここでは、シークレット情報が取得できない場合に 500 エラーを返すようにミドルウェア実装を追加しています。
import { Hono } from "hono";
// 環境変数やシークレットの型定義
type Bindings = {
HOGE_API_KEY: string;
};
const app = new Hono<{ Bindings: Bindings }>();
// 必要な環境変数が設定されていない場合は 500 エラーを返すミドルウェア
app.use(async (c, next) => {
if (!c.env.HOGE_API_KEY) {
console.error("HOGE_API_KEY is not set");
return c.json({ message: "The server setup is not complete" }, 500);
}
await next();
});
// 各ハンドラーの中でシークレットを参照
app.get("/", (c) => {
const apiKey = c.env.HOGE_API_KEY;
const masked = apiKey.substring(0, 4) + "****";
return c.json({ message: `API key: ${masked}` });
});
export default app;
- 参考: https://hono.dev/docs/getting-started/cloudflare-workers#bindings
- 参考: https://developers.cloudflare.com/workers/configuration/secrets/
(アプリ用の)環境変数を設定&参照する
シークレット情報ではない、公開可能な環境変数は wrangler.toml
の中の [vars]
セクションで定義します。
wrangler
コマンドに渡されるシステム環境変数 とは異なることに注意してください。
開発中の PC に設定されているシステム環境変数を Workers のコードから参照することはできません。# ...
[vars]
API_BASE_URL = "https://api.example.com/"
参照方法は、シークレット情報と同じく c.env.変数名
です。
import { Hono } from "hono";
// 環境変数やシークレットの型定義
type Bindings = {
API_BASE_URL: string;
};
const app = new Hono<{ Bindings: Bindings }>();
// 必要な環境変数が設定されていない場合は 500 エラーを返すミドルウェア
app.use(async (c, next) => {
if (!c.env.API_BASE_URL) {
console.error("API_BASE_URL is not set");
return c.json({ message: "The server setup is not complete" }, 500);
}
await next();
});
// 各ハンドラーの中で環境変数を参照
app.get("/", (c) => {
return c.json({ message: `API_BASE_URL: ${c.env.API_BASE_URL}` });
});
export default app;
特定の環境で別の値を使いたい場合は、wrangler.toml
の中で、[env.<環境名>.vars]
というセクションを作って環境変数を定義します。
次の例では、local
という環境用の環境変数を定義しています。
[vars]
API_BASE_URL = "https://api.example.com/"
[env.local.vars]
API_BASE_URL = "http://localhost:3000/"
環境を切り替えて開発サーバーを起動するには、wrangler dev
コマンドに --env <環境名>
オプションをつけて起動します。
npm
や pnpm
のスクリプト経由で起動するときは以下のような感じで起動します。
$ npm run dev -- --env local # npm の場合 (-- を挟むことに注意)
$ pnpm dev --env local # pnpm の場合
開発環境では常に --env local
で起動したいという場合は、次のように package.json
の中でスクリプト定義してしまえばよいでしょう。
{
"name": "my-hono-app",
"scripts": {
"dev": "wrangler dev --env local",
"deploy": "wrangler deploy --minify"
},
// ...
}
その他
- ミドルウェアから route ハンドラーにデータを渡す
- ミドルウェアから 401 エラーを返す
- CORS 対応