Skip to main content

Building Multi-Region APIs 2026

·APIScout Team
multi-regionedge computingcloudflare workerstursoglobal apislatencygeo-distributiondatabase replication

Single-Region APIs Are Slow for Half Your Users

A single-region API hosted in US East 1 responds in 20ms for a user in New York. The same API responds in 220ms for a user in Singapore — a 10x latency difference caused entirely by the speed of light across 16,000 km of cable.

For applications where latency directly affects user experience (real-time collaboration, gaming, financial trading, interactive web apps), single-region deployment leaves performance on the table for users outside the hosting region. Multi-region deployment solves this by running your API close to where users are.

In 2026, three approaches define global API deployment: edge computing (Cloudflare Workers, Deno Deploy), multi-region cloud deployments (fly.io, Railway global regions, AWS multi-region), and globally distributed databases (Turso, PlanetScale, CockroachDB). Each addresses different parts of the latency problem.

TL;DR

Cloudflare Workers is the most accessible edge deployment — your code runs at 330+ global edge locations, user requests are served from the nearest PoP, and global TTFB is typically under 50ms. For database reads, Cloudflare D1 global read replication and Turso's edge-replicated SQLite eliminate database-induced latency globally. For writes, the challenge remains: writes must go to a primary region or use conflict resolution strategies. Multi-region databases (CockroachDB, PlanetScale) handle replication automatically at higher cost.

Key Takeaways

  • Cloudflare Workers runs at 330+ PoPs — user requests are served from the nearest edge location with Time to First Byte under 50ms for 95% of the world's population.
  • Cloudflare D1 global read replication copies your SQLite database to all regions — read queries execute locally at the edge, writes still go to the primary region.
  • Turso edge-replicates SQLite to 35+ regions — designed for edge computing, with per-database pricing ($29/month for 100 databases on Pro plan).
  • CockroachDB and PlanetScale provide globally distributed SQL — writes are accepted in any region and replicated globally, with configurable consistency guarantees.
  • The write problem: Read replication is relatively simple; globally-consistent writes require careful trade-offs between consistency (CAP theorem) and latency.
  • Fly.io runs Docker containers in 30+ regions — closer to traditional cloud deployment than serverless edge, with automatic regional routing and private networking between instances.
  • Smart Placement: Cloudflare Workers Smart Placement automatically places your Worker near your database to minimize compute-to-data latency, even if that's not the nearest PoP to the user.

The Latency Problem, Quantified

User LocationSingle Region (US East)Edge (Cloudflare Workers)
New York20ms8ms
London80ms18ms
Singapore220ms25ms
Sydney280ms30ms
São Paulo150ms20ms

Source: Based on published Cloudflare benchmark data and typical cloud provider latency profiles.

Approach 1: Edge Computing with Cloudflare Workers

Best for: Read-heavy APIs, cacheable responses, simple CRUD against edge-replicated databases

Cloudflare Workers runs JavaScript/TypeScript (and any language compiling to Wasm) at 330+ global edge locations. Requests are automatically routed to the nearest PoP — no DNS geo-routing configuration required.

Basic Global API (Workers)

// src/index.ts — Cloudflare Worker
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);

    // Check CF headers for user's location
    const userCountry = request.headers.get("CF-IPCountry") || "unknown";
    const userCity = request.cf?.city || "unknown";

    if (url.pathname === "/api/products") {
      // Query local edge database (D1 or KV)
      const products = await getProducts(env);

      return Response.json(products, {
        headers: {
          "X-Served-From": request.cf?.colo || "unknown",  // e.g., "SIN", "LHR", "EWR"
          "Cache-Control": "s-maxage=60",  // Cache at edge for 60 seconds
        },
      });
    }

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

async function getProducts(env: Env) {
  // Cloudflare D1 — SQLite at the edge
  const { results } = await env.DB.prepare(
    "SELECT id, name, price, description FROM products WHERE active = 1 LIMIT 100"
  ).all();
  return results;
}

D1 Global Read Replication

// Cloudflare D1 read replication (2026 feature)
// Enable in wrangler.toml:
// [[d1_databases]]
// binding = "DB"
// database_name = "products"
// database_id = "your-database-id"
// read_replication = { mode = "auto" }

