Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

Cloudflare Workers に Express.js アプリケーションをデプロイする

最終更新 Markdown で表示Agent セットアップ

このチュートリアルでは、Cloudflare Workers プラットフォームD1 データベース を使い、Express.js アプリケーションを Cloudflare Workers にデプロイします。作成・読み取り・更新・削除(CRUD)の基本操作を持つメンバー登録 API を作ります。メンバーデータの保存と取得には、データベースとして D1 を使います。

始める前に

すべてのチュートリアルは、Cloudflare Workers アカウント、C3、および Wrangler のセットアップが完了している前提です。セットアップは Get started ガイド で行います。

クイックスタート

手順を飛ばしてすぐに始めたい場合は、下の Deploy to Cloudflare を選びます。

Cloudflare にデプロイ

GitHub アカウントにリポジトリが作成され、アプリケーションが Cloudflare Workers にデプロイされます。Cloudflare Workers に慣れていて、手順ごとの案内を飛ばしたい場合に使います。

Cloudflare Workers が初めてなら、手順を手で追うことをおすすめします。

1. 新しい Cloudflare Workers プロジェクトを作成する

Cloudflare の開発者向け製品用のコマンドラインツール C3 を使い、新しいディレクトリを作成して Worker プロジェクトを初期化します。

npm create cloudflare@latest -- express-d1-app

セットアップでは、次のオプションを選びます。

  • What would you like to start with? では、Hello World example を選びます。
  • Which template would you like to use? では、Worker only を選びます。
  • Which language do you want to use? では、TypeScript を選びます。
  • Do you want to use git for version control? では、Yes を選びます。
  • Do you want to deploy your application? では、No を選びます(デプロイ前にいくつか変更します)。

新しいプロジェクトのディレクトリへ移動します。

cd express-d1-app

2. Express と依存関係をインストールする

このチュートリアルでは、Node.js 向けの人気フレームワーク Express.js を使います。Cloudflare Workers 環境で Express を使うため、Express と必要な TypeScript の型をインストールします。

npm i express @types/express

Cloudflare Workers 上の Express.js には、nodejs_compat 互換性フラグ が必要です。このフラグは Node.js API を有効にし、Workers ランタイムで Express を動かせます。Wrangler 設定ファイルに次を追加します。

{
	"compatibility_flags": [
		"nodejs_compat"
	]
}
compatibility_flags = [ "nodejs_compat" ]

3. D1 データベースを作成する

メンバー情報を保存する D1 データベースを作成します。wrangler d1 create コマンドで新しいデータベースを作成します。

npx wrangler d1 create members-db

コマンドは新しい D1 データベースを作成し、次の質問をします。

  • Would you like Wrangler to add it on your behalf?: Y を入力します。
  • What binding name would you like to use?: DB を入力して Enter を押します。
  • For local dev, do you want to connect to the remote resource instead of a local resource?: N を入力します。
 ⛅️ wrangler 4.44.0
───────────────────
 Successfully created DB 'members-db' in region WNAM
Created your new D1 database.

To access your new D1 Database in your Worker, add the following snippet to your configuration file:
{
  "d1_databases": [
    {
      "binding": "members_db",
      "database_name": "members-db",
      "database_id": "<unique-ID-for-your-database>"
    }
  ]
}
 Would you like Wrangler to add it on your behalf? yes
 What binding name would you like to use? DB
 For local dev, do you want to connect to the remote resource instead of a local resource? no

バインディングは Wrangler 設定ファイルに追加されます。

{
	"d1_databases": [
		{
			"binding": "DB",
			"database_name": "members-db",
			"database_id": "<unique-ID-for-your-database>"
		}
	]
}
[[d1_databases]]
binding = "DB"
database_name = "members-db"
database_id = "<unique-ID-for-your-database>"

4. データベーススキーマを作成する

プロジェクトのルートに schemas ディレクトリを作成し、その中に schema.sql ファイルを作成します。

schemas/schema.sqlsql
DROP TABLE IF EXISTS members;
CREATE TABLE IF NOT EXISTS members (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  name TEXT NOT NULL,
  email TEXT NOT NULL UNIQUE,
  joined_date TEXT NOT NULL
);

-- Insert sample data
INSERT INTO members (name, email, joined_date) VALUES
  ('Alice Johnson', 'alice@example.com', '2024-01-15'),
  ('Bob Smith', 'bob@example.com', '2024-02-20'),
  ('Carol Williams', 'carol@example.com', '2024-03-10');

このスキーマは、自動増分の ID、名前、メール、参加日のフィールドを持つ members テーブルを作成します。サンプルのメンバーも 3 件挿入します。

スキーマファイルを D1 データベースに対して実行します。

npx wrangler d1 execute members-db --file=./schemas/schema.sql

上のコマンドは、ローカル開発用データベースにテーブルを作成します。本番へのスキーマのデプロイは後で行います。

5. Express アプリケーションを初期化する

