Back to blog feed

Cloud & DevOps

Wrangler Explained: Deploying Edge Applications to Cloudflare

A practical guide to Wrangler CLI: multi-environment configuration, KV and D1 database bindings, secret management, and fast edge deployments.

Wrangler Explained: Deploying Edge Applications to Cloudflare

Wrangler Explained: Deploying Edge Applications to Cloudflare

Deploying backend logic close to users used to require managing distributed server clusters or dealing with slow cold starts on traditional container lambdas. Cloudflare Workers changed this by running code on V8 isolates distributed across hundreds of edge locations.

Wrangler is the official CLI used to develop, test, and deploy Workers, Pages, and edge storage primitives.

Here is a practical guide to using Wrangler effectively in production.


Setting Up wrangler.toml for Multiple Environments

A clean wrangler.toml configuration defines your entry points, bindings, and environment overrides:

name = "api-gateway"
main = "src/index.ts"
compatibility_date = "2026-03-01"
compatibility_flags = ["nodejs_compat"]

# Production environment (default)
[vars]
ENVIRONMENT = "production"
ALLOWED_ORIGIN = "https://yourdomain.com"

# Key-Value storage binding
[[kv_namespaces]]
binding = "CACHE_KV"
id = "a1b2c3d4e5f67890123456789abcdef0"

# Cloudflare D1 (Serverless SQLite) binding
[[d1_databases]]
binding = "DB"
database_name = "prod-db"
database_id = "f9e8d7c6-b5a4-3210-9876-543210fedcba"

# Staging environment override
[env.staging]
name = "api-gateway-staging"
vars = { ENVIRONMENT = "staging", ALLOWED_ORIGIN = "https://staging.yourdomain.com" }

[[env.staging.kv_namespaces]]
binding = "CACHE_KV"
id = "b2c3d4e5f6a17890123456789abcdef1"

Writing an Edge Worker with TypeScript

With nodejs_compat enabled, you get standard Node-compatible APIs alongside native web primitives (Request, Response, fetch):

export interface Env {
  CACHE_KV: KVNamespace;
  DB: D1Database;
  ENVIRONMENT: string;
  ALLOWED_ORIGIN: string;
  API_SECRET_KEY: string; // Injected via wrangler secret
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const url = new URL(request.url);

    // Handle CORS preflight
    if (request.method === "OPTIONS") {
      return new Response(null, {
        headers: {
          "Access-Control-Allow-Origin": env.ALLOWED_ORIGIN,
          "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
          "Access-Control-Allow-Headers": "Content-Type, Authorization",
        },
      });
    }

    // Edge-cached endpoint
    if (url.pathname === "/v1/summary" && request.method === "GET") {
      const cacheKey = `summary:${url.searchParams.get("id") || "default"}`;
      
      // 1. Check Cloudflare KV (sub-15ms edge lookup)
      const cached = await env.CACHE_KV.get(cacheKey);
      if (cached) {
        return new Response(cached, {
          headers: {
            "Content-Type": "application/json",
            "X-Cache": "HIT",
            "Access-Control-Allow-Origin": env.ALLOWED_ORIGIN,
          },
        });
      }

      // 2. Query Cloudflare D1 serverless database on cache miss
      const id = url.searchParams.get("id") || "1";
      const record = await env.DB.prepare(
        "SELECT id, title, content FROM posts WHERE id = ?"
      ).bind(id).first();

      if (!record) {
        return new Response(JSON.stringify({ error: "Not found" }), {
          status: 404,
          headers: { "Content-Type": "application/json" }
        });
      }

      const payload = JSON.stringify(record);

      // 3. Populate edge KV asynchronously without blocking user response
      ctx.waitUntil(env.CACHE_KV.put(cacheKey, payload, { expirationTtl: 3600 }));

      return new Response(payload, {
        headers: {
          "Content-Type": "application/json",
          "X-Cache": "MISS",
          "Access-Control-Allow-Origin": env.ALLOWED_ORIGIN,
        },
      });
    }

    return new Response("Not Found", { status: 404 });
  },
};

Managing Secrets Securely

Never commit API keys, database credentials, or JWT signing secrets to wrangler.toml.

Store secrets using Wrangler's encrypted store:

# Upload production secret
npx wrangler secret put API_SECRET_KEY

# Upload secret for staging environment
npx wrangler secret put API_SECRET_KEY --env staging

For local development, create a .dev.vars file in your project root (and add it to .gitignore):

ENVIRONMENT=local
API_SECRET_KEY=dev_secret_local_12345

Local Development and CI/CD Deployment

1. Local Testing

Run the local emulation engine with hot-reloading:

npx wrangler dev --port 8787

This spins up a local Miniflare instance that accurately emulates Workers, KV, and D1 SQLite storage on your machine.

2. GitHub Actions Deployment Workflow

Deploying on every merge to main:

name: Deploy Cloudflare Worker

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - name: Deploy to Cloudflare
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          command: deploy

Deploying to edge infrastructure with Wrangler provides sub-30ms cold starts worldwide and simplifies hosting APIs and gateways without running dedicated servers.

Comments

Threaded replies are enabled. Upvote useful ideas.