Cloudflare Workers
プラットフォーム
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';
}
}