src/index.ts を更新し、TypeScript で Express をセットアップします。ファイルの内容を次に置き換えます。

src/index.tsts
import { env } from "cloudflare:workers";
import { httpServerHandler } from "cloudflare:node";
import express from "express";

const app = express();

// Middleware to parse JSON bodies
app.use(express.json());

// Health check endpoint
app.get("/", (req, res) => {
	res.json({ message: "Express.js running on Cloudflare Workers!" });
});

app.listen(3000);
export default httpServerHandler({ port: 3000 });

このコードは Express を初期化し、基本的なヘルスチェック用エンドポイントを作成します。重要なインポート import { env } from "cloudflare:workers" により、コードのどこからでも D1 データベースなどの バインディング にアクセスできます。httpServerHandler は Express を Workers ランタイムとつなぎ、Cloudflare のネットワーク上で HTTP リクエストを処理できるようにします。

次に、typegen コマンドを実行し、Worker 環境の型定義を生成します。

npm run cf-typegen

6. 読み取り操作を実装する

データベースからメンバーを取得するエンドポイントを追加します。ヘルスチェック用エンドポイントのあとに、次のルートを src/index.ts へ追加します。

src/index.tsts
// GET all members
app.get('/api/members', async (req, res) => {
	try {
		const { results } = await env.DB.prepare('SELECT * FROM members ORDER BY joined_date DESC').all();

		res.json({ success: true, members: results });
	} catch (error) {
		res.status(500).json({ success: false, error: 'Failed to fetch members' });
	}
});

// GET a single member by ID
app.get('/api/members/:id', async (req, res) => {
	try {
		const { id } = req.params;

		const { results } = await env.DB.prepare('SELECT * FROM members WHERE id = ?').bind(id).all();

		if (results.length === 0) {
			return res.status(404).json({ success: false, error: 'Member not found' });
		}

		res.json({ success: true, member: results[0] });
	} catch (error) {
		res.status(500).json({ success: false, error: 'Failed to fetch member' });
	}
});

これらのルートは、D1 バインディング(env.DB)で SQL 文を準備して実行します。ファイル先頭で cloudflare:workers から env をインポートしているため、アプリケーション全体で使えます。D1 バインディングの preparebindall メソッドで、データベースを安全に照会できます。利用できるメソッドの一覧は D1 Workers Binding API を参照してください。

7. 作成操作を実装する

新しいメンバーを作成するエンドポイントを追加します。次のルートを src/index.ts に追加します。

src/index.tsts
// POST - Create a new member
app.post("/api/members", async (req, res) => {
  try {
    const { name, email } = req.body;

    // Validate input
    if (!name || !email) {
      return res.status(400).json({
        success: false,
        error: "Name and email are required",
      });
    }

    // Basic email validation (simplified for tutorial purposes)
    // For production, consider using a validation library or more comprehensive checks
    if (!email.includes("@") || !email.includes(".")) {
      return res.status(400).json({
        success: false,
        error: "Invalid email format",
      });
    }

    const joined_date = new Date().toISOString().split("T")[0];

    const result = await env.DB.prepare(
      "INSERT INTO members (name, email, joined_date) VALUES (?, ?, ?)"
    )
      .bind(name, email, joined_date)
      .run();

    if (result.success) {
      res.status(201).json({
        success: true,
        message: "Member created successfully",
        id: result.meta.last_row_id,
      });
    } else {
      res
        .status(500)
        .json({ success: false, error: "Failed to create member" });
    }
  } catch (error: any) {
    // Handle unique constraint violation
    if (error.message?.includes("UNIQUE constraint failed")) {
      return res.status(409).json({
        success: false,
        error: "Email already exists",
      });
    }
    res.status(500).json({ success: false, error: "Failed to create member" });
  }
});

このエンドポイントは入力を検証し、メール形式を確認して、データベースに新しいメンバーを挿入します。一意制約違反を確認し、重複したメールアドレスも処理します。

8. 更新操作を実装する

既存メンバーを更新するエンドポイントを追加します。次のルートを src/index.ts に追加します。

src/index.tsts
app.put("/api/members/:id", async (req, res) => {
  try {
    const { id } = req.params;
    const { name, email } = req.body;

    // Validate input
    if (!name && !email) {
      return res.status(400).json({
        success: false,
        error: "At least one field (name or email) is required",
      });
    }

    // Basic email validation if provided (simplified for tutorial purposes)
    // For production, consider using a validation library or more comprehensive checks
    if (email && (!email.includes("@") || !email.includes("."))) {
      return res.status(400).json({
        success: false,
        error: "Invalid email format",
      });
    }

    // Build dynamic update query
    const updates: string[] = [];
    const values: any[] = [];

    if (name) {
      updates.push("name = ?");
      values.push(name);
    }
    if (email) {
      updates.push("email = ?");
      values.push(email);
    }

    values.push(id);

    const result = await env.DB.prepare(
      `UPDATE members SET ${updates.join(", ")} WHERE id = ?`
    )
      .bind(...values)
      .run();

    if (result.meta.changes === 0) {
      return res
        .status(404)
        .json({ success: false, error: "Member not found" });
    }

    res.json({ success: true, message: "Member updated successfully" });
  } catch (error: any) {
    if (error.message?.includes("UNIQUE constraint failed")) {
      return res.status(409).json({
        success: false,
        error: "Email already exists",
      });
    }
    res.status(500).json({ success: false, error: "Failed to update member" });
  }
});

