blog.dopana

Back

Building a modern, globally distributed API used to mean managing multiple database replicas, configuring reverse proxies, and provisioning containers across different cloud regions.

With Cloudflare’s Edge Platform and the Wrangler CLI, you can build, test, and deploy a blazingly fast fullstack REST API with a relational database (D1) and low-latency cache (KV) in minutes — running across 300+ data centers worldwide with zero cold start.

1. What Are We Building? (ELI5)#

Think of your application like a high-end restaurant:

  • Cloudflare Worker (The Waiter/Chef): Takes incoming requests, executes business logic, and prepares responses right next to the customer.
  • Cloudflare KV (The Ready-to-Serve Counter / Cache): Holds pre-made, popular dishes. When someone orders a popular item, the waiter grabs it instantly without going into the kitchen (ultra-fast reads, sub-millisecond response).
  • Cloudflare D1 (The Main Pantry & Recipe Vault / Database): A serverless SQLite database where all records are persistently stored and safely modified using SQL queries.
graph TD
    Client["📱 Client Request"] -->|HTTP GET/POST| Worker["⚡ Cloudflare Worker (Edge API)"]
    Worker -->|1. Check Cache| KV["⚡ Cloudflare KV (Fast Read Cache)"]
    KV -.->|Cache Hit - Fast Return| Worker
    KV -.->|Cache Miss| D1
    Worker -->|2. Query/Mutate Data| D1["🗄️ Cloudflare D1 (Serverless SQLite)"]
    D1 -->|3. Populate Cache| KV
    Worker -->|HTTP JSON Response| Client

2. Prerequisites & Project Setup#

Ensure you have Node.js / Bun installed and an active Cloudflare account.

Step 1: Initialize the Worker Project#

Create a new Cloudflare Workers project using npm create cloudflare@latest (C3) or initialize directly with Wrangler:

# Initialize a TypeScript Worker
npm create cloudflare@latest edge-api -- --type=hello-world-typescript --ts --git --deploy=false
cd edge-api
bash

Install the latest Wrangler CLI locally if not already present:

npm install -D wrangler
bash

Authenticate Wrangler with your Cloudflare account:

npx wrangler login
bash

3. Provisioning Cloudflare D1 (SQL Database)#

Cloudflare D1 is a serverless relational database built on SQLite.

Step 1: Create the D1 Database#

Run the following command to create a D1 database named ecommerce-db:

npx wrangler d1 create ecommerce-db
bash

Wrangler will output configuration information similar to:

[[d1_databases]]
binding = "DB"
database_name = "ecommerce-db"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
text

Step 2: Define Schema & Apply Migrations#

Create a schema.sql file in your project:

schema.sql
DROP TABLE IF EXISTS products;