// With read replication enabled:
// - SELECT queries execute against the nearest replica
// - INSERT/UPDATE/DELETE go to the primary region
// - 95.7% latency reduction for read-heavy APIs from non-primary regions

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const { results } = await env.DB.prepare(
      // This SELECT runs locally at the edge — 5ms instead of 220ms
      "SELECT * FROM products WHERE id = ?1"
    ).bind(productId).all();

    return Response.json(results[0]);
  },
};

Workers KV for Global Caching

// Workers KV — globally distributed key-value, read in ~5ms anywhere
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const cacheKey = `product:${productId}`;

    // Check global KV cache first (replicates in ~60 seconds globally)
    const cached = await env.KV.get(cacheKey, "json");
    if (cached) {
      return Response.json(cached, {
        headers: { "X-Cache": "HIT", "X-Cache-Key": cacheKey },
      });
    }

    // Cache miss — fetch from origin database
    const product = await fetchFromDatabase(productId);

    // Store in KV — available globally within 60 seconds
    await env.KV.put(cacheKey, JSON.stringify(product), {
      expirationTtl: 3600,  // 1-hour TTL
    });

    return Response.json(product, { headers: { "X-Cache": "MISS" } });
  },
};

Smart Placement for Database-Bound Workers

# wrangler.toml — Smart Placement places Worker near your database
[placement]
mode = "smart"
# Cloudflare analyzes your Worker's traffic patterns and database connections
# Automatically places Worker in region closest to your database
# When database is in US East, Worker runs from US East PoP
# even if user is in Europe (but routes read replicas to EU PoPs)

Approach 2: Turso Edge SQLite

Best for: Read-heavy applications with edge-replicated SQLite, hobby projects to production scale

Turso is SQLite for the edge — a libSQL fork with global database replication. Each database can be replicated to 35+ regions; reads execute against the nearest replica; writes go to the primary replica.

Pricing

PlanCostDatabases
StarterFree500 databases, 9GB storage
Scaler$29/monthUnlimited databases
EnterpriseCustomVolume pricing

Integration with Cloudflare Workers

// Turso + Cloudflare Workers
import { createClient } from "@libsql/client/web";

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // Turso client — automatically connects to nearest replica
    const db = createClient({
      url: env.TURSO_DATABASE_URL,   // Primary URL
      authToken: env.TURSO_AUTH_TOKEN,
    });

    // This read goes to nearest Turso replica — could be Singapore, Frankfurt, etc.
    const { rows } = await db.execute({
      sql: "SELECT * FROM products WHERE id = ?",
      args: [productId],
    });

    return Response.json(rows[0]);
  },
};

Database Replication Setup

# Create a database with global replication
turso db create products --location sjc  # Primary in San Jose

# Add read replicas in additional regions
turso db replicate products nrt  # Tokyo replica
turso db replicate products lhr  # London replica
turso db replicate products sao  # São Paulo replica
turso db replicate products syd  # Sydney replica

# List replicas
turso db show products

Approach 3: Multi-Region Cloud Deployments (fly.io)

Best for: Traditional Docker containers with global regional distribution

fly.io runs Docker containers in 30+ regions worldwide — closer to traditional cloud deployment than serverless edge, but with automatic global routing and private networking between instances in different regions.

Deployment Configuration

# fly.toml — deploy to multiple regions
app = "my-api"
primary_region = "iad"  # US East as primary

[[services]]
  protocol = "tcp"
  internal_port = 8080

  [[services.ports]]
    port = 443
    handlers = ["tls", "http"]

  [services.concurrency]
    type = "requests"
    hard_limit = 200

# Regions are set via:
# fly scale count 2 --region lhr  # 2 instances in London
# fly scale count 2 --region nrt  # 2 instances in Tokyo
# fly scale count 2 --region gru  # 2 instances in São Paulo

Fly.io Read Replicas with PlanetScale

import planetscale

# PlanetScale automatically routes reads to nearest replica
db = planetscale.Client(
    host=os.environ["DATABASE_HOST"],  # PlanetScale global endpoint
    username=os.environ["DATABASE_USER"],
    password=os.environ["DATABASE_PASSWORD"],
    database="production",
)

