【Hono + Cloudflare Workers + Supabase】でAPIを作ってCRUDする
はじめに
本記事では、Hono + Cloudflare Workers + Supabase を組み合わせて、シンプルな CRUD API を構築する方法を紹介します!
Hono と Cloudflare Workers の組み合わせや、Hono と Supabase の組み合わせに関する記事はすでにたくさんあるんですが、Hono + Cloudflare Workers + Supabase の3つを組み合わせた記事はほとんど見つかりませんでした。
実際に業務研修でこれらの技術を使う機会があり、3つを一緒に使う方法を調べるのに結構苦労したので、同じように困っている人の参考になればと思い、この記事を書くことにしました!
記事作成時の環境
- Hono: 4.9.12
- Supabase: 2.75.0
- Cloudflare Workers (Wrangler): 4.43.0
- Node.js: 22.20.0
環境構築
Cloudflare アカウント登録
-
Cloudflare にアクセスし、アカウントを作成します。
GitHub や Google アカウントでサインアップできます。 - ログイン後、ホームに移動します。
- 左メニューから 「Compute & AI」 → 「Workers & Pages」 を選択します。
ここで自分のサーバーレスアプリを管理できるようになります。
このときメールアドレスを認証するよう求められますので、認証してください。
Supabase プロジェクト作成
次に、データベースとして利用する Supabase を設定します。
- Supabase にアクセスし、アカウントを作成します。
- 認証メールの「Confirm Email Address」をクリックします
- 「Create a new organization」画面で情報を入力します。
-
Name:任意(例:
Your name's Org) -
Type:任意(例:
Personal) - Plan:Free
-
Name:任意(例:
- 「Create a new project」画面で新しいプロジェクトを作成します。
-
Organization:先ほど作成した organization(例:
Your name's Org) -
Project name:任意(例:
todo-api) - Database password:任意のパスワード
-
Region:近い地域(例:
Asia-Pacific)
-
Organization:先ほど作成した organization(例:
- プロジェクトが作成されます。
Todo テーブルを作成する
CRUD の対象となる todos テーブルを作成します。
左側メニューから「SQL Editor」を選択し、以下の SQL 文を実行してください。
CREATE TABLE todos (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL
);
SQL 文を入力したら「RUN」ボタンをクリックして実行します。
実行が完了したら、「Table Editor」タブに移動して todos テーブルが正しく作成されていることを確認しましょう。以下のような構造になっているはずです。
| カラム名 | 型 | 制約 |
|---|---|---|
| id | int4 | primary key, auto increment |
| title | text | not null |
これで、Todo データを保存するためのテーブルが完成です。
Supabase の API 情報を確認
- 左側メニューから 「Project Settings」 を開きます。
- 以下の 2 つを控えておきましょう。
-
「Data API」 → Project URL(例:
https://xxxx.supabase.co) - 「API Keys」 → anon public key
-
「Data API」 → Project URL(例:
これらは後ほど Cloudflare Workers から接続する際に使います。
Node.js の確認
最後に、ローカル開発環境を整えます。
Node.js のバージョン確認
まず、Node.js がインストールされているか確認します。
node -v
まだインストールしていない場合は、Node.js 公式サイトからインストールしましょう。
プロジェクト作成
それでは、Hono + Cloudflare Workers + Supabase を使った API プロジェクトを作成していきます。
Hono プロジェクトの作成
まずは、Hono のテンプレートを使ってプロジェクトを作成します。
任意のディレクトリに移動し、以下コマンドを実行してください。
npm create hono@latest todo-api
いくつか質問されるので、以下のように選択してください。
-
Which template do you want to use? →
cloudflare-workers -
Do you want to install dependencies? →
Y -
Which package manager do you want to use? →
npm
プロジェクトが作成されたら、ディレクトリに移動します。
cd todo-api
Supabase クライアントの導入
Supabase に接続するため、Supabase のクライアントライブラリをインストールします。
npm install @supabase/supabase-js
Wrangler のセットアップ
Cloudflare Workers をデプロイするための CLI ツール「Wrangler」をセットアップします。
npx wrangler login
ブラウザが開き、Cloudflare アカウントとの連携を許可すれば完了です。
実装
それでは、実際に CRUD API を実装していきます。
ディレクトリ構成
まず、プロジェクトの構成を確認しましょう。以下のような構造になっています。
todo-api/
├── src/
│ └── index.ts # メインのアプリケーションファイル
├── wrangler.jsonc # Cloudflare Workers設定
├── package.json
└── tsconfig.json
CRUD エンドポイントの実装
src/index.ts ファイルを編集して、Todo API を実装していきます。
import { Hono } from "hono";
import { createClient } from "@supabase/supabase-js";
type Todo = {
id: number;
title: string;
};
const app = new Hono();
// Supabaseクライアントを作成するヘルパー関数
const createSupabaseClient = (c: any) => {
const supabaseUrl = c.env.SUPABASE_URL;
const supabaseKey = c.env.SUPABASE_ANON_KEY;
// 環境変数の存在チェックを追加
if (!supabaseUrl || !supabaseKey) {
throw new Error("Supabase credentials are not configured");
}
return createClient(supabaseUrl, supabaseKey);
};
// ルートエンドポイント
app.get("/", (c) => {
return c.json({
message: "Todo API with Hono + Cloudflare Workers + Supabase",
});
});
GET /todos
全ての Todo を取得するエンドポイントを実装します。
app.get("/todos", async (c) => {
const supabase = createSupabaseClient(c);
const { data, error } = await supabase.from("todos").select("*").order("id");
if (error) {
return c.json({ error: error.message }, 500);
}
return c.json({ todos: data });
});
POST /todos
新しい Todo を作成するエンドポイントです。
app.post("/todos", async (c) => {
const supabase = createSupabaseClient(c);
// リクエストボディからタイトルを取得
const body = await c.req.json();
const { data, error } = await supabase
.from("todos")
.insert([{ title: body.title }])
.select();
if (error) {
return c.json({ error: error.message }, 500);
}
return c.json({ todo: data[0] }, 201);
});
PUT /todos/:id
既存の Todo を更新するエンドポイントです。
app.put("/todos/:id", async (c) => {
const supabase = createSupabaseClient(c);
// URLパラメータからIDを取得
const id = parseInt(c.req.param("id"));
// リクエストボディから更新内容を取得
const body = await c.req.json();
const { data, error } = await supabase
.from("todos")
.update({ title: body.title })
.eq("id", id)
.select();
if (error) {
return c.json({ error: error.message }, 500);
}
if (data.length === 0) {
return c.json({ error: "Todo not found" }, 404);
}
return c.json({ todo: data[0] });
});
DELETE /todos/:id
指定された ID の Todo を削除するエンドポイントです。
app.delete("/todos/:id", async (c) => {
const supabase = createSupabaseClient(c);
// URLパラメータからIDを取得
const id = parseInt(c.req.param("id"));
const { data, error } = await supabase
.from("todos")
.delete()
.eq("id", id)
.select();
if (error) {
return c.json({ error: error.message }, 500);
}
if (data.length === 0) {
return c.json({ error: "Todo not found" }, 404);
}
return c.json({ message: "Todo deleted successfully" });
});
export default app;
リクエストパラメータについて詳しく知りたい場合はHono の公式サイトを確認してください。
完全版
上記のコードを全て組み合わせた完全な src/index.ts ファイルは以下のようになります。
完全版
import { Hono } from "hono";
import { createClient } from "@supabase/supabase-js";
type Todo = {
id: number;
title: string;
};
const app = new Hono();
// Supabaseクライアントを作成するヘルパー関数
const createSupabaseClient = (c: any) => {
const supabaseUrl = c.env.SUPABASE_URL;
const supabaseKey = c.env.SUPABASE_ANON_KEY;
// 環境変数の存在チェック
if (!supabaseUrl || !supabaseKey) {
throw new Error("Supabase credentials are not configured");
}
return createClient(supabaseUrl, supabaseKey);
};
// ルートエンドポイント
app.get("/", (c) => {
return c.json({
message: "Todo API with Hono + Cloudflare Workers + Supabase",
});
});
// 全件取得
app.get("/todos", async (c) => {
const supabase = createSupabaseClient(c);
const { data, error } = await supabase.from("todos").select("*").order("id");
if (error) {
return c.json({ error: error.message }, 500);
}
return c.json({ todos: data });
});
// 新規作成
app.post("/todos", async (c) => {
const supabase = createSupabaseClient(c);
// リクエストボディからタイトルを取得
const body = await c.req.json();
const { data, error } = await supabase
.from("todos")
.insert([{ title: body.title }])
.select();
if (error) {
return c.json({ error: error.message }, 500);
}
return c.json({ todo: data[0] }, 201);
});
// 更新
app.put("/todos/:id", async (c) => {
const supabase = createSupabaseClient(c);
// URLパラメータからIDを取得
const id = parseInt(c.req.param("id"));
// リクエストボディから更新内容を取得
const body = await c.req.json();
const { data, error } = await supabase
.from("todos")
.update({ title: body.title })
.eq("id", id)
.select();
if (error) {
return c.json({ error: error.message }, 500);
}
if (data.length === 0) {
return c.json({ error: "Todo not found" }, 404);
}
return c.json({ todo: data[0] });
});
// 削除
app.delete("/todos/:id", async (c) => {
const supabase = createSupabaseClient(c);
// URLパラメータからIDを取得
const id = parseInt(c.req.param("id"));
const { data, error } = await supabase
.from("todos")
.delete()
.eq("id", id)
.select();
if (error) {
return c.json({ error: error.message }, 500);
}
if (data.length === 0) {
return c.json({ error: "Todo not found" }, 404);
}
return c.json({ message: "Todo deleted successfully" });
});
export default app;
これで、シンプルな CRUD 機能を持つ Todo API が完成しました。
デプロイ
それでは、作成した Todo API を Cloudflare Workers にデプロイしていきます。
環境変数の設定
デプロイの前に、本番環境用の環境変数を設定する必要があります。Supabase の接続情報を安全に保存するため、Wrangler の secret コマンドを使用します。
Supabase URL の設定
npx wrangler secret put SUPABASE_URL
コマンドを実行すると、値の入力を求められるので、Supabase プロジェクトの URL を入力してください。
Enter a secret value: https://your-project-id.supabase.co
いくつか質問されるので、以下のように選択してください。
-
There doesn't seem to be a Worker called "todo-api". Do you want to create a new Worker with that name and add secrets to it? →
Y
Supabase API Key の設定
npx wrangler secret put SUPABASE_ANON_KEY
Supabase の匿名キーを入力してください。
Enter a secret value: your-supabase-anon-key
Cloudflare Workers へのデプロイ
環境変数の設定が完了したら、いよいよデプロイを行います。
ローカルでのテスト実行
まず、ローカル環境でアプリケーションが正常に動作することを確認しましょう。環境変数を直接指定して開発サーバーを起動します。
SUPABASE_URL="https://your-project-id.supabase.co" \
SUPABASE_ANON_KEY="your-supabase-anon-key" \
npm run dev
このコマンドで開発サーバーが起動します。ブラウザで http://localhost:8787 にアクセスし、以下のレスポンスが表示されれば成功です。
{
"message": "Todo API with Hono + Cloudflare Workers + Supabase"
}
動作確認が完了したら、Ctrl + C でサーバーを停止します。
デプロイコマンドの実行
それでは、実際に Cloudflare Workers にデプロイします。
npm run deploy
または、直接 wrangler コマンドを使用することもできます。
npx wrangler deploy
デプロイが成功すると、以下のような出力が表示されます。
✨ Successfully published your Worker!
🌍 Available at https://todo-api.your-subdomain.workers.dev
表示された URL があなたの API のエンドポイントです。
デプロイの確認
デプロイされた API が正常に動作するか確認してみましょう。ブラウザまたは curl コマンドでアクセスします。
curl https://todo-api.your-subdomain.workers.dev
以下のレスポンスが返ってくれば、デプロイは成功です。
{
"message": "Todo API with Hono + Cloudflare Workers + Supabase"
}
本番環境での注意事項
-
環境変数: 本番環境では
wrangler secretで設定した環境変数が使用されます -
ログ:
npx wrangler tailコマンドでリアルタイムログを確認できます
# リアルタイムログの確認
npx wrangler tail
これで、Todo API の本番環境へのデプロイが完了しました!
動作確認
デプロイした API が正常に動作するか、実際に CRUD 操作を行って確認してみましょう。
curl での確認
https://todo-api.your-subdomain.workers.dev の部分は、実際のデプロイ先 URL に置き換えてください。
新しい Todo を作成
curl -X POST https://todo-api.your-subdomain.workers.dev/todos \
-H "Content-Type: application/json" \
-d '{"title": "テスト用Todo"}'
全件取得
curl https://todo-api.your-subdomain.workers.dev/todos
Todo の更新(ID:1 の場合)
curl -X PUT https://todo-api.your-subdomain.workers.dev/todos/1 \
-H "Content-Type: application/json" \
-d '{"title": "更新されたTodo"}'
Todo の削除(ID:1 の場合)
curl -X DELETE https://todo-api.your-subdomain.workers.dev/todos/1
Supabase 側での確認
- Supabase ダッシュボードにログイン
- 該当プロジェクトを選択
- 左メニューから「Table Editor」→「todos」テーブルを確認
API 経由で作成・更新したデータが正しく反映されていることを確認できます。
無料枠について
本記事で使用した技術スタックは、すべて無料枠内で利用できます。
- Cloudflare Workers: 100,000リクエスト/日まで無料
- Supabase: データベース500MB、50,000月間アクティブユーザーまで無料
個人プロジェクトや学習目的であれば、この無料枠で十分に運用できます。
まとめ
本記事では、Hono + Cloudflare Workers + Supabase を組み合わせて、シンプルな Todo API を構築・デプロイしました。
この構成では、すべて無料枠内で開発でき、サーバーレスなので運用も簡単です。今回のコードを基に、認証機能やバリデーション機能を追加してより実用的な API にカスタマイズしていくことが可能です!
実際にこの構成で開発してみて、思った以上にスムーズに API が作れることに驚きました。特に、Cloudflare Workers のデプロイが爆速で、コードを書いたらすぐに本番環境で試せるのが最高でした。
Supabase も、SQL で簡単にテーブルが作れて、すぐに使い始められるのが便利でしたね。無料枠でここまでできるのはありがたいです。
最初は3つの技術を組み合わせるのが難しそうに思えましたが、実際にやってみると意外とシンプルで、それぞれの技術が上手く連携してくれました。この記事が同じように困っている方の助けになれば嬉しいです!
Discussion