このエンドポイントは、既存メンバーの名前、メール、またはその両方を更新できます。指定されたフィールドに応じて、動的な SQL クエリを組み立てます。

9. 削除操作を実装する

メンバーを削除するエンドポイントを追加します。次のルートを src/index.ts に追加します。

src/index.tsts
// DELETE - Delete a member
app.delete("/api/members/:id", async (req, res) => {
  try {
    const { id } = req.params;

    const result = await env.DB.prepare("DELETE FROM members WHERE id = ?")
      .bind(id)
      .run();

    if (result.meta.changes === 0) {
      return res
        .status(404)
        .json({ success: false, error: "Member not found" });
    }

    res.json({ success: true, message: "Member deleted successfully" });
  } catch (error) {
    res.status(500).json({ success: false, error: "Failed to delete member" });
  }
});

このエンドポイントは、ID でメンバーを削除します。メンバーが存在しない場合はエラーを返します。

10. ローカルでテストする

開発サーバーを起動し、API をローカルでテストします。

npm run dev

開発サーバーが起動し、http://localhost:8787 で API にアクセスできます。

新しいターミナルウィンドウを開き、curl でエンドポイントをテストします。

すべてのメンバーを取得するsh
curl http://localhost:8787/api/members
{
	"success": true,
	"members": [
		{
			"id": 1,
			"name": "Alice Johnson",
			"email": "alice@example.com",
			"joined_date": "2024-01-15"
		},
		{
			"id": 2,
			"name": "Bob Smith",
			"email": "bob@example.com",
			"joined_date": "2024-02-20"
		},
		{
			"id": 3,
			"name": "Carol Williams",
			"email": "carol@example.com",
			"joined_date": "2024-03-10"
		}
	]
}

新しいメンバーの作成をテストします。

メンバーを作成するsh
curl -X POST http://localhost:8787/api/members \
  -H "Content-Type: application/json" \
  -d '{"name": "David Brown", "email": "david@example.com"}'
{
	"success": true,
	"message": "Member created successfully",
	"id": 4
}

1 件のメンバー取得をテストします。

ID でメンバーを取得するsh
curl http://localhost:8787/api/members/1

メンバーの更新をテストします。

メンバーを更新するsh
curl -X PUT http://localhost:8787/api/members/1 \
  -H "Content-Type: application/json" \
  -d '{"name": "Alice Cooper"}'

メンバーの削除をテストします。

メンバーを削除するsh
curl -X DELETE http://localhost:8787/api/members/4

11. Cloudflare Workers へデプロイする

本番へデプロイする前に、リモート(本番)データベースに対してスキーマファイルを実行します。

npx wrangler d1 execute members-db --remote --file=./schemas/schema.sql

アプリケーションを Cloudflare のネットワークへデプロイします。

npm run deploy
⛅️ wrangler 4.44.0
───────────────────
Total Upload: 1743.64 KiB / gzip: 498.65 KiB
Worker Startup Time: 48 ms
Your Worker has access to the following bindings:
Binding                  Resource
env.DB (members-db)      D1 Database

Uploaded express-d1-app (2.99 sec)
Deployed express-d1-app triggers (5.26 sec)
  https://<your-subdomain>.workers.dev
Current Version ID: <version-id>

デプロイが成功すると、Wrangler は Worker の URL を出力します。

12. 本番デプロイをテストする

提供された URL で、デプロイした API をテストします。<your-worker-url> を実際の Worker URL に置き換えます。

本番 API をテストするsh
curl https://<your-worker-url>/api/members

本番データベースに作成したのと同じメンバーデータが表示されるはずです。

本番で新しいメンバーを作成します。

本番でメンバーを作成するsh
curl -X POST https://<your-worker-url>/api/members \
  -H "Content-Type: application/json" \
  -d '{"name": "Eva Martinez", "email": "eva@example.com"}'

D1 データベース付きの Express.js アプリケーションが、Cloudflare Workers 上で動作しています。

まとめ

このチュートリアルでは、Express.js と D1 データベースでメンバー登録 API を作り、Cloudflare Workers にデプロイしました。CRUD(作成・読み取り・更新・削除)を一通り実装し、次を学びました。

  • Cloudflare Workers 向けに Express.js アプリケーションをセットアップする
  • バインディング付きの D1 データベースを作成して設定する
  • D1 のプリペアドステートメントでデータベース操作を実装する
  • API をローカルと本番でテストする

次のステップ

役に立ちましたか?