Using D1 Read Replication for your e-commerce website

Last updated Aug 25, 2026 is a feature that allows you to replicate your D1 database to multiple regions. This is useful for your e-commerce website, as it reduces read latencies and improves read throughput. In this tutorial, you will learn how to use D1 read replication for your e-commerce website.

While this tutorial uses a fictional e-commerce website, the principles can be applied to any use-case that requires low read latencies and scaling reads, such as a news website, a social media platform, or a marketing website.

Quick start

If you want to skip the steps and get started quickly, click on the below button:

Deploy to Cloudflare

This will create a repository in your GitHub account and deploy the application to Cloudflare Workers. It will also create and bind a D1 database, create the required tables, add some sample data. During deployment, tick the Enable read replication box to activate read replication.

You can then visit the deployed application.

Prerequisites

  1. Sign up for a Cloudflare account ↗︎.
  2. Install Node.js ↗︎.

<details>

<summary>

Node.js version manager

</summary>

Use <a href="https://viteplus.dev/guide/env">Vite+ (<code>vp env</code>) ↗︎</a> to install and manage Node.js versions.

For supported Node.js versions, refer to the <a href="https://developers.cloudflare.com/workers/wrangler/install-and-update/#install-wrangler">Wrangler system requirements</a>.

</details>

Step 1: Create a Workers project

Create a new Workers project by running the following command:

npmyarnpnpm

npm create cloudflare@latest -- fast-commerce
yarn create cloudflare fast-commerce
pnpm create cloudflare@latest fast-commerce

For setup, select the following options:

  • For What would you like to start with?, choose Hello World example.
  • For Which template would you like to use?, choose SSR / full-stack app.
  • For Which language do you want to use?, choose TypeScript.
  • For Do you want to use git for version control?, choose Yes.
  • For Do you want to add an AGENTS.md file to help AI coding tools understand Cloudflare APIs?, choose Yes.
  • For Do you want to deploy your application?, choose No (we will be making some changes before deploying).

For creating the API routes, you will use Hono ↗︎. You need to install Hono by running the following command:

npmyarnpnpmbun

npm i hono
yarn add hono
pnpm add hono
bun add hono

Step 2: Update the frontend

The above step creates a new Workers project with a default frontend and installs Hono. You will update the frontend to list the products. You will also add a new page to the frontend to display a single product.

Navigate to the newly created Worker project folder.

cd fast-commerce

Update the public/index.html file to list the products. Use the below code as a reference.

<details>

<summary>

public/index.html

</summary>

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>E-commerce Store</title>
        <style>
            * {
                margin: 0;
                padding: 0;
                box-sizing: border-box;
                font-family: Arial, sans-serif;
            }

body {
                background-color: #f9fafb;
                min-height: 100vh;
                display: flex;
                flex-direction: column;
            }

header {
                background-color: white;
                padding: 1rem 2rem;
                display: flex;
                justify-content: space-between;
                align-items: center;
                border-bottom: 1px solid #e5e7eb;
            }

.store-title {
                font-weight: bold;
                font-size: 1.25rem;
            }

.cart-button {
                padding: 0.5rem 1rem;
                cursor: pointer;
                background: none;
                border: none;
            }

.products-grid {
                display: grid;
                grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
                gap: 1.5rem;
                padding: 2rem;
            }

.product-card {
                background-color: white;
                border-radius: 0.5rem;
                overflow: hidden;
                box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
            }

.product-info {
                padding: 1rem;
            }

.product-title {
                font-size: 1.125rem;
                font-weight: 600;
                margin-bottom: 0.5rem;
            }

.product-description {
                color: #4b5563;
                font-size: 0.875rem;
                margin-bottom: 1rem;
            }

.product-price {
                font-size: 1.25rem;
                font-weight: bold;
                margin-bottom: 0.5rem;
            }

.product-stock {
                color: #4b5563;
                font-size: 0.875rem;
                margin-bottom: 1rem;
            }

.view-details-btn {
                display: block;
                width: 100%;
                padding: 0.5rem 0;
                background-color: #2563eb;
                color: white;
                border: none;
                border-radius: 0.375rem;
                cursor: pointer;
                text-align: center;
                text-decoration: none;
                font-size: 0.875rem;
            }

.view-details-btn:hover {
                background-color: #1d4ed8;
            }

footer {
                background-color: white;
                padding: 1rem 2rem;
                text-align: center;
                border-top: 1px solid #e5e7eb;
                color: #4b5563;
                font-size: 0.875rem;
            }

