Edge Functions Development for Websites (Cloudflare Workers)
Cloudflare Workers are edge functions running on 300+ Cloudflare PoP nodes. Unlike Vercel Edge, Workers run on your domain as part of the CDN: every HTTP request to yourdomain.com can be intercepted, modified, or fully handled by a Worker without hitting the origin server.
Architectural Patterns
Workers as API Gateway — Worker receives requests, performs authentication, rate limiting, routing, and proxies to the right backend.
Workers as Middleware — modify requests/responses: add headers, transform body, redirect by geolocation.
Workers as full API — complete logic in Worker with storage in KV/D1/R2. Origin server not needed.
Example: Rate Limiting and Authentication
import { Hono } from "hono";
import { jwt } from "hono/jwt";
const app = new Hono<{ Bindings: Env }>();
// Rate limiting via KV
app.use("*", async (c, next) => {
const ip = c.req.header("CF-Connecting-IP") || "unknown";
const key = `rate:${ip}`;
const count = parseInt(await c.env.KV.get(key) || "0");
if (count > 100) {
return c.json({ error: "Too many requests" }, 429);
}
await c.env.KV.put(key, String(count + 1), { expirationTtl: 60 });
return next();
});
// JWT protection for /api/*
app.use("/api/*", jwt({ secret: (c) => c.env.JWT_SECRET }));
app.get("/api/user/:id", async (c) => {
const { id } = c.req.param();
const user = await c.env.DB.prepare("SELECT * FROM users WHERE id = ?").bind(id).first();
if (!user) return c.json({ error: "Not found" }, 404);
return c.json(user);
});
export default app;
Hono is a lightweight router optimized for edge environments (Cloudflare Workers, Deno Deploy, Bun).
Response Transformation from Origin
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const response = await fetch(request);
// Add security headers to all responses
const newHeaders = new Headers(response.headers);
newHeaders.set("X-Content-Type-Options", "nosniff");
newHeaders.set("X-Frame-Options", "SAMEORIGIN");
newHeaders.set("Referrer-Policy", "strict-origin-when-cross-origin");
newHeaders.set(
"Content-Security-Policy",
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
);
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: newHeaders,
});
},
};
Geolocation and Content Personalization
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const cf = (request as any).cf as CfProperties;
const url = new URL(request.url);
// Redirect to localized site
const countryLangMap: Record<string, string> = {
RU: "ru", UA: "uk", DE: "de", FR: "fr",
};
const lang = countryLangMap[cf.country || ""] || "en";
if (!url.pathname.match(/^\/(ru|uk|de|fr|en)\//)) {
return Response.redirect(
`${url.origin}/${lang}${url.pathname}${url.search}`,
302
);
}
// Pass geo data to origin via headers
const modifiedRequest = new Request(request, {
headers: {
...Object.fromEntries(request.headers),
"X-Country": cf.country || "",
"X-City": cf.city || "",
"X-Timezone": cf.timezone || "",
},
});
return fetch(modifiedRequest);
},
};
R2: File Storage Without Egress Fee
Cloudflare R2 is S3-compatible storage without egress charges.
app.post("/upload", async (c) => {
const formData = await c.req.formData();
const file = formData.get("file") as File;
const key = `uploads/${Date.now()}-${file.name}`;
await c.env.R2.put(key, file.stream(), {
httpMetadata: { contentType: file.type },
});
return c.json({ url: `https://cdn.yourdomain.com/${key}` });
});
app.get("/files/:key{.+}", async (c) => {
const key = c.req.param("key");
const object = await c.env.R2.get(key);
if (!object) return c.json({ error: "Not found" }, 404);
return new Response(object.body, {
headers: {
"Content-Type": object.httpMetadata?.contentType || "application/octet-stream",
"Cache-Control": "public, max-age=31536000",
},
});
});
Durable Objects: State on Edge
For tasks requiring consistent state (counters, WebSocket rooms, distributed locks):
export class RateLimiter implements DurableObject {
private requests = 0;
async fetch(request: Request): Promise<Response> {
this.requests++;
if (this.requests > 1000) {
return new Response("Rate limited", { status: 429 });
}
// Reset counter after 1 minute
setTimeout(() => { this.requests = 0; }, 60000);
return new Response("OK");
}
}
Timeframe
Worker with routing and rate limiting — 2–3 days. Full API with D1, KV, R2, and CI/CD — 5–6 days.







