Skip to content

Repository files navigation

todo

A TODO app running on Cloudflare Containers with a D1 SQLite database.

Architecture

Browser
│
▼
Cloudflare Worker (src/index.ts)
├── GET/POST/PATCH/DELETE /api/todos → D1 via Drizzle ORM
├── POST /api/todos → RateLimiter Durable Object (20 req/hour/IP)
└── /* → proxy to Container
│
▼
Bun HTTP server (app/server.ts)
Serves HTML + client-side JS

Why two runtimes?
D1 is a Cloudflare-native binding — it has no TCP endpoint and is only accessible from the Workers runtime. The container (Bun) handles stateless UI serving; the Worker owns all database access.

Stack

LayerTechnology
Edge runtimeCloudflare Workers
Container runtimeCloudflare Containers (Firecracker, linux/amd64)
Container serverBun
DatabaseCloudflare D1 (SQLite)
ORMDrizzle ORM
Rate limitingDurable Object (in-memory, per-IP, hourly bucket)
Domaintodo.thecodeorigin.com

Project structure

├── app/
│ ├── server.ts # Bun HTTP server — serves UI, /api/instance
│ └── tsconfig.json # Bun-specific TS config (bun-types)
├── migrations/
│ ├── 0001_init.sql # todos table
│ └── 0002_rate_limits.sql # rate_limits table (unused, kept for history)
├── src/
│ ├── index.ts # Worker entry — API routes, RateLimiter DO, TodoContainer
│ └── schema.ts # Drizzle table definitions
├── Dockerfile # FROM oven/bun:1-alpine
├── tsconfig.json # Workers TS config (@cloudflare/workers-types)
└── wrangler.jsonc # Cloudflare deploy config

Prerequisites

Setup

npm install

Set your Cloudflare API token (requires Workers, D1, and Containers permissions):

export CLOUDFLARE_API_TOKEN=<your-token># Windows PowerShell:# $env:CLOUDFLARE_API_TOKEN = "<your-token>"

Create the D1 database (first time only):

npx wrangler d1 create todo-db
# Copy the database_id into wrangler.jsonc

Apply migrations:

npx wrangler d1 migrations apply todo-db --remote

Development

npm run dev # wrangler dev (Workers local emulation)

The container is not emulated locally — /api/todos routes work via wrangler dev, but the UI proxy to the container won't resolve. Run bun app/server.ts separately on port 3000 if you need the full UI locally.

Deploy

npm run deploy # wrangler deploy

Wrangler builds and pushes the Docker image to Cloudflare's registry, deploys the Worker, and wires up the custom domain automatically.

Rate limiting

POST /api/todos is limited to 20 requests per IP per hour, enforced by a RateLimiter Durable Object. Each IP maps to a named DO instance that keeps an in-memory counter per hourly bucket. No database writes on the hot path.

Exceeding the limit returns:

HTTP 429 Too Many Requests
Retry-After: 3600
{"error": "Rate limit exceeded — max 20 todos per hour per IP."}

License

MIT

About

Minimal TODO example with D1, deployed on Cloudflare Containers

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages