Skip to content

thecodeorigin/todo

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

Layer Technology
Edge runtime Cloudflare Workers
Container runtime Cloudflare Containers (Firecracker, linux/amd64)
Container server Bun
Database Cloudflare D1 (SQLite)
ORM Drizzle ORM
Rate limiting Durable Object (in-memory, per-IP, hourly bucket)
Domain todo.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

License

Stars

Watchers

Forks

Contributors