Cloudflare Workers入門|エッジコンピューティングでAPIを高速化する方法

kento_morota 22分で読めます

「APIのレスポンスが遅くてユーザー体験が悪い」「海外からのアクセスに対応したいが、サーバーを各地に立てるのはコストが高い」——こうした課題に対して、Cloudflare Workersはエッジコンピューティングというアプローチで解決策を提供します。

Cloudflare Workersは、世界300以上のデータセンターでJavaScriptを実行できるサーバーレスプラットフォームです。ユーザーに最も近い場所でコードが実行されるため、従来のサーバーと比べて圧倒的に低いレイテンシを実現します。

本記事では、Cloudflare Workersの基本概念から実践的なAPI高速化の実装まで、段階的に解説します。

エッジコンピューティングとCloudflare Workersの基礎

エッジコンピューティングとは、処理をエンドユーザーに地理的に近い場所(エッジ)で実行する技術です。従来のクラウドコンピューティングでは、東京や米国のデータセンターにあるサーバーで処理を行っていました。エッジコンピューティングでは、世界中に分散されたエッジサーバーで処理を実行します。

Cloudflare Workersは、Cloudflareのグローバルネットワーク上でJavaScript(およびWebAssembly)を実行するプラットフォームです。V8エンジン(ChromeやNode.jsと同じJavaScriptエンジン)をベースにしており、コールドスタートが非常に高速です。

従来のサーバーレスとの違い

AWS Lambdaなどの従来のサーバーレスプラットフォームと比較したCloudflare Workersの特徴は以下の通りです。

コールドスタートの速さ
AWS Lambdaではコールドスタートに数百ms〜数秒かかることがありますが、Cloudflare WorkersはV8 Isolateモデルにより、コールドスタートが通常5ms以下です。

グローバル分散
従来のサーバーレスは特定のリージョン(例:ap-northeast-1)で実行されますが、Workersはデプロイするだけで世界300以上のロケーションに自動で分散されます。

Web標準API
Workersは、Service Workers APIやFetch APIなどのWeb標準に準拠しています。ブラウザのJavaScriptに近い書き方ができるため、フロントエンド開発者にとって馴染みやすいです。

料金体系

Freeプラン:1日10万リクエストまで無料、CPU時間10ms/リクエスト。個人プロジェクトやプロトタイプに十分です。

Standard プラン(月額5ドル〜):1,000万リクエスト/月含む、CPU時間30ms/リクエスト。超過分は従量課金です。

開発環境のセットアップ

Cloudflare Workersの開発には、Wranglerというコマンドラインツールを使用します。

プロジェクトの作成

# Wranglerのインストール
npm install -g wrangler

# Cloudflareアカウントへのログイン
wrangler login

# 新規プロジェクトの作成
npm create cloudflare@latest my-worker

# プロジェクト構成
my-worker/
├── src/
│   └── index.ts      # ワーカーのエントリーポイント
├── wrangler.toml      # 設定ファイル
├── package.json
└── tsconfig.json

基本的なWorkerの構造

// src/index.ts - 最もシンプルなWorker
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const url = new URL(request.url);

    // ルーティング
    if (url.pathname === '/api/hello') {
      return new Response(JSON.stringify({ message: 'Hello from the edge!' }), {
        headers: { 'Content-Type': 'application/json' },
      });
    }

    if (url.pathname === '/api/time') {
      return new Response(JSON.stringify({
        timestamp: new Date().toISOString(),
        cf: request.cf, // Cloudflareが付加するリクエスト情報
      }), {
        headers: { 'Content-Type': 'application/json' },
      });
    }

    return new Response('Not Found', { status: 404 });
  },
};

interface Env {
  // 環境変数やバインディングの型定義
}
# ローカルでの開発サーバー起動
wrangler dev

# 本番へのデプロイ
wrangler deploy

wrangler devコマンドでローカル開発サーバーが起動し、ホットリロードで開発できます。デプロイはwrangler deployの一発で、世界中のエッジに配信されます。

実践ユースケース1:APIプロキシとキャッシュ

Cloudflare Workersの代表的なユースケースとして、外部APIのプロキシとエッジキャッシュの実装を見てみましょう。

外部APIのキャッシュプロキシ

外部APIのレスポンスをエッジでキャッシュし、同じリクエストに対してはキャッシュから高速に応答する仕組みです。

// src/index.ts - APIキャッシュプロキシ
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const url = new URL(request.url);

    if (url.pathname.startsWith('/api/products')) {
      return handleProductsAPI(request, env, ctx);
    }

    return new Response('Not Found', { status: 404 });
  },
};

