Using D1 Read Replication for your e-com·续

npx wrangler d1 execute fast-commerce --command "INSERT INTO products (id, name, description, price, inventory, category) VALUES (1, 'Fast Ergonomic Chair', 'A comfortable chair for your home or office', 100.00, 10, 'Furniture'), (2, 'Fast Organic Cotton T-shirt', 'A comfortable t-shirt for your home or office', 20.00, 100, 'Clothing'), (3, 'Fast Wooden Desk', 'A wooden desk for your home or office', 150.00, 5, 'Furniture'), (4, 'Fast Leather Sofa', 'A leather sofa for your home or office', 300.00, 3, 'Furniture'), (5, 'Fast Organic Cotton T-shirt', 'A comfortable t-shirt for your home or office', 20.00, 100, 'Clothing')"

Step 6: Add retry logic

To make the application more resilient, you can add retry logic to the API routes. Create a new file called retry.ts in the src directory.

export interface RetryConfig {
    maxRetries: number;
    initialDelay: number;
    maxDelay: number;
    backoffFactor: number;
}

const shouldRetry = (error: unknown): boolean => {
    const errMsg = error instanceof Error ? error.message : String(error);
    return (
        errMsg.includes("Network connection lost") ||
        errMsg.includes("storage caused object to be reset") ||
        errMsg.includes("reset because its code was updated")
    );
};

// Helper function for sleeping
const sleep = (ms: number): Promise<void> => {
    return new Promise((resolve) => setTimeout(resolve, ms));
};

export const defaultRetryConfig: RetryConfig = {
    maxRetries: 3,
    initialDelay: 100,
    maxDelay: 1000,
    backoffFactor: 2,
};

export async function withRetry<T>(
    operation: () => Promise<T>,
    config: Partial<RetryConfig> = defaultRetryConfig,
): Promise<T> {
    const maxRetries = config.maxRetries ?? defaultRetryConfig.maxRetries;
    const initialDelay = config.initialDelay ?? defaultRetryConfig.initialDelay;
    const maxDelay = config.maxDelay ?? defaultRetryConfig.maxDelay;
    const backoffFactor =
        config.backoffFactor ?? defaultRetryConfig.backoffFactor;

let lastError: Error | unknown;
    let delay = initialDelay;

for (let attempt = 0; attempt <= maxRetries; attempt++) {
        try {
            const result = await operation();
            return result;
        } catch (error) {
            lastError = error;

if (!shouldRetry(error) || attempt === maxRetries) {
                throw error;
            }

// Add randomness to avoid synchronizing retries
            // Wait for a random delay between delay and delay*2
            await sleep(delay * (1 + Math.random()));

// Calculate next delay with exponential backoff
            delay = Math.min(delay * backoffFactor, maxDelay);
        }
    }

throw lastError;
}

The withRetry function is a utility function that retries a given operation with exponential backoff. It takes a configuration object as an argument, which allows you to customize the number of retries, initial delay, maximum delay, and backoff factor. It will only retry the operation if the error is due to a network connection loss, storage reset, or code update.

Caution

In a distributed system, retry mechanisms can have certain risks. Read the article Retry Strategies in Distributed Systems: Identifying and Addressing Key Pitfalls ↗︎ to learn more about the risks of retry mechanisms and how to avoid them.

Retries can sometimes lead to data inconsistency. Make sure to handle the retry logic carefully.

Next, update the src/index.ts file to import the withRetry function and use it in the API routes.

import { withRetry } from "./retry";

Step 7: Update the API routes

Update the API routes to connect to the D1 database.

1. POST /api/product

