Gemcook Tech Blog
🚀

【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 アカウント登録

  1. Cloudflare にアクセスし、アカウントを作成します。
    GitHub や Google アカウントでサインアップできます。
  2. ログイン後、ホームに移動します。
  3. 左メニューから 「Compute & AI」 → 「Workers & Pages」 を選択します。
    ここで自分のサーバーレスアプリを管理できるようになります。
    このときメールアドレスを認証するよう求められますので、認証してください。

Supabase プロジェクト作成

次に、データベースとして利用する Supabase を設定します。

  1. Supabase にアクセスし、アカウントを作成します。
  2. 認証メールの「Confirm Email Address」をクリックします
  3. 「Create a new organization」画面で情報を入力します。
    • Name:任意(例:Your name's Org
    • Type:任意(例:Personal
    • Plan:Free
  4. 「Create a new project」画面で新しいプロジェクトを作成します。
    • Organization:先ほど作成した organization(例:Your name's Org
    • Project name:任意(例:todo-api
    • Database password:任意のパスワード
    • Region:近い地域(例:Asia-Pacific
  5. プロジェクトが作成されます。

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 情報を確認

  1. 左側メニューから 「Project Settings」 を開きます。
  2. 以下の 2 つを控えておきましょう。
    • 「Data API」 → Project URL(例:https://xxxx.supabase.co
    • 「API Keys」 → anon public key

これらは後ほど 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 側での確認

  1. Supabase ダッシュボードにログイン
  2. 該当プロジェクトを選択
  3. 左メニューから「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つの技術を組み合わせるのが難しそうに思えましたが、実際にやってみると意外とシンプルで、それぞれの技術が上手く連携してくれました。この記事が同じように困っている方の助けになれば嬉しいです!

Gemcook Tech Blog
Gemcook Tech Blog

Discussion