How front-end, back-end and DB connect
A browser, a Worker, and a database. Three small pieces, wired together with bindings. Once you see how they fit, the whole Integrations section clicks.
What are we wiring together?
Almost every web app is made of three jobs: showing things to a person, running logic and rules, and remembering data. On Cloudflare those three jobs map to three pieces — the browser (front-end), a Worker (your back-end API), and a database (D1, KV, R2, or Durable Objects). This guide gives you the mental map for how they connect.
Front-end means the part that runs inside the user's browser — the HTML, CSS and JavaScript they actually see and click. Back-end means code that runs on a server (here, a Worker) which the user never sees directly; it does the thinking and talks to the database. A database is just where data is stored so it survives after the request ends.
Think of it like a restaurant
The browser is the dining room where the customer sits and orders. The Worker is the kitchen that receives orders and decides what to do. The database is the pantry where ingredients are stored. The customer never walks into the pantry — they ask the kitchen, and the kitchen fetches from the pantry.
Notice two different arrows leaving the browser. One loads the static files that make up the page (the look). The other is an API call that asks the Worker for fresh data (the content). Keeping these separate is the heart of the whole model.
The three layers, and who does what
Each layer has one clear job. Mixing the jobs up is the most common beginner mistake (for example, putting your database password in front-end code, where anyone can read it). Keep the responsibilities separated like this:
Front-end — the browser
Renders the UI and reacts to clicks. It owns nothing secret. It asks the back-end for data using fetch() and shows the result.
Back-end — the Worker
Receives requests, applies your rules and security checks, holds the secrets, and is the only layer allowed to touch the database.
Data — the database
Remembers data between requests. On Cloudflare this can be D1 (SQL), KV (key-value), R2 (files), or Durable Objects (live state).
Why the split matters
Anything in the browser is public — users can open dev tools and read it. Secrets and database access must stay in the Worker, never the front-end.
One Worker can be both
On Cloudflare a single Worker often serves the static front-end files AND runs the back-end API. They live in one project but stay logically separate: static files on most paths, API logic on paths like /api/*.
Follow one request, end to end
The fastest way to understand a full-stack app is to trace a single click. Imagine a page with a "Load todos" button. Here is exactly what happens, step by step, when the user presses it.
What each step means
- Click: the browser's JavaScript reacts to the button press and calls fetch().
- fetch GET /api/todos: an HTTP request leaves the browser and is routed to your Worker.
- SELECT ...: the Worker asks the database for the rows it needs.
- rows: the database hands the matching data back to the Worker.
- JSON response: the Worker turns that data into JSON (a simple text format) and replies.
- render: the browser receives the JSON and updates the page — no full reload needed.
Why JSON?
JSON (JavaScript Object Notation) is a lightweight text format that both the Worker and the browser understand natively. The back-end sends data, not HTML, so the front-end is free to display it however it likes.
Bindings: how a Worker reaches storage
A Worker doesn't connect to a database with a password and a connection string the way old servers did. Instead you declare a binding — a named, secure connection that Cloudflare wires up for you. In your code the binding appears as a property on the env object, like env.DB.
D1 → env.DB
A SQL database for structured, related data like users and orders. Query it with SELECT / INSERT / UPDATE.
KV → env.CACHE
A key-value store for simple lookups read very often, like settings or cached pages. Super fast reads worldwide.
R2 → env.BUCKET
Object storage for large files — images, videos, PDFs, backups. Like a folder in the cloud, with no egress fees.
Durable Objects → env.ROOM
A single, consistent place to keep live state — perfect for chat rooms, game lobbies, or counters that many users share.
The name is yours to choose
DB, CACHE, BUCKET and ROOM are just names you pick in wrangler.toml. Whatever you write as the binding name is exactly what appears on env in your code. Pick clear names and you'll never get lost.
Wire it up yourself
Here is the smallest possible version of all three layers working together: a front-end that calls fetch, a Worker that returns JSON from D1, the wrangler.toml that binds them, and the SQL that creates the data. Copy these into one project and you have a real full-stack app.
1. The front-end (runs in the browser)
Plain JavaScript that calls your API on click and paints the result. No framework needed to understand the idea.
<!-- public/index.html --> <button id="load">Load todos</button> <ul id="list"></ul> <script> async function loadTodos() { // Ask the Worker (back-end) for data const res = await fetch("/api/todos"); const todos = await res.json(); // Paint the result into the page document.querySelector("#list").innerHTML = todos.map((t) => `<li>${t.title}</li>`).join(""); } document.querySelector("#load").addEventListener("click", loadTodos); </script>2. The back-end Worker (returns JSON)
It serves static files for normal paths, and for /api/todos it reads D1 via env.DB and replies with JSON. Notice the user's request never touches the database directly — only the Worker does.
// src/index.js export default { async fetch(request, env, ctx) { const url = new URL(request.url); if (url.pathname === "/api/todos") { // env.DB is the D1 binding from wrangler.toml const { results } = await env.DB .prepare("SELECT id, title FROM todos") .all(); return Response.json(results); } // Everything else is served as a static asset return env.ASSETS.fetch(request); }, };3. The wrangler.toml (wires the binding)
This config file tells Cloudflare your Worker's entry file, where the static files live, and which database to expose as env.DB.
name = "my-fullstack-app" main = "src/index.js" compatibility_date = "2025-01-01" # Serve the front-end from ./public, exposed as env.ASSETS [assets] directory = "./public" binding = "ASSETS" # Bind a D1 database; reach it in code as env.DB [[d1_databases]] binding = "DB" database_name = "my-app-db" database_id = "<paste-your-database-id>"4. The data layer (create the table)
Run this SQL once to create the todos table and add a couple of rows, so your /api/todos endpoint has something to return.
CREATE TABLE IF NOT EXISTS todos ( id INTEGER PRIMARY KEY, title TEXT NOT NULL ); INSERT INTO todos (title) VALUES ('Learn the full-stack model'), ('Wire a Worker to D1');5. Run it, then deploy
wrangler dev starts everything locally at http://localhost:8787. When it looks good, wrangler deploy puts all three layers live on Cloudflare's global network in one shot.
npx wrangler d1 execute my-app-db --local --file=./schema.sql npx wrangler dev npx wrangler deploy
Next step: full CRUD
This guide is read-only (it only loads data). To also create, update and delete rows from the browser, follow the companion guide "Front-end ↔ Worker ↔ D1: full CRUD".
Key terms in one place
Front-end
Code that runs in the user's browser: the HTML, CSS and JavaScript they see and interact with.
Back-end
Code that runs on a server — here a Worker — handling logic, security, and database access out of the user's sight.
API
Application Programming Interface — the set of URLs (like /api/todos) the front-end calls to ask the back-end for things.
fetch()
The built-in browser function for sending an HTTP request to a URL and reading the response — how the front-end talks to the API.
Binding
A named, secure connection from a Worker to another service (D1, KV, R2, Durable Objects), accessed via the env object.
wrangler.toml
The config file where you name your Worker and declare its bindings. Wrangler reads it when you run or deploy.
JSON
A lightweight text format for data. The back-end usually replies in JSON so the front-end can easily read it.
Database
Where data is stored so it survives between requests. On Cloudflare: D1, KV, R2, or Durable Objects.
Common pitfalls & tips
Never put secrets in the front-end
API keys, database credentials and tokens belong in the Worker only. Anything shipped to the browser can be read by anyone using dev tools. Keep secrets server-side with Wrangler secrets (wrangler secret put).
Things beginners trip over
- CORS errors: if your front-end and API are on the same Worker, you usually avoid them entirely — serving both from one origin is the simplest setup.
- Forgetting await: fetch() and D1 queries are asynchronous; always await them or you'll get a Promise instead of data.
- Returning HTML when you meant JSON: use Response.json(data) so the front-end can call res.json() cleanly.
- Binding name mismatch: the name in wrangler.toml must match what you read on env (env.DB needs binding = "DB").
- Editing local vs remote DB: --local touches your machine's copy; drop it (or use --remote) to change the deployed database.
It's cheap to start
Workers' free plan gives 100,000 requests a day and D1 includes a generous free tier — plenty to build and learn a full-stack app before paying anything. Check the docs for current limits before you launch.
Related products
menu_bookOfficial docsopen_in_new