app.post("/api/product", async (c) => {
    const product = await c.req.json();

if (!product) {
        return c.json({ message: "No data passed" }, 400);
    }

const db = c.env.DB;
    const session = db.withSession("first-primary");

const { id } = product;

try {
        return await withRetry(async () => {
            // Check if the product exists
            const { results } = await session
                .prepare("SELECT * FROM products where id = ?")
                .bind(id)
                .run();
            if (results.length === 0) {
                const fields = [...Object.keys(product)];
                const values = [...Object.values(product)];
                // Insert the product
                await session
                    .prepare(
                        `INSERT INTO products (${fields.join(", ")}) VALUES (${fields.map(() => "?").join(", ")})`,
                    )
                    .bind(...values)
                    .run();
                const latestBookmark = session.getBookmark();
                latestBookmark &&
                    setCookie(c, "product_bookmark", latestBookmark, {
                        maxAge: 60 * 60, // 1 hour
                    });
                return c.json({ message: "Product inserted" });
            }

// Update the product
            const updates = Object.entries(product)
                .filter(([_, value]) => value !== undefined)
                .map(([key, _]) => `${key} = ?`)
                .join(", ");

if (!updates) {
                throw new Error("No valid fields to update");
            }

const values = Object.entries(product)
                .filter(([_, value]) => value !== undefined)
                .map(([_, value]) => value);

await session
                .prepare(`UPDATE products SET ${updates} WHERE id = ?`)
                .bind(...[...values, id])
                .run();
            const latestBookmark = session.getBookmark();
            latestBookmark &&
                setCookie(c, "product_bookmark", latestBookmark, {
                    maxAge: 60 * 60, // 1 hour
                });
            return c.json({ message: "Product updated" });
        });
    } catch (e) {
        console.error(e);
        return c.json({ message: "Error upserting product" }, 500);
    }
});

In the above code:

  • You get the product data from the request body.
  • You then check if the product exists in the database.
  • If it does, you update the product.
  • If it doesn't, you insert the product.
  • You then set the bookmark in the cookie.
  • Finally, you return the response.

Since you want to start the session with the latest data, you use the first-primary constraint. Even if you use the first-unconstrained constraint or pass a bookmark, the write request will always be routed to the primary database.

The bookmark set in the cookie can be used to guarantee that a new session reads a database version that is at least as up-to-date as the provided bookmark.

If you are using an external platform to manage your products, you can connect this API to the external platform, such that, when a product is created or updated in the external platform, the D1 database automatically updates the product details.

2. GET /api/products

app.get("/api/products", async (c) => {
    const db = c.env.DB;

// Get bookmark from the cookie
    const bookmark = getCookie(c, "product_bookmark") || "first-unconstrained";

const session = db.withSession(bookmark);

try {
        return await withRetry(async () => {
            const { results } = await session.prepare("SELECT * FROM products").run();

const latestBookmark = session.getBookmark();

// Set the bookmark in the cookie
            latestBookmark &&
                setCookie(c, "product_bookmark", latestBookmark, {
                    maxAge: 60 * 60, // 1 hour
                });

return c.json(results);
        });
    } catch (e) {
        console.error(e);
        return c.json([]);
    }
});

In the above code:

  • You get the database session bookmark from the cookie.
  • If the bookmark is not set, you use the first-unconstrained constraint.
  • You then create a database session with the bookmark.
  • You fetch all the products from the database and get the latest bookmark.
  • You then set this bookmark in the cookie.
  • Finally, you return the results.

3. GET /api/products/:id

app.get("/api/products/:id", async (c) => {
    const id = c.req.param("id");

if (!id) {
        return c.json({ message: "Invalid id" }, 400);
    }

const db = c.env.DB;

// Get bookmark from the cookie
    const bookmark = getCookie(c, "product_bookmark") || "first-unconstrained";

const session = db.withSession(bookmark);

try {
        return await withRetry(async () => {
            const { results } = await session
                .prepare("SELECT * FROM products where id = ?")
                .bind(id)
                .run();

const latestBookmark = session.getBookmark();

// Set the bookmark in the cookie
            latestBookmark &&
                setCookie(c, "product_bookmark", latestBookmark, {
                    maxAge: 60 * 60, // 1 hour
                });

console.log(results);

return c.json(results);
        });
    } catch (e) {
        console.error(e);
        return c.json([]);
    }
});

In the above code:

  • You get the product ID from the request parameters.
  • You then create a database session with the bookmark.
  • You fetch the product from the database and get the latest bookmark.
  • You then set this bookmark in the cookie.
  • Finally, you return the results.