CREATE TABLE products (
  id TEXT PRIMARY KEY,
  name TEXT NOT NULL,
  price REAL NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

INSERT INTO products (id, name, price) VALUES
  ('prod_1', 'Wireless Mechanical Keyboard', 129.99),
  ('prod_2', 'Ultra-Wide Gaming Monitor', 499.99),
  ('prod_3', 'Ergonomic Standing Desk', 349.50);
sql

Execute the schema locally for development:

npx wrangler d1 execute ecommerce-db --local --file=./schema.sql
bash

Execute the schema on the remote production database:

npx wrangler d1 execute ecommerce-db --remote --file=./schema.sql
bash

4. Provisioning Cloudflare KV (Key-Value Cache)#

KV provides high-throughput, low-latency key-value storage optimized for high-volume read scenarios.

Step 1: Create KV Namespaces#

Create two namespaces: one for production and one for local testing.

# Create production namespace
npx wrangler kv namespace create CACHE_KV

# (Optional) Create preview/test namespace
npx wrangler kv namespace create CACHE_KV --preview
bash

Wrangler will output the namespace IDs:

[[kv_namespaces]]
binding = "CACHE_KV"
id = "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
preview_id = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
text

5. Configuring wrangler.toml#

Update your wrangler.toml (or wrangler.json) file to bind D1 and KV to your Worker environment:

wrangler.toml
name = "edge-api"
main = "src/index.ts"
compatibility_date = "2026-08-01"

# Bind D1 Database
[[d1_databases]]
binding = "DB"
database_name = "ecommerce-db"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

# Bind KV Cache
[[kv_namespaces]]
binding = "CACHE_KV"
id = "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
preview_id = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
toml

Generate TypeScript types for your environment bindings automatically:

npx wrangler types
bash

6. Implementing the API with Cache-Aside Pattern#

Now let’s write our API logic in src/index.ts. We implement the Cache-Aside strategy:

  1. Check KV cache for existing product data.
  2. If cache hit, return immediately with CF-Cache-Status: HIT.
  3. If cache miss, query D1 SQLite, store the result in KV with a TTL (Time-To-Live), and return.
  4. On POST / PUT, write to D1 and invalidate/update the KV cache key.

[!TIP] Use ctx.waitUntil(...) when writing to KV on cache misses so your API response is returned immediately to the client without waiting for the background cache write to finish.

7. Local Testing and Production Deployment#

Local Development#

Run the local development server with emulated D1 and KV instances:

npx wrangler dev
bash

Test querying products via curl:

# First request (MISS from D1)
curl http://localhost:8787/api/products/prod_1

# Second request (HIT from KV)
curl http://localhost:8787/api/products/prod_1
bash

Production Deployment#

Deploy your code to Cloudflare’s global edge network in one single command:

npx wrangler deploy
bash

You will get an edge URL like https://edge-api.<your-subdomain>.workers.dev live immediately across 300+ edge locations!

8. What If You Use Rust with Axum?#

If you prefer Rust over TypeScript, you can compile Rust to WebAssembly (wasm32-unknown-unknown) and run Axum / worker-rs on Cloudflare Workers.

Architectural Differences: TypeScript vs Rust (Axum)#

graph LR
    subgraph TS["TypeScript Worker"]
        direction TB
        TS_Code["src/index.ts"] --> V8["V8 Isolate Engine<br/>(Native JS bindings)"]
        V8 --> DB_TS["env.DB / env.CACHE_KV"]
    end

    subgraph RS["Rust + Axum Worker"]
        direction TB
        RS_Code["src/lib.rs (Axum Router)"] --> WASM["WASM Module<br/>(wasm32-unknown-unknown)"]
        WASM --> WRS["worker-rs FFI Bridge"]
        WRS --> DB_RS["env.d1('DB') / env.kv('CACHE_KV')"]
    end
DimensionTypeScriptRust (Axum + worker-rs)
Compilation TargetJavaScript (V8 Isolate)WebAssembly (.wasm) via worker-build
Routing ModelStandard fetch(request, env, ctx) handleraxum::Router with extractors (State, Path, Json)
Binding AccessNative object: env.DB, env.CACHE_KVworker::Env / FFI: env.d1("DB")?, env.kv("CACHE_KV")?
Type Safety & PerformanceFast iteration, type checks at build timeMemory safety, zero-cost abstractions, compiled WASM binary
wrangler.toml Configmain = "src/index.ts"main = "build/worker/shim.mjs" + [build] command = "cargo install -q worker-build && worker-build --release"

Rust Axum Code Example with D1 and KV#

In Rust, we initialize an axum::Router inside the worker entrypoint, sharing worker::Env via Axum’s Extension or State:

9. Summary of Wrangler CLI Commands#

CommandPurpose
wrangler loginAuthenticate CLI with your Cloudflare account
wrangler devStart local emulation server with D1 & KV
wrangler d1 create <name>Create a new D1 SQL database
wrangler d1 execute <name> --file=./schema.sqlExecute SQL migrations (--local or --remote)
wrangler kv namespace create <name>Create a new KV Key-Value store
wrangler typesGenerate TypeScript definitions for bindings
wrangler deployBuild and deploy Worker to production
wrangler tailStream real-time production logs directly in terminal

References#