/* Basic Responsiveness */
            @media (max-width: 768px) {
                .products-grid {
                    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
                }
            }

@media (max-width: 480px) {
                .products-grid {
                    grid-template-columns: 1fr;
                }
            }
        </style>
    </head>
    <body>
        <header>
            <h1 class="store-title">E-commerce Store</h1>
            <button class="cart-button">Cart</button>
        </header>

<main class="products-grid" id="products-container">
            <!-- Products will be loaded here by JavaScript -->
        </main>

<footer>
            <p>© 2025 E-commerce Store. All rights reserved.</p>
        </footer>

<script>
            document.addEventListener('DOMContentLoaded', () => {
                let products = [];
                let d1Duration,
                    queryDuration = 0;
                let dbLocation;
                let isPrimary = true;

// Function to create product HTML
                function createProductCard(product) {
                    return `
                <div class="product-card" data-category="${product.category}">
                    <div class="product-info">
                        <h3 class="product-title">${product.name}</h3>
                        <p class="product-description">${product.description}</p>
                        <p class="product-price">$${product.price.toFixed(2)}</p>
                        <p class="product-stock">${product.inventory} in stock</p>
                        <a href="product-details.html?id=${product.id}" class="view-details-btn">View Details</a>
                    </div>
                </div>
            `;
                }

// Function to render content
                function renderContent() {
                    try {
                        const productsContainer = document.getElementById('products-container');
                        if (!productsContainer) return;
                        productsContainer.innerHTML = '';

products.forEach((product) => {
                            productsContainer.innerHTML += createProductCard(product);
                        });
                    } catch (error) {
                        console.error('Error rendering content:', error);
                    }
                }

// Fetch products
                fetch('/api/products')
                    .then((response) => response.json())
                    .then((data) => {
                        products = data;
                        renderContent();
                    })
                    .catch((error) => console.error('Error fetching products:', error));
            });
        </script>
    </body>

</html>

</details>

Create a new public/product-details.html file to display a single product.

<details>

<summary>

public/product-details.html

</summary>

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Product Details - E-commerce Store</title>
        <style>
            * {
                margin: 0;
                padding: 0;
                box-sizing: border-box;
                font-family: Arial, sans-serif;
            }

body {
                background-color: #f9fafb;
                min-height: 100vh;
                display: flex;
                flex-direction: column;
            }

header {
                background-color: white;
                padding: 1rem 2rem;
                display: flex;
                justify-content: space-between;
                align-items: center;
                border-bottom: 1px solid #e5e7eb;
            }

.store-title {
                font-weight: bold;
                font-size: 1.25rem;
                text-decoration: none;
                color: black;
            }

.cart-button {
                padding: 0.5rem 1rem;
                cursor: pointer;
                background: none;
                border: none;
            }

.product-container {
                max-width: 800px;
                margin: 2rem auto;
                background-color: white;
                border-radius: 0.5rem;
                box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
                padding: 2rem;
            }

.product-title {
                font-size: 1.875rem;
                font-weight: bold;
                margin-bottom: 0.5rem;
            }

.product-description {
                color: #4b5563;
                margin-bottom: 1.5rem;
            }

.product-price {
                font-size: 1.875rem;
                font-weight: bold;
                margin-bottom: 0.5rem;
            }

.product-stock {
                font-size: 0.875rem;
                color: #4b5563;
                text-align: right;
            }

.add-to-cart-btn {
                display: block;
                width: 100%;
                padding: 0.75rem;
                background-color: #2563eb;
                color: white;
                border: none;
                border-radius: 0.375rem;
                cursor: pointer;
                text-align: center;
                font-size: 1rem;
                margin-top: 1.5rem;
            }

.add-to-cart-btn:hover {
                background-color: #1d4ed8;
            }

.price-stock-container {
                display: flex;
                justify-content: space-between;
                align-items: center;
                margin-bottom: 1rem;
            }

footer {
                background-color: white;
                padding: 1rem 2rem;
                text-align: center;
                border-top: 1px solid #e5e7eb;
                color: #4b5563;
                font-size: 0.875rem;
                margin-top: auto;
            }

/* Back button */
            .back-button {
                display: inline-block;
                margin-bottom: 1.5rem;
                color: #2563eb;
                text-decoration: none;
                font-size: 0.875rem;
            }

.back-button:hover {
                text-decoration: underline;
            }