Step 8: Test the application

You have now updated the API routes to connect to the D1 database. You can now test the application by starting the development server and navigating to the frontend.

npm run dev

Navigate to `http://localhost:8787 ↗︎. You should see the products listed. Click on a product to view the product details.

To insert a new product, use the following command (while the development server is running):

curl -X POST http://localhost:8787/api/product \
     -H "Content-Type: application/json" \
     -d '{"id": 6, "name": "Fast Computer", "description": "A computer for your home or office", "price": 1000.00, "inventory": 10, "category": "Electronics"}'

Navigate to http://localhost:8787/product-details?id=6. You should see the new product.

Update the product using the following command, and navigate to http://localhost:8787/product-details?id=6 again. You will see the updated product.

curl -X POST http://localhost:8787/api/product \
     -H "Content-Type: application/json" \
     -d '{"id": 6, "name": "Fast Computer", "description": "A computer for your home or office", "price": 1050.00, "inventory": 10, "category": "Electronics"}'

Note

Read replication is only used when the application has been deployed. D1 does not create read replicas when you develop locally.

To test it locally, you can set "remote" : true in the D1 binding configuration. Refer to the remote bindings documentation for more information.

Step 9: Deploy the application

Since the database you used in the previous steps is local, you need to create the products table in the remote database. Execute the following D1 commands to create the products table in the remote database.

npx wrangler d1 execute fast-commerce --remote --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 --remote --command "CREATE INDEX IF NOT EXISTS idx_products_id ON products (id)"

Optionally, you can insert the products into the remote database by running the following command:

npx wrangler d1 execute fast-commerce --remote --command "INSERT INTO products (id, name, description, price, inventory, category) VALUES (1, 'Fast Ergonomic Chair', 'A comfortable chair for your home or office', 100.00, 10, 'Furniture'), (2, 'Fast Organic Cotton T-shirt', 'A comfortable t-shirt for your home or office', 20.00, 100, 'Clothing'), (3, 'Fast Wooden Desk', 'A wooden desk for your home or office', 150.00, 5, 'Furniture'), (4, 'Fast Leather Sofa', 'A leather sofa for your home or office', 300.00, 3, 'Furniture'), (5, 'Fast Organic Cotton T-shirt', 'A comfortable t-shirt for your home or office', 20.00, 100, 'Clothing')"

Now, you can deploy the application with the following command:

npm run deploy

This will deploy the application to Workers and the D1 database will be replicated to the remote regions. If a user requests the application from any region, the request will be redirected to the nearest region where the database is replicated.

Conclusion

In this tutorial, you learned how to use D1 Read Replication for your e-commerce website. You created a D1 database and enabled read replication for it. You then created an API to create and update products in the database. You also learned how to use the bookmark to get the latest data from the database.

You then created the products table in the remote database and deployed the application.

You can use the same approach for your existing read heavy application to reduce read latencies and improve read throughput. If you are using an external platform to manage the content, you can connect the external platform to the D1 database, so that the content is automatically updated in the database.

You can find the complete code for this tutorial in the GitHub repository ↗︎.

Was this helpful?

YesNo

On this page

Using D1 Read Replication for your e-com·续 - 图1Docs

{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/d1/tutorials/using-read-replication-for-e-com/#page","headline":"Using D1 Read Replication for your e-commerce website","description":"D1 Read Replication 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.","url":"https://developers.cloudflare.com/d1/tutorials/using-read-replication-for-e-com/","inLanguage":"en","image":"https://developers.cloudflare.com/d1/tutorials/using-read-replication-for-e-com/og.png?v=47a40a3273934c8c","dateModified":"2026-08-25","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"},"keywords":["JavaScript","TypeScript","SQL"]}


description: Explore demo applications and reference architectures that use D1 databases. title: Demos and architectures image: https://developers.cloudflare.com/d1/demos/og.png?v=fc3162fbfbb60439


Skip to content

Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/d1/llms.txt
Use this file to discover all available pages before exploring further.