Initial commit
This commit is contained in:
418
skills/devops/references/cloudflare-workers-basics.md
Normal file
418
skills/devops/references/cloudflare-workers-basics.md
Normal file
@@ -0,0 +1,418 @@
|
||||
# Cloudflare Workers Basics
|
||||
|
||||
Getting started with Cloudflare Workers: serverless functions that run on edge network across 300+ cities.
|
||||
|
||||
## Handler Types
|
||||
|
||||
### Fetch Handler (HTTP Requests)
|
||||
```typescript
|
||||
export default {
|
||||
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
||||
return new Response('Hello World!');
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Scheduled Handler (Cron Jobs)
|
||||
```typescript
|
||||
export default {
|
||||
async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext): Promise<void> {
|
||||
await fetch('https://api.example.com/cleanup');
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**Configure in wrangler.toml:**
|
||||
```toml
|
||||
[triggers]
|
||||
crons = ["0 0 * * *"] # Daily at midnight
|
||||
```
|
||||
|
||||
### Queue Handler (Message Processing)
|
||||
```typescript
|
||||
export default {
|
||||
async queue(batch: MessageBatch, env: Env, ctx: ExecutionContext): Promise<void> {
|
||||
for (const message of batch.messages) {
|
||||
await processMessage(message.body);
|
||||
message.ack(); // Acknowledge success
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Email Handler (Email Routing)
|
||||
```typescript
|
||||
export default {
|
||||
async email(message: ForwardableEmailMessage, env: Env, ctx: ExecutionContext): Promise<void> {
|
||||
await message.forward('destination@example.com');
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Request/Response Basics
|
||||
|
||||
### Parsing Request
|
||||
```typescript
|
||||
const url = new URL(request.url);
|
||||
const method = request.method;
|
||||
const headers = request.headers;
|
||||
|
||||
// Query parameters
|
||||
const name = url.searchParams.get('name');
|
||||
|
||||
// JSON body
|
||||
const data = await request.json();
|
||||
|
||||
// Text body
|
||||
const text = await request.text();
|
||||
|
||||
// Form data
|
||||
const formData = await request.formData();
|
||||
```
|
||||
|
||||
### Creating Response
|
||||
```typescript
|
||||
// Text response
|
||||
return new Response('Hello', { status: 200 });
|
||||
|
||||
// JSON response
|
||||
return new Response(JSON.stringify({ message: 'Hello' }), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
|
||||
// Stream response
|
||||
return new Response(readable, {
|
||||
headers: { 'Content-Type': 'text/plain' }
|
||||
});
|
||||
|
||||
// Redirect
|
||||
return Response.redirect('https://example.com', 302);
|
||||
```
|
||||
|
||||
## Routing Patterns
|
||||
|
||||
### URL-Based Routing
|
||||
```typescript
|
||||
export default {
|
||||
async fetch(request: Request): Promise<Response> {
|
||||
const url = new URL(request.url);
|
||||
|
||||
switch (url.pathname) {
|
||||
case '/':
|
||||
return new Response('Home');
|
||||
case '/about':
|
||||
return new Response('About');
|
||||
default:
|
||||
return new Response('Not Found', { status: 404 });
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Using Hono Framework (Recommended)
|
||||
```typescript
|
||||
import { Hono } from 'hono';
|
||||
|
||||
const app = new Hono();
|
||||
|
||||
app.get('/', (c) => c.text('Home'));
|
||||
app.get('/api/users/:id', async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const user = await getUser(id);
|
||||
return c.json(user);
|
||||
});
|
||||
|
||||
export default app;
|
||||
```
|
||||
|
||||
## Working with Bindings
|
||||
|
||||
### Environment Variables
|
||||
```toml
|
||||
# wrangler.toml
|
||||
[vars]
|
||||
API_URL = "https://api.example.com"
|
||||
```
|
||||
|
||||
```typescript
|
||||
const apiUrl = env.API_URL;
|
||||
```
|
||||
|
||||
### KV Namespace
|
||||
```typescript
|
||||
// Put with TTL
|
||||
await env.KV.put('session:token', JSON.stringify(data), {
|
||||
expirationTtl: 3600
|
||||
});
|
||||
|
||||
// Get
|
||||
const data = await env.KV.get('session:token', 'json');
|
||||
|
||||
// Delete
|
||||
await env.KV.delete('session:token');
|
||||
|
||||
// List with prefix
|
||||
const list = await env.KV.list({ prefix: 'user:123:' });
|
||||
```
|
||||
|
||||
### D1 Database
|
||||
```typescript
|
||||
// Query
|
||||
const result = await env.DB.prepare(
|
||||
'SELECT * FROM users WHERE id = ?'
|
||||
).bind(userId).first();
|
||||
|
||||
// Insert
|
||||
await env.DB.prepare(
|
||||
'INSERT INTO users (name, email) VALUES (?, ?)'
|
||||
).bind('Alice', 'alice@example.com').run();
|
||||
|
||||
// Batch (atomic)
|
||||
await env.DB.batch([
|
||||
env.DB.prepare('UPDATE accounts SET balance = balance - 100 WHERE id = ?').bind(1),
|
||||
env.DB.prepare('UPDATE accounts SET balance = balance + 100 WHERE id = ?').bind(2)
|
||||
]);
|
||||
```
|
||||
|
||||
### R2 Bucket
|
||||
```typescript
|
||||
// Put object
|
||||
await env.R2_BUCKET.put('path/to/file.jpg', fileBuffer, {
|
||||
httpMetadata: {
|
||||
contentType: 'image/jpeg'
|
||||
}
|
||||
});
|
||||
|
||||
// Get object
|
||||
const object = await env.R2_BUCKET.get('path/to/file.jpg');
|
||||
if (!object) {
|
||||
return new Response('Not found', { status: 404 });
|
||||
}
|
||||
|
||||
// Stream response
|
||||
return new Response(object.body, {
|
||||
headers: {
|
||||
'Content-Type': object.httpMetadata?.contentType || 'application/octet-stream'
|
||||
}
|
||||
});
|
||||
|
||||
// Delete
|
||||
await env.R2_BUCKET.delete('path/to/file.jpg');
|
||||
```
|
||||
|
||||
## Context API
|
||||
|
||||
### waitUntil (Background Tasks)
|
||||
```typescript
|
||||
export default {
|
||||
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
||||
// Run analytics after response sent
|
||||
ctx.waitUntil(
|
||||
fetch('https://analytics.example.com/log', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ url: request.url })
|
||||
})
|
||||
);
|
||||
|
||||
return new Response('OK');
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### passThroughOnException
|
||||
```typescript
|
||||
// Continue to origin on error
|
||||
ctx.passThroughOnException();
|
||||
|
||||
// Your code that might throw
|
||||
const data = await riskyOperation();
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```typescript
|
||||
export default {
|
||||
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
||||
try {
|
||||
const response = await processRequest(request, env);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
|
||||
// Log to external service
|
||||
ctx.waitUntil(
|
||||
fetch('https://logging.example.com/error', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
error: error.message,
|
||||
url: request.url
|
||||
})
|
||||
})
|
||||
);
|
||||
|
||||
return new Response('Internal Server Error', { status: 500 });
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## CORS
|
||||
|
||||
```typescript
|
||||
function corsHeaders(origin: string) {
|
||||
return {
|
||||
'Access-Control-Allow-Origin': origin,
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
'Access-Control-Max-Age': '86400'
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(request: Request): Promise<Response> {
|
||||
const origin = request.headers.get('Origin') || '*';
|
||||
|
||||
// Handle preflight
|
||||
if (request.method === 'OPTIONS') {
|
||||
return new Response(null, { headers: corsHeaders(origin) });
|
||||
}
|
||||
|
||||
// Handle request
|
||||
const response = await handleRequest(request);
|
||||
const headers = new Headers(response.headers);
|
||||
Object.entries(corsHeaders(origin)).forEach(([key, value]) => {
|
||||
headers.set(key, value);
|
||||
});
|
||||
|
||||
return new Response(response.body, {
|
||||
status: response.status,
|
||||
headers
|
||||
});
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Cache API
|
||||
|
||||
```typescript
|
||||
export default {
|
||||
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
||||
const cache = caches.default;
|
||||
const cacheKey = new Request(request.url);
|
||||
|
||||
// Check cache
|
||||
let response = await cache.match(cacheKey);
|
||||
if (response) return response;
|
||||
|
||||
// Fetch from origin
|
||||
response = await fetch(request);
|
||||
|
||||
// Cache response
|
||||
ctx.waitUntil(cache.put(cacheKey, response.clone()));
|
||||
|
||||
return response;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Secrets Management
|
||||
|
||||
```bash
|
||||
# Add secret
|
||||
wrangler secret put API_KEY
|
||||
# Enter value when prompted
|
||||
|
||||
# Use in Worker
|
||||
const apiKey = env.API_KEY;
|
||||
```
|
||||
|
||||
## Local Development
|
||||
|
||||
```bash
|
||||
# Start local dev server
|
||||
wrangler dev
|
||||
|
||||
# Test with remote edge
|
||||
wrangler dev --remote
|
||||
|
||||
# Custom port
|
||||
wrangler dev --port 8080
|
||||
|
||||
# Access at http://localhost:8787
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
```bash
|
||||
# Deploy to production
|
||||
wrangler deploy
|
||||
|
||||
# Deploy to specific environment
|
||||
wrangler deploy --env staging
|
||||
|
||||
# Preview deployment
|
||||
wrangler deploy --dry-run
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### API Gateway
|
||||
```typescript
|
||||
import { Hono } from 'hono';
|
||||
|
||||
const app = new Hono();
|
||||
|
||||
app.get('/api/users', async (c) => {
|
||||
const users = await c.env.DB.prepare('SELECT * FROM users').all();
|
||||
return c.json(users.results);
|
||||
});
|
||||
|
||||
app.post('/api/users', async (c) => {
|
||||
const { name, email } = await c.req.json();
|
||||
await c.env.DB.prepare(
|
||||
'INSERT INTO users (name, email) VALUES (?, ?)'
|
||||
).bind(name, email).run();
|
||||
return c.json({ success: true }, 201);
|
||||
});
|
||||
|
||||
export default app;
|
||||
```
|
||||
|
||||
### Rate Limiting
|
||||
```typescript
|
||||
async function rateLimit(ip: string, env: Env): Promise<boolean> {
|
||||
const key = `ratelimit:${ip}`;
|
||||
const limit = 100;
|
||||
const window = 60;
|
||||
|
||||
const current = await env.KV.get(key);
|
||||
const count = current ? parseInt(current) : 0;
|
||||
|
||||
if (count >= limit) return false;
|
||||
|
||||
await env.KV.put(key, (count + 1).toString(), {
|
||||
expirationTtl: window
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(request: Request, env: Env): Promise<Response> {
|
||||
const ip = request.headers.get('CF-Connecting-IP') || 'unknown';
|
||||
|
||||
if (!await rateLimit(ip, env)) {
|
||||
return new Response('Rate limit exceeded', { status: 429 });
|
||||
}
|
||||
|
||||
return new Response('OK');
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- Docs: https://developers.cloudflare.com/workers/
|
||||
- Examples: https://developers.cloudflare.com/workers/examples/
|
||||
- Runtime APIs: https://developers.cloudflare.com/workers/runtime-apis/
|
||||
Reference in New Issue
Block a user