How to Build a Serverless Web App with Cloudflare Workers and D1

How to Build a Serverless Web App with Cloudflare Workers and D1

You have a great idea for a web app. You want to ship it fast. You do not want to manage servers, patch databases at 3 AM, or worry about scaling. The solution is a serverless architecture. And in 2026, one of the most practical stacks for that is Cloudflare Workers paired with D1.

Cloudflare Workers lets you run JavaScript at the edge. D1 is a serverless SQLite database built into Cloudflare’s network. Together, they let you build a full-stack web app without provisioning a single virtual machine. This guide walks you through the entire process.

Key Takeaway

Building a serverless web app with Cloudflare Workers and D1 gives you a scalable, low cost backend that deploys globally in seconds. You will learn to set up a Workers project, define a D1 database schema, create REST endpoints, and handle data validation. The result is a production ready app with zero server management.

Why Cloudflare Workers and D1 Work So Well

Serverless does not mean magic. It means someone else handles the infrastructure. Cloudflare runs Workers on their global network of data centers. When a user makes a request, the nearest data center runs your code. That means low latency for everyone.

D1 is a relational database built on SQLite. It is serverless in the truest sense. You do not configure a cluster. You do not worry about connection limits. You write SQL, and D1 handles the rest. Replication happens automatically across Cloudflare’s network.

This combo is especially good for:

  • APIs for mobile or web apps. You can serve JSON responses from the same edge location as your database reads.
  • Internal tools. Spin up a CRUD app for your team in an afternoon.
  • Prototypes that go to production. You can start small and scale without rewriting your code.

Setting Up Your Project

Before you write any code, you need the Cloudflare CLI. It is called wrangler. Install it globally with npm or use the official installer.

npm install -g wrangler

Next, authenticate with your Cloudflare account.

wrangler login

Now create a new Workers project.

npx wrangler init my-serverless-app

Choose “Hello World” worker when prompted. This gives you a basic src/index.ts file. We will replace that with our own logic.

Creating Your D1 Database

You can create a D1 database from the command line.

npx wrangler d1 create my-app-db

The CLI will output a binding name and a database ID. You need to add those to your wrangler.toml file.

name = "my-serverless-app"
main = "src/index.ts"
compatibility_date = "2026-01-01"

[[d1_databases]]
binding = "DB"
database_name = "my-app-db"
database_id = "your-database-id-here"

Now run a migration to create your first table.

npx wrangler d1 migrations create my-app-db create-users-table

Open the generated SQL file in the migrations folder and add your schema.

CREATE TABLE IF NOT EXISTS users (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  name TEXT NOT NULL,
  email TEXT NOT NULL UNIQUE,
  created_at TEXT DEFAULT (datetime('now'))
);

Apply the migration.

npx wrangler d1 migrations apply my-app-db

Writing Your Worker Code

Your worker will handle HTTP requests and talk to D1. Here is a basic example that handles a GET and POST route for users.

export interface Env {
  DB: D1Database;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    const path = url.pathname;

    if (request.method === "GET" && path === "/users") {
      const { results } = await env.DB.prepare(
        "SELECT * FROM users ORDER BY created_at DESC"
      ).all();
      return Response.json(results);
    }

    if (request.method === "POST" && path === "/users") {
      const body = await request.json();
      const { name, email } = body;

      if (!name || !email) {
        return new Response("Missing name or email", { status: 400 });
      }

      const { success } = await env.DB.prepare(
        "INSERT INTO users (name, email) VALUES (?, ?)"
      ).bind(name, email).run();

      if (success) {
        return new Response("User created", { status: 201 });
      } else {
        return new Response("Failed to create user", { status: 500 });
      }
    }

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

This is a minimal example. In a real app, you would add authentication, pagination, and more robust error handling.

Deploying Your App

Deployment is a single command.

npx wrangler deploy

Your app will be live at a workers.dev subdomain. You can also route a custom domain through Cloudflare’s dashboard.

Common Mistakes and How to Avoid Them

Even experienced developers hit a few snags with this stack. Here is a table of the most common issues and their fixes.

Mistake Why It Happens How to Fix
Forgetting to bind the database The wrangler.toml binding is missing. Double check the [[d1_databases]] section matches your database name.
Not awaiting database calls D1 queries are async. Always use await before DB.prepare().
Using SQLite features not supported by D1 D1 does not support VACUUM or PRAGMA statements. Stick to standard SQLite syntax. Check the D1 docs for limitations.
Hardcoding environment variables Secrets should not be in your code. Use wrangler secret put for API keys and tokens.
Ignoring cold starts Workers can have a brief cold start on first request. Keep your worker bundle small. Avoid large dependencies.

Practical Tips for Your First App

Here are a few things that will make your development smoother.

