このチュートリアルでは、Cloudflare Workers プラットフォーム と D1 データベース を使い、Express.js ↗ アプリケーションを Cloudflare Workers にデプロイします。作成・読み取り・更新・削除(CRUD)の基本操作を持つメンバー登録 API を作ります。メンバーデータの保存と取得には、データベースとして D1 を使います。
すべてのチュートリアルは、Cloudflare Workers アカウント、C3 ↗、および Wrangler のセットアップが完了している前提です。セットアップは Get started ガイド で行います。
手順を飛ばしてすぐに始めたい場合は、下の Deploy to Cloudflare を選びます。
GitHub アカウントにリポジトリが作成され、アプリケーションが Cloudflare Workers にデプロイされます。Cloudflare Workers に慣れていて、手順ごとの案内を飛ばしたい場合に使います。
Cloudflare Workers が初めてなら、手順を手で追うことをおすすめします。
Cloudflare の開発者向け製品用のコマンドラインツール C3 ↗ を使い、新しいディレクトリを作成して Worker プロジェクトを初期化します。
npm create cloudflare@latest -- express-d1-appyarn create cloudflare express-d1-apppnpm 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このチュートリアルでは、Node.js 向けの人気フレームワーク Express.js ↗ を使います。Cloudflare Workers 環境で Express を使うため、Express と必要な TypeScript の型をインストールします。
npm i express @types/expressyarn add express @types/expresspnpm add express @types/expressbun add express @types/expressCloudflare Workers 上の Express.js には、nodejs_compat 互換性フラグ が必要です。このフラグは Node.js API を有効にし、Workers ランタイムで Express を動かせます。Wrangler 設定ファイルに次を追加します。
{
"compatibility_flags": [
"nodejs_compat"
]
}compatibility_flags = [ "nodejs_compat" ]メンバー情報を保存する 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>"プロジェクトのルートに schemas ディレクトリを作成し、その中に schema.sql ファイルを作成します。
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', '[email protected]', '2024-01-15'),
('Bob Smith', '[email protected]', '2024-02-20'),
('Carol Williams', '[email protected]', '2024-03-10');このスキーマは、自動増分の ID、名前、メール、参加日のフィールドを持つ members テーブルを作成します。サンプルのメンバーも 3 件挿入します。
スキーマファイルを D1 データベースに対して実行します。
npx wrangler d1 execute members-db --file=./schemas/schema.sql上のコマンドは、ローカル開発用データベースにテーブルを作成します。本番へのスキーマのデプロイは後で行います。
src/index.ts を更新し、TypeScript で Express をセットアップします。ファイルの内容を次に置き換えます。
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データベースからメンバーを取得するエンドポイントを追加します。ヘルスチェック用エンドポイントのあとに、次のルートを src/index.ts へ追加します。
// 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 バインディングの prepare、bind、all メソッドで、データベースを安全に照会できます。利用できるメソッドの一覧は D1 Workers Binding API を参照してください。
新しいメンバーを作成するエンドポイントを追加します。次のルートを src/index.ts に追加します。
// 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" });
}
});このエンドポイントは入力を検証し、メール形式を確認して、データベースに新しいメンバーを挿入します。一意制約違反を確認し、重複したメールアドレスも処理します。
既存メンバーを更新するエンドポイントを追加します。次のルートを src/index.ts に追加します。
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 クエリを組み立てます。
メンバーを削除するエンドポイントを追加します。次のルートを src/index.ts に追加します。
// 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 でメンバーを削除します。メンバーが存在しない場合はエラーを返します。
開発サーバーを起動し、API をローカルでテストします。
npm run dev開発サーバーが起動し、http://localhost:8787 で API にアクセスできます。
新しいターミナルウィンドウを開き、curl でエンドポイントをテストします。
curl http://localhost:8787/api/members{
"success": true,
"members": [
{
"id": 1,
"name": "Alice Johnson",
"email": "[email protected]",
"joined_date": "2024-01-15"
},
{
"id": 2,
"name": "Bob Smith",
"email": "[email protected]",
"joined_date": "2024-02-20"
},
{
"id": 3,
"name": "Carol Williams",
"email": "[email protected]",
"joined_date": "2024-03-10"
}
]
}新しいメンバーの作成をテストします。
curl -X POST http://localhost:8787/api/members \
-H "Content-Type: application/json" \
-d '{"name": "David Brown", "email": "[email protected]"}'{
"success": true,
"message": "Member created successfully",
"id": 4
}1 件のメンバー取得をテストします。
curl http://localhost:8787/api/members/1メンバーの更新をテストします。
curl -X PUT http://localhost:8787/api/members/1 \
-H "Content-Type: application/json" \
-d '{"name": "Alice Cooper"}'メンバーの削除をテストします。
curl -X DELETE http://localhost:8787/api/members/4本番へデプロイする前に、リモート(本番)データベースに対してスキーマファイルを実行します。
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 を出力します。
提供された URL で、デプロイした API をテストします。<your-worker-url> を実際の Worker URL に置き換えます。
curl https://<your-worker-url>/api/members本番データベースに作成したのと同じメンバーデータが表示されるはずです。
本番で新しいメンバーを作成します。
curl -X POST https://<your-worker-url>/api/members \
-H "Content-Type: application/json" \
-d '{"name": "Eva Martinez", "email": "[email protected]"}'D1 データベース付きの Express.js アプリケーションが、Cloudflare Workers 上で動作しています。
このチュートリアルでは、Express.js と D1 データベースでメンバー登録 API を作り、Cloudflare Workers にデプロイしました。CRUD(作成・読み取り・更新・削除)を一通り実装し、次を学びました。
- Cloudflare Workers 向けに Express.js アプリケーションをセットアップする
- バインディング付きの D1 データベースを作成して設定する
- D1 のプリペアドステートメントでデータベース操作を実装する
- API をローカルと本番でテストする
- D1 データベースの機能 を詳しく見る
- Workers のルーティングとミドルウェア を調べる
- Workers の認証 で API に認証を追加する
- D1 のクエリ最適化 で大規模データセットのページネーションを実装する