/* Notification */
            .notification {
                position: fixed;
                top: 1rem;
                right: 1rem;
                background-color: #10b981;
                color: white;
                padding: 0.75rem 1rem;
                border-radius: 0.375rem;
                box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
                transform: translateX(150%);
                transition: transform 0.3s ease;
            }

.notification.show {
                transform: translateX(0);
            }
        </style>
    </head>
    <body>
        <header>
            <a href="index.html" class="store-title">E-commerce Store</a>
            <button class="cart-button">Cart</button>
        </header>

<main class="product-container">
            <a href="index.html" class="back-button">← Back to products</a>
            <h1 class="product-title" id="product-title">Product Name</h1>
            <p class="product-description" id="product-description">
                Product description goes here.
            </p>

<div class="price-stock-container">
                <p class="product-price" id="product-price">$0.00</p>
                <p class="product-stock" id="product-stock">0 in stock</p>
            </div>

<button class="add-to-cart-btn" id="add-to-cart">Add to Cart</button>
        </main>

<div class="notification" id="notification">Added to cart!</div>

<footer>
            <p>© 2025 E-commerce Store. All rights reserved.</p>
        </footer>

<script>
            // Get query parameter from URL
            const url = new URL(window.location.href);
            const searchParams = new URLSearchParams(url.search);
            const productId = searchParams.get("id");

// Fetch product details
            fetch(`/api/products/${productId}`)
                .then((response) => response.json())
                .then((product) => displayContent(product))
                .catch((error) =>
                    console.error("Error fetching product details:", error),
                );

// Function to display product details
            function displayContent(product) {
                document.title = `${product[0].name} - E-commerce Store`;
                document.getElementById("product-title").textContent = product[0].name;
                document.getElementById("product-description").textContent =
                    product[0].description;
                document.getElementById("product-price").textContent =
                    `$${product[0].price.toFixed(2)}`;
                document.getElementById("product-stock").textContent =
                    `${product[0].inventory} in stock`;
            }
        </script>
    </body>
</html>

</details>

You now have a frontend that lists products and displays a single product. However, the frontend is not yet connected to the D1 database. If you start the development server now, you will see no products. In the next steps, you will create a D1 database and create APIs to fetch products and display them on the frontend.

Step 3: Create a D1 database and enable read replication

Create a new D1 database by running the following command:

npx wrangler d1 create fast-commerce

Add the D1 bindings returned in the terminal to the wrangler file:

{
    "d1_databases": [
        {
            "binding": "DB",
            "database_name": "fast-commerce",
            "database_id": "YOUR_DATABASE_ID"
        }
    ]
}
[[d1_databases]]
binding = "DB"
database_name = "fast-commerce"
database_id = "YOUR_DATABASE_ID"

Run the following command to update the Env interface in the worker-configuration.d.ts file.

npm run cf-typegen

Next, enable read replication for the D1 database. Navigate to Workers & Pages > D1 ↗︎, then select an existing database > Settings > Enable Read Replication.

Step 4: Create the API routes

Update the src/index.ts file to import the Hono library and create the API routes.

import { Hono } from "hono";
// Set db session bookmark in the cookie
import { getCookie, setCookie } from "hono/cookie";

const app = new Hono<{ Bindings: Env }>();

// Get all products
app.get("/api/products", async (c) => {
    return c.json({ message: "get list of products" });
});

// Get a single product
app.get("/api/products/:id", async (c) => {
    return c.json({ message: "get a single product" });
});

// Upsert a product
app.post("/api/product", async (c) => {
    return c.json({ message: "create or update a product" });
});

export default app;

The above code creates three API routes:

  • GET /api/products: Returns a list of products.
  • GET /api/products/:id: Returns a single product.
  • POST /api/product: Creates or updates a product.

However, the API routes are not connected to the D1 database yet. In the next steps, you will create a products table in the D1 database, and update the API routes to connect to the D1 database.

Step 5: Create local D1 database schema

Create a products table in the D1 database by running the following command:

npx wrangler d1 execute fast-commerce --command "CREATE TABLE IF NOT EXISTS products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, description TEXT, price DECIMAL(10, 2) NOT NULL, inventory INTEGER NOT NULL DEFAULT 0, category TEXT NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, last_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)"

Next, create an index on the products table by running the following command:

npx wrangler d1 execute fast-commerce --command "CREATE INDEX IF NOT EXISTS idx_products_id ON products (id)"

For development purposes, you can also execute the insert statements on the local D1 database by running the following command: