Build a seat booking app with SQLite in Durable Objects

Last updated Oct 12, 2026 In this tutorial, you will learn how to build a seat reservation app using Durable Objects. This app will allow users to book a seat for a flight. The app will be written in TypeScript and will use the new SQLite storage backend in Durable Object to store the data.

Using Durable Objects, you can write reusable code that can handle coordination and state management for multiple clients. Moreover, writing data to SQLite in Durable Objects is synchronous and uses local disks, therefore all queries are executed with great performance. You can learn more about SQLite storage in Durable Objects in the SQLite in Durable Objects blog post ↗︎.

SQLite in Durable Objects

SQLite in Durable Objects is currently in beta. You can learn more about the limitations of SQLite in Durable Objects in the SQLite in Durable Objects documentation.

The application will function as follows:

  • A user navigates to the application with a flight number passed as a query parameter.
  • The application will create a new Durable Object for the flight number, if it does not already exist.
  • If the Durable Object already exists, the application will retrieve the seats information from the SQLite database.
  • If the Durable Object does not exist, the application will create a new Durable Object and initialize the SQLite database with the seats information. For the purpose of this tutorial, the seats information is hard-coded in the application.
  • When a user selects a seat, the application asks for their name. The application will then reserve the seat and store the name in the SQLite database.
  • The application also broadcasts any changes to the seats to all clients.

Let's get started!

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>

1. Create a new project

Create a new Worker project to create and deploy your app.

  1. Create a Worker named seat-booking by running:npmyarnpnpm
   npm create cloudflare@latest -- seat-booking
   ```

yarn create cloudflare seat-booking ```

   pnpm create cloudflare@latest seat-booking
   ```

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 `Worker + Durable Objects`.
   - 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).
2. Change into your new project directory to start developing:

```sh
cd seat-booking

2. Create the frontend

The frontend of the application is a simple HTML page that allows users to select a seat and enter their name. The application uses Workers Static Assets to serve the frontend.

  1. Create a new directory named public in the project root.
  2. Create a new file named index.html in the public directory.
  3. Add the following HTML code to the index.html file:

<details>

<summary>

public/index.html

</summary>

public/index.htmlhtml

<!doctype html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Flight Seat Booking</title>
        <style>
            body {
                font-family: Arial, sans-serif;
                display: flex;
                justify-content: center;
                align-items: center;
                height: 100vh;
                margin: 0;
                background-color: #f0f0f0;
            }
            .booking-container {
                background-color: white;
                padding: 20px;
                border-radius: 8px;
                box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
            }
            .seat-grid {
                display: grid;
                grid-template-columns: repeat(7, 1fr);
                gap: 10px;
                margin-top: 20px;
            }
            .aisle {
                grid-column: 4;
            }
            .seat {
                width: 40px;
                height: 40px;
                display: flex;
                justify-content: center;
                align-items: center;
                border: 1px solid #ccc;
                cursor: pointer;
            }
            .seat.available {
                background-color: #5dbf61ba;
                color: white;
            }
            .seat.unavailable {
                background-color: #f4433673;
                color: white;
                cursor: not-allowed;
            }
            .airplane {
                display: flex;
                flex-direction: column;
                align-items: center;
                background-color: #f0f0f0;
                padding: 20px;
                border-radius: 20px;
            }
        </style>
    </head>
    <body>
        <div class="booking-container">
            <h2 id="title"></h2>
            <div class="airplane">
                <div id="seatGrid" class="seat-grid"></div>
            </div>
        </div>

<script>
            const seatGrid = document.getElementById("seatGrid");
            const title = document.getElementById("title");

const flightId = window.location.search.split("=")[1];

const hostname = window.location.hostname;

if (flightId === undefined) {
                title.textContent = "No Flight ID provided";
                seatGrid.innerHTML = "<p>Add `flightId` to the query string</p>";
            } else {
                handleBooking();
            }

