Steven's Knowledge

Getting Started

Deploy your first Cloudflare Worker - request handler, environment, secrets, deploy globally in 30 seconds

Getting Started

The fastest first edge function: Cloudflare Workers. Free tier, single CLI command to deploy, runs in 300+ POPs.

Install Wrangler (the CLI)

npm install -g wrangler

wrangler login    # opens a browser to authenticate

Create a Worker

mkdir my-worker && cd my-worker
npm create cloudflare@latest -- --type=hello-world

Pick "Hello World example" → "JavaScript" → "Yes" to deploy. The scaffolding looks like:

my-worker/
├── src/
│   └── index.js
├── wrangler.toml
├── package.json

src/index.js:

export default {
  async fetch(request, env, ctx) {
    return new Response('Hello, edge!', {
      headers: { 'content-type': 'text/plain' },
    });
  },
};

Run Locally

wrangler dev

# In another terminal:
curl http://localhost:8787
# Hello, edge!

wrangler dev runs the same V8 runtime locally that runs at the edge — fidelity is excellent.

Deploy Globally

wrangler deploy

In ~10 seconds your Worker is live in 300+ POPs at https://my-worker.<your-subdomain>.workers.dev.

curl https://my-worker.example.workers.dev
# Hello, edge!

Visit that URL from anywhere in the world — the request lands in the nearest POP.

A More Useful Handler

Workers run on Request/Response — Web standard APIs. The pattern is:

export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);

    // Routing
    if (url.pathname === '/health') {
      return new Response('ok');
    }

    if (url.pathname === '/api/echo') {
      const body = await request.text();
      return Response.json({
        method: request.method,
        path: url.pathname,
        headers: Object.fromEntries(request.headers),
        body,
      });
    }

    // Pass-through to your origin
    const originUrl = new URL(url.pathname, 'https://origin.example.com');
    return fetch(originUrl, request);
  },
};

request.headers, request.cf (Cloudflare-specific request metadata like country, timezone), fetch() for outbound — same APIs as the browser.

Bind Storage and Services

wrangler.toml declares bindings — KV namespaces, Durable Objects, D1 databases, R2 buckets, queues, secrets:

name = "my-worker"
main = "src/index.js"
compatibility_date = "2025-05-21"

# KV namespace
[[kv_namespaces]]
binding = "CACHE"
id = "abc123..."

# D1 database (SQLite at the edge)
[[d1_databases]]
binding = "DB"
database_name = "myapp"
database_id = "xyz789..."

# R2 bucket (S3-compatible)
[[r2_buckets]]
binding = "ASSETS"
bucket_name = "my-assets"

# Secrets — set via `wrangler secret put`, not in this file

Bindings become properties on the env parameter:

export default {
  async fetch(request, env, ctx) {
    // KV
    const cached = await env.CACHE.get('key');
    await env.CACHE.put('key', 'value', { expirationTtl: 300 });

    // D1
    const { results } = await env.DB.prepare(
      'SELECT * FROM users WHERE id = ?'
    ).bind(42).all();

    // R2
    const file = await env.ASSETS.get('photo.jpg');

    return Response.json({ cached, users: results });
  },
};

Secrets

Plaintext config goes in wrangler.toml; secrets are set separately:

echo "my-api-key" | wrangler secret put MY_API_KEY
# now env.MY_API_KEY is available at runtime

Secrets are encrypted at rest and never appear in logs.

Trigger by Schedule

Cron-like triggers:

[triggers]
crons = ["0 6 * * *"]   # daily at 06:00 UTC
export default {
  async scheduled(event, env, ctx) {
    await env.CACHE.put('last-run', new Date().toISOString());
  },
  async fetch(request, env, ctx) { /* ... */ },
};

Custom Domains

Workers default to *.workers.dev. To bind to your own domain:

  1. Add your domain to Cloudflare (DNS).
  2. In wrangler.toml:
routes = [
  { pattern = "api.example.com/*", custom_domain = true },
]
  1. wrangler deploy.

Now api.example.com/* is handled by your Worker.

Limits to Know

LimitFree planPaid plan
CPU time10 ms/request30 s/request
Memory128 MB128 MB
Subrequests (fetch from worker)501000
Script size1 MB10 MB
KV operations100k reads/dayPer-operation pricing
D1 storage / queriesGenerous free tierPer-operation pricing

10 ms CPU per request is plenty for routing, auth, header rewrites — not enough for heavy computation. That's by design.

Other Edge Platforms

The same handler shape works on:

  • Vercel Edge Functionsexport default async function (request) { ... } in app/api/route.ts with export const runtime = 'edge'.
  • Deno Deploy — top-level Deno.serve() or addEventListener('fetch', ...).
  • Netlify Edge Functions — same as Deno Deploy.
  • Fastly Compute@Edge — Rust / JS / AssemblyScript; more involved tooling, faster.

Different deploy commands, similar code.

What's Next

You can write and deploy an edge function. Next:

  • Patterns — auth, A/B testing, geo-routing, edge-side rendering, KV storage
  • Best Practices — limits, observability, secret management, cost

On this page