Worker (API) の作成

Page content

Astro(Pages)とWorkerを最小構成で作成し、実際にデプロイして動作確認するための手順をまとめました。

この手順では、Astroをフロントエンド、**Workerをバックエンド(API)**として扱い、同一ドメインの /api/* ルートで連携させます。


1. Worker (API) の作成とデプロイ

まずはAPIとなるWorkerを作成します。

Bash

# 1. Workerプロジェクトの作成(C3を使用)
# 設定: "Hello World" worker / TypeScript: Yes / Deploy: Yes
npm create cloudflare@latest my-api-worker

# 2. プロジェクトディレクトリへ移動
cd my-api-worker

src/index.ts を編集して動作確認用レスポンスを返す:

TypeScript

export default {
  async fetch(request, env, ctx): Promise<Response> {
    return new Response(JSON.stringify({ message: "Hello from Worker!" }), {
      headers: { "content-type": "application/json" },
    });
  },
} satisfies ExportedHandler;

デプロイ:

Bash

npm run deploy

[!NOTE]

デプロイ完了時に表示される https://my-api-worker.xxxx.workers.dev をメモしておいてください。


OK、ここは混乱しやすい最重要ポイントなので、完全に整理します👇


■ 結論(コマンド一覧)

✅ ① Pages(静的サイト / SSG)

👉 一番おすすめ(まずはこれ)

npm run build
npx wrangler pages deploy ./dist

または(設定済みなら)

npx wrangler pages deploy

✅ ② SSR(Workersで動かす)

👉 動的サイト・APIあり

npm run build
npx wrangler deploy

■ 違い(超重要)

項目 Pages SSR(Workers)
コマンド pages deploy deploy
出力 静的HTML serverコード
フォルダ dist/ dist/server/
用途 ブログ・LP API・ログイン

■ Astro設定との対応

Pagesにする場合

// astro.config.mjs
export default {
  output: "static"
}

👉 これで dist/ が生成


SSRにする場合

import cloudflare from '@astrojs/cloudflare';

export default {
  adapter: cloudflare()
}

👉 dist/server/ が生成


■ よくあるミス(あなたがハマってた所)

❌ SSRなのに

wrangler pages deploy

❌ 静的なのに

wrangler deploy

👉 これでエラー地獄になる


■ 判断基準(迷ったらこれ)

👉 今のあなたなら👇

✔ Pages(静的)にする

  • シンプル

  • エラー少ない

  • 速い


✔ SSRにするのは後

  • API必要になったら

  • ログイン機能やるとき


■ 一言で覚える

👉 Pages = HTML配る
👉 SSR = サーバー動かす


■ おすすめ最終形(プロ構成)

👉 将来的にはこれ👇

  • フロント → Astro(Pages)

  • API → Cloudflare Workers



2. Astro (Pages) の作成と設定

次に、Workerを呼び出すAstroプロジェクトを作成します。

Bash

# 1. Astroプロジェクトの作成
# 設定: "Empty" または "Sample" / TypeScript: Yes / Install dependencies: Yes
npm create astro@latest my-astro-app

# 2. ディレクトリへ移動
cd my-astro-app

# 3. Cloudflare Adapterの追加(SSRモードにするため)
npx astro add cloudflare

src/pages/index.astro を編集してAPIを叩く:

コード スニペット

---
// サーバーサイドでのフェッチ例
// デプロイ後は相対パス "/api/test" で動くようになります
---
<html>
  <body>
    <h1>Astro + Worker 連携テスト</h1>
    <button id="fetchBtn">Workerからデータを取得</button>
    <p id="result"></p>

    <script>
      document.getElementById('fetchBtn')?.addEventListener('click', async () => {
        const res = await fetch('/api/test'); // 同一ドメインのルート経由
        const data = await res.json();
        const resultEl = document.getElementById('result');
        if (resultEl) resultEl.innerText = data.message;
      });
    </script>
  </body>
</html>

デプロイ:

Bash

# Pagesとしてデプロイ(GitHub連携が推奨ですが、コマンドでも可能)
npx wrangler pages deploy ./dist

3. Cloudflareコンソールでの紐付け(重要)

最後に、前回の回答で解説した 「Routes」 の設定を行います。

  1. Cloudflareダッシュボード > Workers & Pagesmy-api-worker を開く。

  2. SettingsDomains & RoutesAdd Route をクリック。

  3. Route: [Astroのドメイン]/api/* を入力。

  4. Zone: 使用しているドメインを選択。


4. 動作確認コマンド

デプロイしたAstroのURLにアクセスし、ボタンを押して “Hello from Worker!” と表示されれば成功です。

ブラウザのデベロッパーツール(Networkタブ)で確認すると、リクエストが https://example.com/api/test へ飛んでおり、CORSエラーなしでデータが取得できているはずです。


5. ローカル開発での動作確認

ローカルで両方を同時に動かしてテストしたい場合は、wrangler の proxy 機能を使います。

Bash

# Astroをビルドしてプレビュー
npm run build
npx wrangler pages dev ./dist --proxy 8787

# 別ターミナルでWorkerを起動
cd ../my-api-worker
npx wrangler dev --port 8787

これにより、ローカルでも localhost:8888/api/* へのアクセスが Worker へ転送されます。

Cloudflare上でAstro(Pages)と、別体で用意したWorkerを「API連携」させる場合、基本的には**「同一ドメインの下にWorkerをぶら下げる」**形にするのが最もスムーズです(CORS対応が不要になるため)。

Cloudflareコンソールの「Routes(ルート)」設定を中心に、その手順を詳しく解説します。


1. 構成のイメージ

Astroプロジェクト(Pages)をメインのドメインで動かし、特定のパス(例: /api/*)へのリクエストだけをWorkerに飛ばす設定を行います。

  • Astro (Cloudflare Pages): example.com/*

  • Worker (API用): example.com/api/*


2. Cloudflareコンソールでの「Routes」設定手順

Workerを特定のURL(パス)で動作させるには、Worker側の設定画面から操作します。

  1. Cloudflareダッシュボードにログインし、左メニューの 「Workers & Pages」 を選択します。

  2. 連携させたい Worker(API側) をクリックして開きます。

  3. 上部タブの 「Settings(設定)」「Domains & Routes(ドメインとルート)」 を選択します。

  4. 「Add(追加)」 ボタンをクリックし、「Route(ルート)」 を選択します。

  5. 以下の内容を入力して保存します。

    • Route: example.com/api/*example.com はあなたのドメインに置き換えてください)

    • Zone: あなたのドメイン名を選択

[!IMPORTANT]

この設定を有効にするには、対象のドメインが CloudflareのDNS管理下(プロキシ済み/オレンジの雲マーク) である必要があります。


3. Astro側での呼び出し方

上記の設定が完了すると、Astroのフロントエンド(クライアントサイド)やSSRコードから、相対パスでAPIを叩けるようになります。

JavaScript

// Astroコンポーネントやスクリプト内
const response = await fetch('/api/user-data');
const data = await response.json();

なぜこの設定が良いのか?

  • CORS回避: 同じドメインとして扱われるため、面倒なCORS(Cross-Origin Resource Sharing)設定が一切不要になります。

  • Cookieの共有: セッション管理などでCookieを使用している場合、そのままWorkerに送信されます。


4. 注意点:Pages Functionsとの競合

Astroで adapter: cloudflare() を使用している場合、Astro自体も内部的にWorker(Pages Functions)として動いています。

  • 優先順位: Cloudflareの仕様上、「WorkersのRoutes」は「Pages」よりも優先されます。

  • そのため、/api/* というルートをWorkerに割り当てると、Astro側で src/pages/api/ にファイルを作っていても、それは無視され、設定したWorkerが実行されます。


5. 代替案:Service Bindings(推奨)

もし「外部公開したくないAPI」をAstroから呼び出したい、あるいはもっと高速に連携させたい場合は、Routesではなく Service Bindings を使うのが現在のトレンドです。

  1. Pagesの設定FunctionsService bindings に移動。

  2. 変数名(例: API_WORKER)を決め、対象のWorkerを紐付ける。

  3. Astroのサーバーサイドコード(SSR)で context.locals.runtime.env.API_WORKER.fetch(request) のように直接呼び出す。

これでネットワークを介さず(インターネットに出ず)にWorkerを叩けます。

Cloudflare Pages(Astro)と Workers を「同一ドメインの特定パス(/api/*)」で共存させる仕組みと、そのメリットを詳しく解説します。

この構成は、モダンな Web 開発において 「BFF (Backend For Frontend)」 パターンを構築する際に非常に強力です。


1. 全体像とルーティングの優先順位

Cloudflare のインフラ内では、リクエストが届いた際に「どのサービスに割り振るか」の優先順位が決まっています。

  1. Workers (Custom Routes): 最も優先度が高い。

  2. Pages (Functions / Static Assets): Workers のルートに該当しない場合に処理される。

つまり、example.com/api/* という Route を Worker に割り当てると、Astro 側(Pages)に同じパスがあっても、Cloudflare は迷わず Worker へリクエストを飛ばします。


2. なぜこの構成にするのか?(3つのメリット)

① CORS (Cross-Origin Resource Sharing) の完全回避

通常、フロントエンド(a.com)から別ドメインの API(api.b.com)を叩く場合、ブラウザのセキュリティ制限により CORS の設定が必要です。

しかし、今回の設定では**ブラウザから見て「同じドメイン」**なので、複雑なヘッダー設定なしで通信が許可されます。

ドメインが同じであるため、Astro 側で発行した HttpOnly 属性付きの Cookie を、Worker 側でもそのまま受け取ることができます。これにより、セッション認証やログイン状態の管理が非常に楽になります。

③ コストとパフォーマンスの最適化

  • Astro: 静的コンテンツの配信や、エッジでのレンダリング(SSR)に集中。

  • Worker: データベース操作、外部 API との通信、重いロジックの処理を担当。

    これらを分離することで、コードの管理がしやすくなり、Cloudflare の強力なエッジネットワークの恩恵をフルに受けられます。


3. 実践的なワークフロー

開発からデプロイまでの流れを整理すると以下のようになります。

  1. Worker のデプロイ: wrangler deploy.workers.dev などのサブドメインに公開される。

  2. Route 設定: Cloudflare コンソールで、メインドメインの /api/* をその Worker に紐付ける。

  3. Astro からの呼び出し: fetch('/api/user') のように相対パスで記述する。


4. 運用上の Tips:環境変数の使い分け

Astro と Worker は別々のプロジェクトとして管理されるため、環境変数も個別に設定する必要があります。

  • Worker 側: wrangler.toml またはコンソールの「Settings」で設定(DB の接続先など)。

  • Astro 側: Pages の「Settings」>「Environment variables」で設定(外部サービスの API キーなど)。


5. 次のステップへの提案

基本的な疎通確認ができたら、次は 「認証」「データの受け渡し」 を強化するのが一般的です。

Astro(クライアント/サーバー)から別体の Worker へデータを送る際、最もシンプルかつ堅牢なのが 「カスタムヘッダーによる API キー認証」 です。

同じドメイン下 (/api/*) で動かしている場合でも、直接 Worker の URL を叩かれるリスクに備えて、以下の実装を行っておくのが安全です。


1. Worker 側の実装(受け取り側)

Worker 側で、リクエストヘッダーに含まれる API キーが、環境変数に設定した値と一致するかをチェックします。

my-api-worker/src/index.ts

TypeScript

export default {
  async fetch(request, env, ctx): Promise<Response> {
    // 1. ヘッダーから API キーを取得
    const apiKey = request.headers.get("X-Custom-API-Key");

    // 2. 環境変数の値と照合 (env.API_SECRET_KEY は Cloudflare コンソールで設定)
    if (!apiKey || apiKey !== env.API_SECRET_KEY) {
      return new Response(JSON.stringify({ error: "Unauthorized" }), {
        status: 401,
        headers: { "content-type": "application/json" },
      });
    }

    // 3. 認証成功時の処理 (POST リクエストのデータを処理するなど)
    if (request.method === "POST") {
      const body = await request.json();
      return new Response(JSON.stringify({ message: "Data received!", data: body }), {
        status: 200,
      });
    }

    return new Response("OK");
  },
} satisfies ExportedHandler;

2. Astro 側の実装(送り側)

Astro のサーバーサイド(.astro ファイルのフロントマターや API Endpoints)から Worker を呼び出す際に、ヘッダーにキーを付与します。

my-astro-app/src/pages/submit.astro

コード スニペット

---
// Astro 側の環境変数から API キーを取得
const API_SECRET = import.meta.env.API_SECRET_KEY;

if (Astro.request.method === "POST") {
  const formData = await Astro.request.formData();
  const name = formData.get("name");

  // Worker へのフェッチ (Routes 設定により /api/ で届く)
  const response = await fetch(`${Astro.url.origin}/api/data`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Custom-API-Key": API_SECRET, // ここでキーを渡す
    },
    body: JSON.stringify({ name }),
  });

  const result = await response.json();
  console.log(result);
}
---

<form method="POST">
  <input type="text" name="name" placeholder="お名前" />
  <button type="submit">送信</button>
</form>

3. Cloudflare コンソールでの設定(重要)

コードに直接キーを書くのは厳禁です。必ず Cloudflare の管理画面から 環境変数 を登録してください。

Worker 側

  1. Workers & Pagesmy-api-workerSettingsVariables

  2. Environment VariablesAPI_SECRET_KEY を追加し、ランダムな文字列を入力して「Encrypt(暗号化)」を有効にして保存。

Astro (Pages) 側

  1. Workers & Pagesmy-astro-appSettingsEnvironment variables

  2. Worker と同じ値API_SECRET_KEY を追加して保存。


4. この方法の安全性について

  • 秘密保持: API キーはサーバーサイド(Astro の SSR または Pages Functions)で付与されるため、ブラウザのデベロッパーツールで見られることはありません。

  • 直叩き防止: /api/* のルートを知っている第三者が直接アクセスしても、正しいヘッダーがない限り Worker は 401 エラーを返します。


5. 次のステップ

さらにセキュリティを高めるなら、Cloudflare Access を使って /api/* へのアクセス自体を特定の IP や認証済みユーザーに絞ることも可能です。

Cloudflare WorkerからCloudflare D1 (SQLデータベース) にデータを書き込むための、最もシンプルで実践的な手順を解説します。

D1を使えば、Astroから送られてきたデータをSQLiteベースの高速なデータベースに保存できます。


1. D1 データベースの作成と初期化

まずは、コマンドライン(Wrangler)を使ってデータベースを作成します。

Bash

# 1. データベースの作成(名前は 'my-db' とします)
npx wrangler d1 create my-db

# 2. 出力された内容(database_idなど)を wrangler.toml に貼り付ける
# [wrangler.toml の例]
# [[d1_databases]]
# binding = "DB" 
# database_name = "my-db"
# database_id = "xxxx-xxxx-xxxx-xxxx"

次に、テーブルを作成するためのSQLファイル(例: schema.sql)を用意します。

SQL

-- schema.sql
CREATE TABLE IF NOT EXISTS users (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  name TEXT NOT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

これを実行してテーブルを作成します。

Bash

npx wrangler d1 execute my-db --local --file=./schema.sql  # ローカル開発用
npx wrangler d1 execute my-db --remote --file=./schema.sql # 本番環境用

2. Worker 側での書き込み実装

Workerのコード内で、環境変数 env.DB を通じてSQLを実行します。

my-api-worker/src/index.ts

TypeScript

export interface Env {
  DB: D1Database; // wrangler.tomlのbinding名と一致させる
  API_SECRET_KEY: string;
}

export default {
  async fetch(request, env, ctx): Promise<Response> {
    // 前回のAPIキー認証(中略)...
    const apiKey = request.headers.get("X-Custom-API-Key");
    if (apiKey !== env.API_SECRET_KEY) return new Response("Unauthorized", { status: 401 });

    if (request.method === "POST") {
      try {
        const { name } = await request.json();

        // D1への書き込み(プリペアドステートメントを使用)
        const info = await env.DB.prepare(
          "INSERT INTO users (name) VALUES (?)"
        )
        .bind(name)
        .run();

        return new Response(JSON.stringify({ success: true, id: info.meta.last_row_id }), {
          headers: { "content-type": "application/json" },
        });
      } catch (e) {
        return new Response("Database Error", { status: 500 });
      }
    }

    // データ取得(GET時)の例
    const { results } = await env.DB.prepare("SELECT * FROM users ORDER BY created_at DESC LIMIT 10").all();
    return Response.json(results);
  },
} satisfies ExportedHandler<Env>;

3. Astro 側での呼び出し確認

Astroのサーバーサイドコード(前回作成したもの)から、そのまま name をPOSTすれば、D1にデータが蓄積されます。


4. データの確認方法

デプロイした後、実際にデータが入っているかどうかはダッシュボードまたはコマンドで確認できます。

  • コンソール: Cloudflareダッシュボード > Workers & PagesD1my-dbTables

  • コマンド: ```bash

    npx wrangler d1 execute my-db –remote –command=“SELECT * FROM users;”


5. まとめ:全体の連携フロー

  1. Astro: ユーザー入力を受け取り、APIキーを添えて /api/data へPOST。

  2. Routes: Cloudflareがパスを検知し、リクエストを Worker へルーティング。

  3. Worker: APIキーを検証し、問題なければ D1INSERT

  4. D1: データを安全に保存。

これで、フロントエンド(Astro)からバックエンド(Worker)、データベース(D1)までのフルスタックな構成が完了しました!

保存したデータをAstroのページ上に一覧表示する方法を解説します。

この構成では、**Astroのサーバーサイド(SSR)**でWorker(API)を叩き、取得したデータをHTMLとしてレンダリングします。こうすることで、ブラウザがページを読み込んだ瞬間にはすでにデータが表示されている状態(SEOにも強い構成)になります。


1. Worker側:データ取得用エンドポイントの準備

まず、WorkerがD1からデータを全件取得して返すようにします。

my-api-worker/src/index.ts

TypeScript

// GETリクエストが来たらデータを返す処理を追加
if (request.method === "GET") {
  try {
    const { results } = await env.DB.prepare(
      "SELECT id, name, created_at FROM users ORDER BY created_at DESC"
    ).all();

    return new Response(JSON.stringify(results), {
      headers: { 
        "content-type": "application/json",
        "Cache-Control": "public, max-age=60" // 任意:1分間キャッシュ
      },
    });
  } catch (e) {
    return new Response("Database Error", { status: 500 });
  }
}

2. Astro側:データを取得して表示する

Astroのコンポーネントの「フロントマター(---の間)」で、Workerからデータをフェッチします。

src/pages/users.astro

コード スニペット

---
// 1. 環境変数からAPIキーを取得
const API_SECRET = import.meta.env.API_SECRET_KEY;

// 2. Worker(API)からデータを取得
// 同一ドメイン運用なので Astro.url.origin を使って絶対URLを作る
const response = await fetch(`${Astro.url.origin}/api/data`, {
  method: "GET",
  headers: {
    "X-Custom-API-Key": API_SECRET,
  },
});

// 型定義(TypeScriptを使う場合)
interface User {
  id: number;
  name: string;
  created_at: string;
}

const users: User[] = await response.json();
---

<html>
  <head>
    <title>ユーザー一覧</title>
  </head>
  <body>
    <h1>登録ユーザー一覧</h1>
    
    {users.length === 0 && <p>ユーザーはまだ登録されていません。</p>}

    <ul>
      {users.map((user) => (
        <li>
          <strong>{user.name}</strong> 
          <small>(登録日: {new Date(user.created_at).toLocaleString('ja-JP')})</small>
        </li>
      ))}
    </ul>

    <a href="/">登録画面へ戻る</a>
  </body>
</html>

3. この仕組みのポイント

  • 秘密の通信: APIキー (X-Custom-API-Key) はAstroのサーバーサイドで付与されます。ユーザーのブラウザにはAPIキーは一切漏れません。

  • 高速な表示: クライアントサイド(JavaScript)で fetch するのではなく、AstroがHTMLを生成するタイミングでデータを埋め込むため、ページを開いた瞬間にリストが表示されます。

  • 同一ドメインの利点: fetch('/api/data') と書くだけで、Cloudflareのルーティングによって自動的にWorkerへ接続されます。


4. 開発時の注意点

ローカル環境でテストする場合、AstroとWorkerの両方を起動しておく必要があります。

  1. Workerを起動: npx wrangler dev --port 8787

  2. Astroを起動: npx wrangler pages dev ./dist --proxy 8787

これで、localhost:8888/users にアクセスすると、ローカルのD1データが表示されるようになります。


次のステップへのアイデア

  • 削除機能: 各リストの横に「削除」ボタンを置き、Workerへ DELETE リクエストを送る。

  • リアルタイム更新: 登録後すぐに一覧を更新したい場合は、Astroのアイランド(ReactやVue、またはVanilla JS)を使ってクライアントサイドで再取得する。

「特定のユーザーだけを検索・表示する機能」や「ページネーション(件数制限)」の実装についても解説が必要ですか?