async function handleProductsAPI(
  request: Request,
  env: Env,
  ctx: ExecutionContext
): Promise<Response> {
  // Cache APIを使ったキャッシュ
  const cache = caches.default;
  const cacheKey = new Request(request.url, request);

  // キャッシュにヒットした場合はそのまま返す
  let response = await cache.match(cacheKey);
  if (response) {
    return new Response(response.body, {
      ...response,
      headers: {
        ...Object.fromEntries(response.headers),
        'X-Cache': 'HIT',
      },
    });
  }

  // キャッシュミスの場合、オリジンAPIにリクエスト
  const originResponse = await fetch('https://api.example.com/products', {
    headers: {
      'Authorization': `Bearer ${env.API_TOKEN}`,
    },
  });

  // レスポンスのクローンを作成(bodyは一度しか読めないため)
  response = new Response(originResponse.body, {
    status: originResponse.status,
    headers: {
      'Content-Type': 'application/json',
      'Cache-Control': 'public, max-age=300', // 5分キャッシュ
      'X-Cache': 'MISS',
    },
  });

  // バックグラウンドでキャッシュに保存
  ctx.waitUntil(cache.put(cacheKey, response.clone()));

  return response;
}

このパターンにより、オリジンAPIへのリクエスト数を大幅に削減しつつ、ユーザーにはエッジから高速にレスポンスを返せます。

実践ユースケース2:KV・D1・R2との連携

Cloudflareは、Workers向けのストレージサービスを複数提供しています。用途に応じて使い分けましょう。

Workers KV:キーバリューストア

KVは、グローバルに分散されたキーバリューストアです。読み取りが非常に高速(エッジでキャッシュ)で、設定情報やセッションデータの保存に適しています。

# KVネームスペースの作成
wrangler kv namespace create "MY_KV"
// wrangler.toml
[[kv_namespaces]]
binding = "MY_KV"
id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
// KVの読み書き
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);

    if (request.method === 'GET' && url.pathname === '/api/config') {
      // KVから読み取り
      const config = await env.MY_KV.get('site-config', 'json');
      return Response.json(config || { theme: 'default' });
    }

    if (request.method === 'PUT' && url.pathname === '/api/config') {
      const body = await request.json();
      // KVに書き込み(TTL: 1時間)
      await env.MY_KV.put('site-config', JSON.stringify(body), {
        expirationTtl: 3600,
      });
      return Response.json({ success: true });
    }

    return new Response('Not Found', { status: 404 });
  },
};

interface Env {
  MY_KV: KVNamespace;
}

D1:SQLiteデータベース

D1は、Cloudflareが提供するサーバーレスSQLiteデータベースです。SQLが使える本格的なリレーショナルデータベースをエッジで利用できます。

# D1データベースの作成
wrangler d1 create my-database

# マイグレーションの作成
wrangler d1 migrations create my-database init
-- migrations/0001_init.sql
CREATE TABLE articles (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  title TEXT NOT NULL,
  slug TEXT UNIQUE NOT NULL,
  content TEXT NOT NULL,
  published_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_articles_slug ON articles(slug);
CREATE INDEX idx_articles_published ON articles(published_at);
// D1データベースの操作
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);

    if (url.pathname === '/api/articles') {
      const { results } = await env.DB.prepare(
        'SELECT id, title, slug, published_at FROM articles ORDER BY published_at DESC LIMIT 20'
      ).all();
      return Response.json({ articles: results });
    }

    if (url.pathname.startsWith('/api/articles/')) {
      const slug = url.pathname.split('/').pop();
      const article = await env.DB.prepare(
        'SELECT * FROM articles WHERE slug = ?'
      ).bind(slug).first();

      if (!article) {
        return Response.json({ error: 'Not found' }, { status: 404 });
      }
      return Response.json({ article });
    }

    return new Response('Not Found', { status: 404 });
  },
};

interface Env {
  DB: D1Database;
}

R2:オブジェクトストレージ

R2は、S3互換のオブジェクトストレージで、エグレス料金(データ転送料金)が無料という大きな特徴があります。画像やファイルの保存に最適です。

// R2へのファイルアップロード
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    if (request.method === 'PUT' && request.url.includes('/upload/')) {
      const url = new URL(request.url);
      const key = url.pathname.replace('/upload/', '');
      const contentType = request.headers.get('Content-Type') || 'application/octet-stream';

      await env.MY_BUCKET.put(key, request.body, {
        httpMetadata: { contentType },
      });

      return Response.json({ key, url: `/files/${key}` });
    }

    if (request.method === 'GET' && request.url.includes('/files/')) {
      const url = new URL(request.url);
      const key = url.pathname.replace('/files/', '');
      const object = await env.MY_BUCKET.get(key);

      if (!object) {
        return new Response('Not Found', { status: 404 });
      }

      return new Response(object.body, {
        headers: {
          'Content-Type': object.httpMetadata?.contentType || 'application/octet-stream',
          'Cache-Control': 'public, max-age=86400',
        },
      });
    }

    return new Response('Not Found', { status: 404 });
  },
};

interface Env {
  MY_BUCKET: R2Bucket;
}

実践ユースケース3:Honoフレームワークの活用

本格的なAPIをWorkersで構築する場合、Honoフレームワークの利用がおすすめです。HonoはCloudflare Workers向けに最適化された軽量Webフレームワークです。

npm create hono@latest my-api
// src/index.ts - Honoを使ったAPI
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { cache } from 'hono/cache';
import { jwt } from 'hono/jwt';

type Bindings = {
  DB: D1Database;
  MY_KV: KVNamespace;
  JWT_SECRET: string;
};

const app = new Hono<{ Bindings: Bindings }>();

// CORSミドルウェア
app.use('/api/*', cors({
  origin: ['https://example.com', 'https://staging.example.com'],
  allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
}));

// 公開API:キャッシュ付き
app.get('/api/articles',
  cache({ cacheName: 'articles', cacheControl: 'max-age=300' }),
  async (c) => {
    const { results } = await c.env.DB.prepare(
      'SELECT id, title, slug, published_at FROM articles ORDER BY published_at DESC LIMIT 20'
    ).all();
    return c.json({ articles: results });
  }
);

// 認証が必要なAPI
app.use('/api/admin/*', jwt({ secret: 'JWT_SECRET' }));

app.post('/api/admin/articles', async (c) => {
  const { title, slug, content } = await c.req.json();
  await c.env.DB.prepare(
    'INSERT INTO articles (title, slug, content) VALUES (?, ?, ?)'
  ).bind(title, slug, content).run();
  return c.json({ success: true }, 201);
});

export default app;

Honoを使うことで、ルーティング、ミドルウェア、バリデーション、認証などの機能を整理された形で実装できます。

デプロイと運用のベストプラクティス

Workersを本番運用する際のベストプラクティスを紹介します。

環境の分離

# wrangler.toml - 本番とステージングの分離
name = "my-api"
main = "src/index.ts"
compatibility_date = "2026-03-01"

# 本番環境の設定
[env.production]
vars = { ENVIRONMENT = "production" }
d1_databases = [
  { binding = "DB", database_name = "my-db-prod", database_id = "xxx" }
]

# ステージング環境の設定
[env.staging]
vars = { ENVIRONMENT = "staging" }
d1_databases = [
  { binding = "DB", database_name = "my-db-staging", database_id = "yyy" }
]
# 環境を指定してデプロイ
wrangler deploy --env production
wrangler deploy --env staging

エラーハンドリングとロギング

// グローバルエラーハンドリング
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    try {
      return await handleRequest(request, env, ctx);
    } catch (error) {
      // エラーログの送信(Logpushや外部サービスに送信)
      console.error('Unhandled error:', error);

      ctx.waitUntil(
        fetch('https://logging.example.com/errors', {
          method: 'POST',
          body: JSON.stringify({
            message: error instanceof Error ? error.message : 'Unknown error',
            url: request.url,
            timestamp: new Date().toISOString(),
          }),
        })
      );

      return Response.json(
        { error: 'Internal Server Error' },
        { status: 500 }
      );
    }
  },
};

レート制限の実装

// KVを使ったシンプルなレート制限
async function rateLimit(request: Request, env: Env): Promise<boolean> {
  const ip = request.headers.get('CF-Connecting-IP') || 'unknown';
  const key = `rate:${ip}:${Math.floor(Date.now() / 60000)}`; // 1分単位

  const count = parseInt(await env.MY_KV.get(key) || '0');
  if (count >= 60) { // 1分あたり60リクエストまで
    return false;
  }

  await env.MY_KV.put(key, String(count + 1), { expirationTtl: 120 });
  return true;
}

まとめ

Cloudflare Workersは、エッジコンピューティングの力を手軽に活用できるプラットフォームです。本記事のポイントを振り返ります。

・Workersは世界300以上のロケーションでJavaScriptを実行し、低レイテンシを実現する
・コールドスタート5ms以下で、従来のサーバーレスより圧倒的に高速
・KV(キーバリュー)、D1(SQLite)、R2(オブジェクトストレージ)でデータを永続化できる
・Honoフレームワークを使えば、本格的なAPIを効率的に構築可能
・APIプロキシ・キャッシュ、画像最適化、A/Bテストなど多彩なユースケースに対応
・無料枠が充実しており、小規模プロジェクトならコストゼロで運用可能

まずはFreeプランでシンプルなAPIを作成し、エッジコンピューティングの速さを体感してみてください。wrangler devによるローカル開発とwrangler deployによるワンコマンドデプロイの体験は、開発者にとって非常に快適です。

#Cloudflare#エッジコンピューティング#Workers
共有:
無料メルマガ

週1回、最新の技術記事をお届け

AI・クラウド・開発の最新記事を毎週月曜にメールでお届けします。登録は無料、いつでも解除できます。

プライバシーポリシーに基づき管理します

AI活用のヒントをお探しですか?お気軽にご相談ください。

まずは話だけ聞いてもらう