function handleBooking() {
                let ws;
                if (hostname === 'localhost') {
            const port = window.location.port;
                    ws = new WebSocket(`ws://${hostname}:${port}/ws?flightId=${flightId}`);
                } else {
                    ws = new WebSocket(`wss://${hostname}/ws?flightId=${flightId}`);
                }

title.textContent = `Book seat for flight ${flightId}`;

ws.onopen = () => {
                    console.log("Connected to WebSocket server");
                };

function createSeatGrid(seats) {
                    seatGrid.innerHTML = "";
                    for (let row = 1; row <= 10; row++) {
                        for (let col = 0; col < 6; col++) {
                            if (col === 3) {
                                const aisle = document.createElement("div");
                                aisle.className = "aisle";
                                seatGrid.appendChild(aisle);
                            }

const seatNumber = `${row}${String.fromCharCode(65 + col)}`;
                            const seat = seats.find((s) => s.seatNumber === seatNumber);
                            const seatElement = document.createElement("div");
                            seatElement.className = `seat ${seat && seat.occupant ? "unavailable" : "available"}`;
                            seatElement.textContent = seatNumber;
                            seatElement.onclick = () => bookSeat(seatNumber);
                            seatGrid.appendChild(seatElement);
                        }
                    }
                }

async function fetchSeats() {
                    const response = await fetch(`/seats?flightId=${flightId}`);
                    const seats = await response.json();
                    createSeatGrid(seats);
                }

async function bookSeat(seatNumber) {
                    const name = prompt("Please enter your name:");
                    if (!name) {
                        return; // User canceled the prompt
                    }

const response = await fetch(`book-seat?flightId=${flightId}`, {
                        method: "POST",
                        headers: { "Content-Type": "application/json" },
                        body: JSON.stringify({ seatNumber, name }),
                    });
                    const result = await response.text();
                    fetchSeats();
                }

ws.onmessage = (event) => {
                    try {
                        const seats = JSON.parse(event.data);
                        createSeatGrid(seats);
                    } catch (error) {
                        console.error("Error parsing WebSocket message:", error);
                    }
                };

ws.onerror = (error) => {
                    console.error("WebSocket error:", error);
                };

ws.onclose = (event) => {
                    console.log("WebSocket connection closed:", event);
                };

fetchSeats();
            }
        </script>
    </body>

</html>

</details>

  • The frontend makes an HTTP GET request to the /seats endpoint to retrieve the available seats for the flight.
  • It also uses a WebSocket connection to receive updates about the available seats.
  • When a user clicks on a seat, the bookSeat() function is called that prompts the user to enter their name and then makes a POST request to the /book-seat endpoint.

  • Update the bindings in the Wrangler configuration file to configure assets to serve the public directory.

{
    "assets": {
        "directory": "public"
    }
}
[assets]
directory = "public"
  1. If you start the development server using the following command, the frontend will be served at http://localhost:8787. However, it will not work because the backend is not yet implemented.
npm run dev

Workers Static Assets

Workers Static Assets is currently in beta. You can also use Cloudflare Pages to serve the frontend. However, you will need a separate Worker for the backend.

3. Create table for each flight

The application already has the binding for the Durable Objects class configured in the Wrangler configuration file. If you update the name of the Durable Objects class in src/index.ts, make sure to also update the binding in the Wrangler configuration file.

  1. Update the binding to use the SQLite storage in Durable Objects. In the Wrangler configuration file, replace new_classes=["Flight"] with new_sqlite_classes=["Flight"], name = "FLIGHT" with name = "FLIGHT", and class_name = "MyDurableObject" with class_name = "Flight". your Wrangler configuration file should look similar to this:
{
    "durable_objects": {
        "bindings": [
            {
                "name": "FLIGHT",
                "class_name": "Flight"
            }
        ]
    },
    // Durable Object migrations.
    // Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#migrations
    "migrations": [
        {
            "tag": "v1",
            "new_sqlite_classes": [
                "Flight"
            ]
        }
    ]
}
[[durable_objects.bindings]]
name = "FLIGHT"
class_name = "Flight"

[[migrations]]
tag = "v1"
new_sqlite_classes = [ "Flight" ]

Your application can now use the SQLite storage in Durable Objects.

  1. Add the initializeSeats() function to the Flight class. This function will be called when the Durable Object is initialized. It will check if the table exists, and if not, it will create it. It will also insert seats information in the table.

For this tutorial, the function creates an identical seating plan for all the flights. However, in production, you would want to update this function to insert seats based on the flight type.

Replace the Flight class with the following code:

src/index.tsts

```ts import { DurableObject } from "cloudflare:workers";

export class Flight extends DurableObject { sql = this.ctx.storage.sql;

constructor(ctx: DurableObjectState, env: Env) { super(ctx, env); this.initializeSeats(); }

private initializeSeats() { const cursor = this.sql.exec(PRAGMA table_list);