Cloudflare Workers

サーバーレスCloudflareEdge ComputingWebAssemblyJavaScriptTypeScript

プラットフォーム

Cloudflare Workers

概要

Cloudflare Workersは、世界最大級のエッジネットワーク上で動作するサーバーレス実行環境として、V8 JavaScriptエンジンをベースにした超高速で高可用性のコンピューティングプラットフォームです。全世界330以上のロケーションでの分散実行により、リクエスト処理を地理的に最適な場所で実行し、レイテンシーを大幅に削減します。2025年現在、WebAssembly対応、TypeScript強化、Containers統合(予定)、そしてDurable Objects、R2 Storage、D1 Databaseなどの包括的なエッジコンピューティングエコシステムにより、従来のサーバーレスの概念を革新する次世代プラットフォームとして急速に成長しています。

詳細

Cloudflare Workers 2025年版は、V8 Isolate技術による0ms冷却時間と高度な分離性を実現し、従来のコンテナベースのサーバーレスプラットフォームとは一線を画すパフォーマンスを提供しています。特に注目すべきは、2025年中頃に予定されているContainers統合により、従来のNode.js環境やDockerコンテナをそのままエッジで実行可能になることです。Wrangler CLIによる洗練された開発体験、Durable Objectsによるステートフル処理、R2 Storageでのオブジェクトストレージ、D1 Databaseでのエッジデータベース、そしてHonoなどのWebフレームワークとの完全統合により、フルスタックアプリケーションをエッジ上で構築できる完全なプラットフォームを提供します。

主な特徴

  • Edge Computing: 世界330+ロケーションでの分散実行
  • V8 Isolate: 0ms冷却時間と高速起動
  • WebAssembly対応: Rust、Go、C++などの言語サポート
  • Durable Objects: ステートフルエッジコンピューティング
  • R2 Storage: S3互換オブジェクストレージ
  • D1 Database: エッジSQLiteデータベース

2025年の最新機能

  • Containers統合: Docker/Node.jsコンテナのエッジ実行(2025年中頃予定)
  • Enhanced Wrangler: 改良されたCLIとデプロイメント体験
  • Advanced Analytics: リアルタイム分析とパフォーマンス監視
  • WebAssembly最適化: WASM実行の高速化と新言語サポート
  • Framework統合: Hono、Next.js、Astro等との深い統合

メリット・デメリット

メリット

  • 世界最高水準のエッジネットワークによる超低遅延
  • V8 Isolate技術による0ms冷却時間と高速実行
  • 自動スケーリングと高可用性の実現
  • WebAssembly対応による多言語サポート
  • 包括的なエッジコンピューティングエコシステム
  • 優れた開発者体験とWrangler CLIの洗練度
  • 競争力のある料金体系と無料利用枠

デメリット

  • 実行時間制限(CPU時間:10ms〜50ms、壁時間:15分)
  • メモリ制約(128MB)による処理制限
  • Node.js互換性の一部制限(ただし2025年Containers統合で改善予定)
  • 複雑なデータベース操作やファイルI/Oの制約
  • エコシステムが比較的新しく、サードパーティツールが限定的
  • デバッグとローカル開発の複雑さ

参考ページ

書き方の例

セットアップと基本設定

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

# Cloudflareアカウントへの認証
wrangler login

# 新しいWorkerプロジェクトの作成
wrangler init my-worker

# ローカル開発サーバーの起動
wrangler dev

# プロダクションへのデプロイ
wrangler deploy
// src/index.js - 基本的なWorker
export default {
    async fetch(request, env, ctx) {
        // リクエスト情報の取得
        const url = new URL(request.url);
        const userAgent = request.headers.get('User-Agent');
        const cfRay = request.headers.get('CF-Ray');
        const country = request.cf?.country || 'Unknown';
        
        // CORS ヘッダーの設定
        const corsHeaders = {
            'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
            'Access-Control-Allow-Headers': 'Content-Type, Authorization',
        };
        
        // プリフライトリクエストの処理
        if (request.method === 'OPTIONS') {
            return new Response(null, {
                status: 200,
                headers: corsHeaders,
            });
        }
        
        try {
            // ルーティング処理
            switch (request.method) {
                case 'GET':
                    return handleGet(request, env);
                case 'POST':
                    return handlePost(request, env);
                default:
                    return new Response('Method not allowed', { 
                        status: 405,
                        headers: corsHeaders 
                    });
            }
        } catch (error) {
            console.error('Error processing request:', error);
            return new Response(JSON.stringify({ 
                error: 'Internal server error',
                ray: cfRay 
            }), {
                status: 500,
                headers: {
                    'Content-Type': 'application/json',
                    ...corsHeaders
                }
            });
        }
    }
};

async function handleGet(request, env) {
    const url = new URL(request.url);
    const path = url.pathname;
    
    if (path === '/api/info') {
        return new Response(JSON.stringify({
            message: 'Hello from Cloudflare Workers!',
            timestamp: new Date().toISOString(),
            location: request.cf?.colo,
            country: request.cf?.country,
            ray: request.headers.get('CF-Ray')
        }), {
            headers: { 'Content-Type': 'application/json' }
        });
    }
    
    return new Response('Not found', { status: 404 });
}

async function handlePost(request, env) {
    const body = await request.json();
    
    return new Response(JSON.stringify({
        received: body,
        processed_at: new Date().toISOString()
    }), {
        headers: { 'Content-Type': 'application/json' }
    });
}

HTTP APIとルーティング

// src/index.ts - TypeScript + Hono Framework
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { logger } from 'hono/logger';
import { jwt } from 'hono/jwt';

type Bindings = {
    DB: D1Database;
    BUCKET: R2Bucket;
    JWT_SECRET: string;
    API_KEY: string;
};

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

// ミドルウェアの設定
app.use('*', cors());
app.use('*', logger());

// 認証が必要なルートの設定
app.use('/api/protected/*', async (c, next) => {
    const jwtMiddleware = jwt({
        secret: c.env.JWT_SECRET,
    });
    return jwtMiddleware(c, next);
});

// パブリックAPI
app.get('/api/health', (c) => {
    return c.json({
        status: 'healthy',
        timestamp: new Date().toISOString(),
        worker_location: c.req.header('CF-Ray')?.split('-')[1] || 'unknown',
        country: c.req.header('CF-IPCountry') || 'unknown'
    });
});

// RESTful API
app.get('/api/users/:id', async (c) => {
    const userId = c.req.param('id');
    
    // D1データベースからユーザー情報を取得
    const user = await c.env.DB.prepare(
        'SELECT * FROM users WHERE id = ?'
    ).bind(userId).first();
    
    if (!user) {
        return c.json({ error: 'User not found' }, 404);
    }
    
    return c.json(user);
});

app.post('/api/users', async (c) => {
    const userData = await c.req.json();
    
    const result = await c.env.DB.prepare(
        'INSERT INTO users (name, email, created_at) VALUES (?, ?, ?)'
    ).bind(
        userData.name,
        userData.email,
        new Date().toISOString()
    ).run();
    
    return c.json({ 
        id: result.meta.last_row_id,
        message: 'User created successfully' 
    }, 201);
});

// 保護されたAPI
app.get('/api/protected/profile', async (c) => {
    const payload = c.get('jwtPayload');
    return c.json({ 
        user_id: payload.sub,
        message: 'This is a protected endpoint' 
    });
});

export default app;

データベース統合とデータ処理

// データベース操作(D1 Database)
app.get('/api/posts', async (c) => {
    try {
        // ページネーション
        const page = parseInt(c.req.query('page') || '1');
        const limit = parseInt(c.req.query('limit') || '10');
        const offset = (page - 1) * limit;
        
        // D1でのSQLクエリ実行
        const posts = await c.env.DB.prepare(`
            SELECT 
                id, title, content, author, created_at, updated_at
            FROM posts 
            ORDER BY created_at DESC 
            LIMIT ? OFFSET ?
        `).bind(limit, offset).all();
        
        const totalCount = await c.env.DB.prepare(
            'SELECT COUNT(*) as count FROM posts'
        ).first();
        
        return c.json({
            posts: posts.results,
            pagination: {
                page,
                limit,
                total: totalCount.count,
                pages: Math.ceil(totalCount.count / limit)
            }
        });
    } catch (error) {
        console.error('Database error:', error);
        return c.json({ error: 'Database error' }, 500);
    }
});

// R2ストレージとの統合
app.post('/api/upload', async (c) => {
    try {
        const formData = await c.req.formData();
        const file = formData.get('file') as File;
        
        if (!file) {
            return c.json({ error: 'No file provided' }, 400);
        }
        
        // ファイル名の生成
        const fileName = `uploads/${Date.now()}-${file.name}`;
        
        // R2にファイルをアップロード
        await c.env.BUCKET.put(fileName, file.stream(), {
            httpMetadata: {
                contentType: file.type
            }
        });
        
        // データベースにファイル情報を保存
        await c.env.DB.prepare(`
            INSERT INTO files (name, original_name, size, content_type, uploaded_at)
            VALUES (?, ?, ?, ?, ?)
        `).bind(
            fileName,
            file.name,
            file.size,
            file.type,
            new Date().toISOString()
        ).run();
        
        return c.json({
            message: 'File uploaded successfully',
            fileName,
            size: file.size
        });
    } catch (error) {
        console.error('Upload error:', error);
        return c.json({ error: 'Upload failed' }, 500);
    }
});

認証とセキュリティ

// JWT認証システム
import { sign, verify } from 'hono/jwt';
import { createHash } from 'crypto';

// ログイン機能
app.post('/api/auth/login', async (c) => {
    try {
        const { email, password } = await c.req.json();
        
        // ユーザー認証
        const user = await c.env.DB.prepare(
            'SELECT id, email, password_hash FROM users WHERE email = ?'
        ).bind(email).first();
        
        if (!user) {
            return c.json({ error: 'Invalid credentials' }, 401);
        }
        
        // パスワード検証
        const passwordHash = createHash('sha256')
            .update(password + c.env.JWT_SECRET)
            .digest('hex');
            
        if (passwordHash !== user.password_hash) {
            return c.json({ error: 'Invalid credentials' }, 401);
        }
        
        // JWTトークン生成
        const token = await sign({
            sub: user.id,
            email: user.email,
            exp: Math.floor(Date.now() / 1000) + (60 * 60 * 24) // 24時間
        }, c.env.JWT_SECRET);
        
        return c.json({ 
            token,
            user: {
                id: user.id,
                email: user.email
            }
        });
    } catch (error) {
        console.error('Login error:', error);
        return c.json({ error: 'Login failed' }, 500);
    }
});

// Rate Limiting (Durable Objects使用)
app.use('/api/*', async (c, next) => {
    const clientIP = c.req.header('CF-Connecting-IP') || 'unknown';
    const rateLimitKey = `rate_limit:${clientIP}`;
    
    // KVからレート制限情報を取得
    const requests = await c.env.KV.get(rateLimitKey);
    const requestCount = requests ? parseInt(requests) : 0;
    
    if (requestCount >= 100) { // 1時間あたり100リクエスト
        return c.json({ error: 'Rate limit exceeded' }, 429);
    }
    
    // リクエストカウントを更新
    await c.env.KV.put(rateLimitKey, (requestCount + 1).toString(), {
        expirationTtl: 3600 // 1時間
    });
    
    await next();
});

イベント駆動アーキテクチャ

// Durable Objectsを使用したステートフル処理
export class ChatRoom {
    constructor(private state: DurableObjectState, private env: Env) {}
    
    async fetch(request: Request): Promise<Response> {
        const url = new URL(request.url);
        
        if (url.pathname === '/websocket') {
            return this.handleWebSocket(request);
        }
        
        return new Response('Not found', { status: 404 });
    }
    
    async handleWebSocket(request: Request): Promise<Response> {
        const [client, server] = new WebSocketPair();
        
        server.accept();
        
        // 接続された WebSocket を管理
        server.addEventListener('message', async (event) => {
            const message = JSON.parse(event.data);
            
            // メッセージをブロードキャスト
            this.state.getWebSockets().forEach((ws) => {
                ws.send(JSON.stringify({
                    type: 'message',
                    data: message,
                    timestamp: new Date().toISOString()
                }));
            });
        });
        
        this.state.acceptWebSocket(server);
        
        return new Response(null, {
            status: 101,
            webSocket: client,
        });
    }
}

