HonoとReactアプリを連携させよう
実際にHono (バックエンド) とReact (フロントエンド) を連携させてみましょう。2つの独立したプロジェクトを作成し、APIを通じてデータをやり取りします。
この演習の流れ
- Step 1: Honoでバックエンドを作成する
- Step 2: Reactでフロントエンドを作成する
- Step 3: CORSエラーを解決する
すべて完了すると、「React画面からHono APIを呼び出してデータを表示する」アプリが動きます。
作業ディレクトリの準備
まず、今回の演習用のディレクトリを作成します。
mkdir hono-react-demo
cd hono-react-demo
この中に api (バックエンド) と web (フロントエンド) の2つのプロジェクトを作ります。
hono-react-demo/
├── api/ # Hono (バックエンド)
└── web/ # React (フロントエンド)
Step 1: Honoでバックエンドを作成する
まず、APIサーバーを作ります。
1-1. プロジェクト作成
pnpm create hono@latest api
対話形式で質問されます。以下のように選択してください。
Which template do you want to use? … nodejs
Do you want to install project dependencies? … yes
Which package manager do you want to use? … pnpm
1-2. APIエンドポイントを作成
作成されたディレクトリに移動して、コードを編集します。
cd api
src/index.ts を以下のように書き換えます。
import { serve } from "@hono/node-server";
import { Hono } from "hono";
const app = new Hono();
// /api/hello エンドポイント
app.get("/api/hello", (c) => {
return c.json({
message: "Hello from Hono!",
timestamp: new Date().toISOString(),
});
});
const port = 3000;
console.log(`API Server is running on http://localhost:${port}`);
serve({
fetch: app.fetch,
port,
});
1-3. 動作確認
開発サーバーを起動します。
pnpm dev
ブラウザで http://localhost:3000/api/hello にアクセスしてみましょう。
{
"message": "Hello from Hono!",
"timestamp": "2025-01-20T12:00:00.000Z"
}
このようなJSONが表示されれば成功です。このターミナルは開いたままにしておいてください。
Step 2: Reactでフロントエンドを作成する
次に、Reactアプリを作ります。新しいターミナルを開いてください。
2-1. プロジェクト作成
hono-react-demo ディレクトリに戻ります。
cd /path/to/hono-react-demo
Reactプロジェクトを作成します。
pnpm create vite@latest web --template react-ts
2-2. 依存関係のインストール
cd web
pnpm install
2-3. APIを呼び出すコードを書く
src/App.tsx を以下のように書き換えます。
import { useEffect, useState } from "react";
// APIレスポンスの型定義
type HelloResponse = {
message: string;
timestamp: string;
};
function App() {
const [data, setData] = useState<HelloResponse | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
// Hono APIを呼び出す
fetch("http://localhost:3000/api/hello")
.then((res) => res.json())
.then((json) => setData(json))
.catch((err) => setError(err.message));
}, []);
return (
<div style={{ padding: "2rem", fontFamily: "sans-serif" }}>
<h1>Hono + React Demo</h1>
{error && (
<div style={{ color: "red", marginBottom: "1rem" }}>
<strong>Error:</strong> {error}
</div>
)}
{data ? (
<div>
<p>
<strong>Message:</strong> {data.message}
</p>
<p>
<strong>Timestamp:</strong> {data.timestamp}
</p>
</div>
) : (
<p>Loading...</p>
)}
</div>
);
}
export default App;
2-4. 開発サーバーを起動
pnpm dev
ブラウザで http://localhost:5173 にアクセスします。
2-5. CORSエラーに遭遇!
画面を見ると…エラーが表示されています!
ブラウザの開発者ツール (F12) を開いて、Consoleタブを確認してみましょう。
Access to fetch at 'http://localhost:3000/api/hello' from origin
'http://localhost:5173' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
これがCORSエラーです。ReactとHonoはポート番号が違うため、別のオリジンとして扱われています。ブラウザのセキュリティ機能により、リクエストがブロックされているのです (詳しくは概要を参照) 。
Step 3: CORSエラーを解決する
Honoに cors() ミドルウェアを追加して、Reactからのリクエストを許可しましょう。
3-1. Honoのコードを修正
Honoのターミナルで一度 Ctrl+C でサーバーを停止してください。
api/src/index.ts を以下のように修正します。
import { serve } from "@hono/node-server";
import { Hono } from "hono";
import { cors } from "hono/cors"; // 追加
const app = new Hono();
// CORSミドルウェアを適用
app.use(
"/api/*",
cors({
origin: "http://localhost:5173", // Reactのオリジンを許可
}),
);
// /api/hello エンドポイント
app.get("/api/hello", (c) => {
return c.json({
message: "Hello from Hono!",
timestamp: new Date().toISOString(),
});
});
const port = 3000;
console.log(`API Server is running on http://localhost:${port}`);
serve({
fetch: app.fetch,
port,
});
3-2. 再起動して確認
pnpm dev
ブラウザで http://localhost:5173 をリロードしてみましょう。
Hono + React Demo
Message: Hello from Hono!
Timestamp: 2025-01-20T12:00:00.000Z
データが表示されました!
3-3. 何が変わったの?
開発者ツールの「Network」タブで /api/hello へのリクエストを確認すると、レスポンスヘッダーに以下が追加されています。
Access-Control-Allow-Origin: http://localhost:5173
このヘッダーにより、ブラウザは「このリクエストはサーバーが許可している」と判断し、データを受け取れるようになりました。
cors()ミドルウェアのオプション
cors() ミドルウェアにはいくつかの設定オプションがあります。
複数のオリジンを許可
app.use(
"/api/*",
cors({
origin: ["http://localhost:5173", "http://localhost:3001"],
}),
);
すべてのオリジンを許可 (開発用)
app.use(
"/api/*",
cors({
origin: "*", // 全オリジン許可 (本番では非推奨)
}),
);
注意:
origin: "*"は開発時には便利ですが、本番環境では具体的なオリジンを指定しましょう。
認証情報 (Cookie等) を含める場合
app.use(
"/api/*",
cors({
origin: "http://localhost:5173",
credentials: true, // Cookieなどを許可
}),
);
完成コード
Hono (api/src/index.ts)
import { serve } from "@hono/node-server";
import { Hono } from "hono";
import { cors } from "hono/cors";
const app = new Hono();
app.use(
"/api/*",
cors({
origin: "http://localhost:5173",
}),
);
app.get("/api/hello", (c) => {
return c.json({
message: "Hello from Hono!",
timestamp: new Date().toISOString(),
});
});
const port = 3000;
console.log(`API Server is running on http://localhost:${port}`);
serve({
fetch: app.fetch,
port,
});
React (web/src/App.tsx)
import { useEffect, useState } from "react";
type HelloResponse = {
message: string;
timestamp: string;
};
function App() {
const [data, setData] = useState<HelloResponse | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetch("http://localhost:3000/api/hello")
.then((res) => res.json())
.then((json) => setData(json))
.catch((err) => setError(err.message));
}, []);
return (
<div style={{ padding: "2rem", fontFamily: "sans-serif" }}>
<h1>Hono + React Demo</h1>
{error && (
<div style={{ color: "red", marginBottom: "1rem" }}>
<strong>Error:</strong> {error}
</div>
)}
{data ? (
<div>
<p>
<strong>Message:</strong> {data.message}
</p>
<p>
<strong>Timestamp:</strong> {data.timestamp}
</p>
</div>
) : (
<p>Loading...</p>
)}
</div>
);
}
export default App;
やってみよう!
演習が完了したら、以下を試してみましょう。
1. 別のエンドポイントを追加する
Honoに /api/time エンドポイントを追加して、現在時刻だけを返すようにしてみましょう。
app.get("/api/time", (c) => {
return c.json({
time: new Date().toLocaleTimeString("ja-JP"),
});
});
2. Reactで呼び出す
React側でボタンをクリックしたら /api/time を呼び出すように改修してみましょう。
3. CORSを外してエラーを確認
cors() ミドルウェアをコメントアウトして、再びCORSエラーが発生することを確認してみましょう。
プロジェクトの再現手順 (まとめ)
復習時にすぐ再現できるよう、全手順をまとめておきます。
# 1. 作業ディレクトリ作成
mkdir hono-react-demo && cd hono-react-demo
# 2. Honoプロジェクト作成
pnpm create hono@latest api
# → nodejs, yes, pnpm を選択
# 3. Reactプロジェクト作成
pnpm create vite@latest web --template react-ts
cd web && pnpm install && cd ..
# 4. Honoのコードを編集 (cors()を追加)
# api/src/index.ts を編集
# 5. Reactのコードを編集 (fetch追加)
# web/src/App.tsx を編集
# 6. 両方のサーバーを起動 (別々のターミナルで)
# ターミナル1: cd api && pnpm dev
# ターミナル2: cd web && pnpm dev
# 7. http://localhost:5173 で確認
ポイント
Honoのcors()ミドルウェア
cors(): CORSを有効にするミドルウェアorigin: 許可するオリジンを指定 (文字列または配列)credentials: Cookie等の認証情報を含める場合はtrue"*": 全オリジン許可 (開発用、本番非推奨)
開発時の構成
- バックエンド (Hono): ポート3000で起動
- フロントエンド (React): ポート5173で起動 (Viteのデフォルト)
- 異なるポート = 異なるオリジン → CORSの設定が必要