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:
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
- Sign up for a Cloudflare account ↗︎.
- 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:

