メインコンテンツまでスキップ

Cloudflare Workersではじめる

このガイドでは、Cloudflare Workers上でZeltアプリケーションをゼロから構築する手順を説明します。

前提条件

  • Node.js v20以上(またはBun v1.0以上)
  • パッケージマネージャ: pnpm(推奨)、npm、またはbun

Cloudflare Workersでは追加で以下が必要です:

インストール

pnpm add @zeltjs/core @zeltjs/adapter-cloudflare-workers
pnpm add -D wrangler @cloudflare/workers-types

プロジェクト構成

my-app/
├── src/
│   ├── entry/
│   │   ├── controllers/    # HTTPエンドポイント
│   │   └── commands/       # CLIコマンド
│   ├── services/           # ビジネスロジック
│   ├── configs/            # 設定クラス
│   ├── app.ts              # アプリケーション定義
│   ├── cli.ts              # CLIエントリポイント
│   └── main.ts             # HTTPサーバーエントリポイント
├── package.json
└── tsconfig.json
ディレクトリ用途
entry/外部向けエントリポイント(HTTP、CLI)
services/ビジネスロジック、DIで注入される
configs/環境変数と設定

Cloudflare Workersの場合、プロジェクトルートに wrangler.toml も追加します。

Hello World

Step 1: Controllerを作成する

Controllerは受信したHTTPリクエストを処理し、レスポンスを返します。各controllerは @Controller でデコレートされたクラスで、ルートのprefixを定義します。

src/entry/controllers/hello.controller.ts を作成します:

@Controller('/hello')
export class HelloController {
  @Get('/:name')
  greet(req = request()) {
    const name = req.pathParam('name');
    return { message: `Hello, ${name}!` };
  }
}
  • @Controller('/hello') — このcontroller内の全ルートの基本パスを設定します
  • @Get('/:name')/hello/:name へのGETリクエストを処理します
  • req.pathParam('name') — URLパスから name パラメータを取り出します

Step 2: アプリケーションを作成する

src/app.ts を作成し、controllerを結線します:

export const app = createApp([http({
    controllers: [HelloController],
  })]);

Step 3: Workerのentryを作成する

Cloudflare Workersのentryとして src/index.ts を作成します:

const workers = await onCloudflareWorkers(app);

export default { fetch: workers.fetch };

onCloudflareWorkers() 関数は非同期で、アプリをWorkersランタイム用に準備します。返されるオブジェクトには fetch ハンドラに加えて、shutdown やserviceにアクセスするための get などのユーティリティが含まれます。デフォルトでは遅延初期化(warmup: false)が使われます — controllerは起動時ではなく最初のリクエストで解決されます。これによりサーバーレス環境でのコールドスタート時間が最適化されます。

Step 4: Wranglerを設定する

wrangler.toml を作成します:

name = "my-zelt-worker"
main = "src/index.ts"
compatibility_date = "2024-01-01"
compatibility_flags = ["nodejs_compat"]

[vars]
API_HOST = "https://api.example.com"

Step 5: TypeScriptを設定する

tsconfig.json を作成します:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "experimentalDecorators": true,
    "skipLibCheck": true,
    "types": ["@cloudflare/workers-types"]
  },
  "include": ["src"]
}

Step 6: ローカルで実行する

npx wrangler dev

http://localhost:8787/hello/world にアクセスすると、次のように表示されます:

{ "message": "Hello, world!" }

設定

環境変数

Cloudflare Workersでは、環境変数は wrangler.toml で設定し、Env 経由でアクセスします。

@Controller('/config')
export class ConfigController {
  constructor(private env = inject(Env)) {}

  @Get('/api-host')
  getApiHost() {
    return { apiHost: this.env.getString('API_HOST', 'localhost') };
  }
}
export const app = createApp([http({
    controllers: [ConfigController],
  })]);

重要: onCloudflareWorkers() はenv adaptorを自動で登録するため、inject(Env) は追加設定なしにWorkersランタイム(cloudflare:workers モジュール)から環境変数を読み取ります。

Secrets

機密情報には [vars] の代わりにWrangler secretsを使います:

npx wrangler secret put DATABASE_URL

アクセス方法は同じく Env 経由です:

  get connectionUrl() {
    return this.env.getString('DATABASE_URL', '');
  }
}

Cloudflare Bindings

Wranglerのbinding(D1、KV、R2、Durable Objectsなど)は Env で公開される文字列のみの環境変数とは別に、fetch(request, env, ctx)env として渡されます。wrangler types が生成する型でこれらにアクセスするには CloudflareBindings を使います:

@Controller('/data')
export class DataController {
  constructor(private bindings = inject(CloudflareBindings)) {}

  @Get('/:key')
  async get(req = request()) {
    const value = await this.bindings.get('CACHE').get(req.pathParam('key'));
    return { value };
  }
}

bindings.get('CACHE') は拡張されたグローバルな Env から型をそのまま読み取るため、手動での型付けなしに KVNamespace(あるいは宣言したbindingに応じて D1DatabaseR2Bucket など)として型付けされます。

重要: CloudflareBindings.get()onCloudflareWorkers() によって設定されるリクエストスコープのストレージから読み取るため、リクエスト処理中でのみ動作します — リクエスト外(例えばモジュール読み込み時)で呼び出すとエラーになります。

Services

ServiceはNode.jsとまったく同じ仕組みで動作します。クラスをserviceとしてマークするには @Injectable を使います。

@Injectable()
export class GreetingService {
  greet(name: string): string {
    return `Hello, ${name}!`;
  }
}

controllerに注入します:

@Controller('/hello')
export class HelloController {
  constructor(private greetingService = inject(GreetingService)) {}

  @Get('/:name')
  greet(req = request()) {
    const name = req.pathParam('name');
    return { message: this.greetingService.greet(name) };
  }
}

デプロイ

WorkerをCloudflareのグローバルネットワークにデプロイします:

npx wrangler deploy

Workerは https://my-zelt-worker.<your-subdomain>.workers.dev で利用できるようになります。

応用: Warmupオプション

デフォルトでは、onCloudflareWorkers() はコールドスタート時間を最小化するために遅延初期化(warmup: false)を使用します。controllerは最初のリクエストで解決されます。

初期化時に全controllerを解決したい場合(デバッグ時や、コールドスタート時間があまり重要でない場合に有用)は warmup: true を設定します:

const workers = await onCloudflareWorkers(app, { warmup: true });

export default { fetch: workers.fetch };
オプション挙動用途
warmup: false(デフォルト)controllerは最初のリクエストで解決されるコールドスタートの最適化
warmup: true全controllerが初期化時に解決されるデバッグ、ウォームな環境

次のステップ

基本的なworkerが動くようになったので、他の機能も見てみましょう:

  • Controllers — ルーティングとHTTPメソッド
  • Services — ビジネスロジックと依存性注入
  • Validation — Valibotによるリクエストボディのバリデーション
  • Middleware — リクエスト/レスポンスのインターセプタ
  • Configuration — 高度な設定パターン