// バックグラウンド処理とスケジュール
export default {
    async scheduled(controller: ScheduledController, env: Env, ctx: ExecutionContext): Promise<void> {
        // 定期実行処理
        console.log('Scheduled task executing at:', new Date().toISOString());
        
        // データベースクリーンアップ
        await env.DB.prepare(`
            DELETE FROM sessions 
            WHERE expires_at < datetime('now')
        `).run();
        
        // R2ストレージの一時ファイル削除
        const objects = await env.BUCKET.list({
            prefix: 'temp/',
        });
        
        for (const object of objects.objects) {
            const ageMs = Date.now() - object.uploaded.getTime();
            if (ageMs > 24 * 60 * 60 * 1000) { // 24時間以上古い
                await env.BUCKET.delete(object.key);
            }
        }
    },
    
    // Queue処理
    async queue(batch: MessageBatch<any>, env: Env): Promise<void> {
        for (const message of batch.messages) {
            try {
                await processQueueMessage(message.body, env);
                message.ack();
            } catch (error) {
                console.error('Queue processing error:', error);
                message.retry();
            }
        }
    }
};

async function processQueueMessage(data: any, env: Env): Promise<void> {
    // 非同期メッセージ処理
    switch (data.type) {
        case 'email':
            await sendEmail(data.payload, env);
            break;
        case 'webhook':
            await processWebhook(data.payload, env);
            break;
        default:
            console.warn('Unknown message type:', data.type);
    }
}

監視とパフォーマンス最適化

// カスタムメトリクスとロギング
app.use('*', async (c, next) => {
    const start = Date.now();
    
    await next();
    
    const duration = Date.now() - start;
    const status = c.res.status;
    const method = c.req.method;
    const path = c.req.path;
    
    // カスタムメトリクス送信
    console.log(JSON.stringify({
        type: 'access_log',
        method,
        path,
        status,
        duration,
        timestamp: new Date().toISOString(),
        ray: c.req.header('CF-Ray'),
        country: c.req.header('CF-IPCountry')
    }));
    
    // Analytics Engine への送信
    if (c.env.ANALYTICS) {
        c.env.ANALYTICS.writeDataPoint({
            blobs: [method, path],
            doubles: [duration],
            indexes: [status.toString()]
        });
    }
});

// エラーハンドリングとアラート
app.onError((err, c) => {
    console.error('Application error:', {
        error: err.message,
        stack: err.stack,
        url: c.req.url,
        method: c.req.method,
        ray: c.req.header('CF-Ray'),
        timestamp: new Date().toISOString()
    });
    
    return c.json({ 
        error: 'Internal server error',
        ray: c.req.header('CF-Ray')
    }, 500);
});

// ヘルスチェックとパフォーマンス監視
app.get('/api/metrics', async (c) => {
    const metrics = {
        timestamp: new Date().toISOString(),
        worker_info: {
            location: c.req.header('CF-Ray')?.split('-')[1] || 'unknown',
            country: c.req.header('CF-IPCountry') || 'unknown',
            colo: c.req.header('CF-Colo') || 'unknown'
        },
        system_info: {
            memory_usage: 'not_available', // Workers では利用不可
            uptime: 'not_applicable' // ステートレス
        },
        services: {
            database: await checkDatabase(c.env.DB),
            storage: await checkStorage(c.env.BUCKET),
            kv: await checkKV(c.env.KV)
        }
    };
    
    return c.json(metrics);
});

async function checkDatabase(db: D1Database): Promise<string> {
    try {
        await db.prepare('SELECT 1').first();
        return 'healthy';
    } catch (error) {
        return 'unhealthy';
    }
}

async function checkStorage(bucket: R2Bucket): Promise<string> {
    try {
        await bucket.head('health-check');
        return 'healthy';
    } catch (error) {
        return 'healthy'; // ファイルが存在しないのは正常
    }
}

async function checkKV(kv: KVNamespace): Promise<string> {
    try {
        await kv.get('health-check');
        return 'healthy';
    } catch (error) {
        return 'unhealthy';
    }
}