  • Use TypeScript. Workers have first class TypeScript support. It catches type errors before they hit production.
  • Test locally with Wrangler dev. Run npx wrangler dev to test your worker on localhost:8787. It will simulate D1 locally.
  • Keep your SQL simple. D1 is SQLite under the hood. Complex joins across many tables can be slow. Design your schema with edge performance in mind.
  • Monitor your usage. Cloudflare provides analytics for Workers and D1. Watch for unexpected query volume.

A Real World Example: A Task Manager API

Let us build a small task manager to see the full flow. You will create a tasks table and endpoints for CRUD operations.

First, create a new migration.

npx wrangler d1 migrations create my-app-db create-tasks-table

Add this schema.

CREATE TABLE IF NOT EXISTS tasks (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  title TEXT NOT NULL,
  completed INTEGER DEFAULT 0,
  user_id INTEGER,
  created_at TEXT DEFAULT (datetime('now')),
  FOREIGN KEY (user_id) REFERENCES users(id)
);

Now add routes to your worker.

if (request.method === "GET" && path === "/tasks") {
  const userId = url.searchParams.get("userId");
  let query = "SELECT * FROM tasks";
  const params: any[] = [];

  if (userId) {
    query += " WHERE user_id = ?";
    params.push(userId);
  }

  query += " ORDER BY created_at DESC";
  const { results } = await env.DB.prepare(query).bind(...params).all();
  return Response.json(results);
}

if (request.method === "POST" && path === "/tasks") {
  const body = await request.json();
  const { title, userId } = body;

  if (!title) {
    return new Response("Missing title", { status: 400 });
  }

  const { success } = await env.DB.prepare(
    "INSERT INTO tasks (title, user_id) VALUES (?, ?)"
  ).bind(title, userId || null).run();

  if (success) {
    return new Response("Task created", { status: 201 });
  } else {
    return new Response("Failed to create task", { status: 500 });
  }
}

This gives you a working API. You can connect a frontend framework like React or Svelte to it. For more on frontend architecture, check out our guide on top trends in front end frameworks for 2026.

When This Stack Is Not the Right Fit

No tool is perfect for every job. Cloudflare Workers and D1 have limitations.

  • Long running tasks. Workers have a CPU time limit. If you need to process large files or run heavy computations, consider a different runtime.
  • Very complex queries. D1 is best for straightforward SQL. If you need full text search across millions of rows, look at a dedicated search service.
  • Stateful connections. Workers are stateless by design. If your app requires WebSocket connections that persist for hours, you may need a different approach.

For most web apps, though, this stack handles the job with grace.

“The best architecture is the one you do not have to think about. With Workers and D1, I focus on features, not infrastructure.” – A developer who migrated from a VPS setup.

Your Next Steps

You now have the foundation. Your next move is to build something real. Start with a simple app like a guestbook or a personal notes API. Add authentication using Cloudflare’s built in features. Connect a frontend and deploy it.

If you want to deepen your understanding of modern web capabilities, read about harnessing webassembly for next generation web applications in 2026. It pairs well with Workers for compute heavy tasks.

Also consider reading about how to optimize web performance with modern javascript techniques. Performance and serverless go hand in hand.

Bringing Your Serverless App to Life

The real power of this stack is how fast you can go from idea to deployed app. In one afternoon, you can have a database, an API, and a global CDN all working together. No servers to provision. No SSH keys to manage. No late night outages.

Try it this week. Pick a small project you have been putting off. Build it with Workers and D1. You will be surprised how much you can accomplish when the infrastructure just works.

Leave a Reply

Your email address will not be published. Required fields are marked *