Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Cloudflare platform skill covering Workers, D1, R2, KV, AI, Durable Objects, and security.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/pages-functions/patterns.md
1# Common Patterns23## Background Tasks (waitUntil)45Non-blocking tasks after response sent (analytics, cleanup, webhooks):67```typescript8export async function onRequest(ctx: EventContext<Env>) {9const res = Response.json({ success: true });1011ctx.waitUntil(ctx.env.KV.put('last-visit', new Date().toISOString()));12ctx.waitUntil(Promise.all([13ctx.env.ANALYTICS.writeDataPoint({ event: 'view' }),14fetch('https://webhook.site/...', { method: 'POST' })15]));1617return res; // Returned immediately18}19```2021## Middleware & Auth2223```typescript24// functions/_middleware.js (global) or functions/users/_middleware.js (scoped)25export async function onRequest(ctx) {26try { return await ctx.next(); }27catch (err) { return new Response(err.message, { status: 500 }); }28}2930// Chained: export const onRequest = [errorHandler, auth, logger];3132// Auth33async function auth(ctx: EventContext<Env>) {34const token = ctx.request.headers.get('authorization')?.replace('Bearer ', '');35if (!token) return new Response('Unauthorized', { status: 401 });36const session = await ctx.env.KV.get(`session:${token}`);37if (!session) return new Response('Invalid', { status: 401 });38ctx.data.user = JSON.parse(session);39return ctx.next();40}41```4243## CORS & Rate Limiting4445```typescript46// CORS middleware47const cors = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST' };48export async function onRequestOptions() { return new Response(null, { headers: cors }); }49export async function onRequest(ctx) {50const res = await ctx.next();51Object.entries(cors).forEach(([k, v]) => res.headers.set(k, v));52return res;53}5455// Rate limiting (KV-based)56async function rateLimit(ctx: EventContext<Env>) {57const ip = ctx.request.headers.get('CF-Connecting-IP') || 'unknown';58const count = parseInt(await ctx.env.KV.get(`rate:${ip}`) || '0');59if (count >= 100) return new Response('Rate limited', { status: 429 });60await ctx.env.KV.put(`rate:${ip}`, (count + 1).toString(), { expirationTtl: 3600 });61return ctx.next();62}63```6465## Forms, Caching, Redirects6667```typescript68// JSON & file upload69export async function onRequestPost(ctx) {70const ct = ctx.request.headers.get('content-type') || '';71if (ct.includes('application/json')) return Response.json(await ctx.request.json());72if (ct.includes('multipart/form-data')) {73const file = (await ctx.request.formData()).get('file') as File;74await ctx.env.BUCKET.put(file.name, file.stream());75return Response.json({ uploaded: file.name });76}77}7879// Cache API80export async function onRequest(ctx) {81let res = await caches.default.match(ctx.request);82if (!res) {83res = new Response('Data');84res.headers.set('Cache-Control', 'public, max-age=3600');85ctx.waitUntil(caches.default.put(ctx.request, res.clone()));86}87return res;88}8990// Redirects91export async function onRequest(ctx) {92if (new URL(ctx.request.url).pathname === '/old') {93return Response.redirect(new URL('/new', ctx.request.url), 301);94}95return ctx.next();96}97```9899## Testing100101**Unit tests** (Vitest + cloudflare:test):102```typescript103import { env } from 'cloudflare:test';104import { it, expect } from 'vitest';105import { onRequest } from '../functions/api';106107it('returns JSON', async () => {108const req = new Request('http://localhost/api');109const ctx = { request: req, env, params: {}, data: {} } as EventContext;110const res = await onRequest(ctx);111expect(res.status).toBe(200);112});113```114115**Integration:** `wrangler pages dev` + Playwright/Cypress116117## Advanced Mode (_worker.js)118119Use `_worker.js` for complex routing (replaces `/functions`):120121```typescript122interface Env { ASSETS: Fetcher; KV: KVNamespace; }123124export default {125async fetch(request: Request, env: Env): Promise<Response> {126const url = new URL(request.url);127if (url.pathname.startsWith('/api/')) {128return Response.json({ data: await env.KV.get('key') });129}130return env.ASSETS.fetch(request); // Static files131}132} satisfies ExportedHandler<Env>;133```134135**When:** Existing Worker, framework-generated (Next.js/SvelteKit), custom routing logic136137**See also:** [api.md](./api.md) for `env.ASSETS.fetch()` | [gotchas.md](./gotchas.md) for debugging138