# This query is automatically served from nearest PlanetScale replica
# Writes go to primary via replication lag (<100ms between regions)
result = db.execute("SELECT * FROM products WHERE id = %s", [product_id])

Handling Writes Globally

The hardest problem in multi-region APIs is globally-consistent writes. Three patterns:

Pattern 1: Write to Primary, Read from Replicas

// Route writes to primary region, reads to nearest replica
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    if (request.method === "GET") {
      // Read from nearest replica — fast
      return handleRead(request, env);
    }

    if (request.method === "POST" || request.method === "PUT") {
      // Route write to primary region
      const primaryUrl = "https://api-primary.us-east.example.com";
      return fetch(new Request(primaryUrl + request.url, request));
    }
  },
};

Pattern 2: CockroachDB — Globally Distributed SQL

import psycopg2

# CockroachDB — accepts writes in any region, replicates globally
# Reads from nearest replica automatically
conn = psycopg2.connect(
    "postgresql://user:password@free-tier.cockroachlabs.cloud:26257/defaultdb?sslmode=verify-full"
)

cursor = conn.cursor()

# This INSERT is accepted by any CockroachDB node globally
# Replicates to all regions within ~150ms
cursor.execute(
    "INSERT INTO orders (id, user_id, amount, status) VALUES (%s, %s, %s, %s)",
    ["ord_123", "user_456", 99.99, "pending"]
)
conn.commit()

Pattern 3: Optimistic Local Writes with Conflict Resolution

// Cloudflare Durable Objects — coordinate global state
// Each Durable Object lives in a single region but accepts requests from any Worker
export class OrderDurableObject {
  constructor(private state: DurableObjectState, private env: Env) {}

  async fetch(request: Request): Promise<Response> {
    const { method } = request;

    if (method === "POST") {
      const order = await request.json();

      // Write goes to this Durable Object's region
      // Cloudflare routes requests to the same DO instance globally
      await this.state.storage.put(`order:${order.id}`, order);

      return Response.json({ success: true });
    }

    if (method === "GET") {
      const orderId = new URL(request.url).searchParams.get("id");
      const order = await this.state.storage.get(`order:${orderId}`);
      return Response.json(order);
    }
  }
}

Caching Strategy for Global APIs

// Cache-Control headers for global CDN caching
export function getCacheHeaders(resource: string): Headers {
  const headers = new Headers();

  switch (resource) {
    case "product-catalog":
      // Catalog changes rarely — cache for 1 hour at edge
      headers.set("Cache-Control", "public, s-maxage=3600");
      headers.set("CDN-Cache-Control", "max-age=3600");
      break;

    case "user-profile":
      // User data — don't cache at edge, cache in browser briefly
      headers.set("Cache-Control", "private, max-age=60");
      break;

    case "pricing":
      // Pricing changes occasionally — cache 5 minutes with stale-while-revalidate
      headers.set("Cache-Control", "public, s-maxage=300, stale-while-revalidate=600");
      break;
  }

  return headers;
}

Decision Framework

ScenarioRecommended
Read-heavy, globally cacheableCloudflare Workers + KV
Edge SQLite, moderate writesTurso + Cloudflare Workers
Traditional containers, globalfly.io
Globally-distributed SQL writesCockroachDB
MySQL-compatible, global readsPlanetScale
Real-time state coordinationCloudflare Durable Objects
Hybrid edge + originCloudflare Workers + Hyperdrive

Verdict

Cloudflare Workers is the most accessible entry point for global API performance — 330+ PoPs, free tier (100K requests/day), and the fastest path from single-region to global edge.

Turso solves the database problem for edge applications — SQLite replicated to 35+ regions with automatic routing to the nearest replica. The combination of Cloudflare Workers + Turso is the highest-performance edge stack available in 2026.

Fly.io is the right choice for teams that want global deployment but need Docker containers (existing application code, specific runtimes, stateful workloads not suited for serverless).

CockroachDB and PlanetScale handle the write distribution problem — when you need globally-distributed SQL writes with automatic replication, these managed databases remove the infrastructure complexity.


Compare global API deployment options, edge database pricing, and latency benchmarks at APIScout — find the right multi-region architecture for your API.

Comments