An opinionated starter for Cloudflare Workers projects focused on:
- High performance + high security
- Great developer experience (local + remote)
- A reusable backend core that can be consumed by SolidStart / SvelteKit / TanStack Start / QwikCity
Create an optimized Cloudflare “Northstar” template repo that serves as a framework-agnostic starter for many app types (blog, dashboard, marketplace, etc.) without hardcoding a specific use case.
Split-worker rationale: keep the public entrypoint thin while routing /api/* to a private-by-design API Worker via Service Bindings for a clean security boundary.
- TypeScript with strict config tuned for Workers and shared packages
- Zod validation for requests, query params, and env bindings
- Drizzle + D1 with local and remote migration patterns
- KV and Durable Objects included as first-class patterns
- Better Auth with D1-backed schema/migrations and session/token guidance
- Wrangler config optimized for local dev and production deploys
- Secrets handling via
.dev.varslocally andpnpm secrets:bulkremotely (CI-friendly) - Testing scaffolding for Workers, D1/Drizzle, Durable Objects, and Playwright E2E
Browser
|
v
apps/web (public worker)
|
v Service Binding: env.API.fetch(...)
apps/api (private worker)
|
v
D1 + KV + Durable Objects
- Worker-to-worker calls stay on the Cloudflare edge without a public URL, which reduces latency and avoids CORS complexity.
- The public Worker proxies
/api/*to the private API Worker via theAPIbinding:
if (url.pathname.startsWith("/api/")) return env.API.fetch(request);- Service Bindings avoid outbound HTTPS hops and keep intra-worker calls on the Cloudflare edge.
apps/webproxies/api/*soSet-Cookiereaches the browser from the public origin.apps/apistays private-by-design to minimize surface area and centralize auth/data logic.- Keep DB/auth as a single source of truth to prevent cross-app drift and security gaps.
apps/api: private-by-design Cloudflare Worker (Hono) with vertical slices + D1/Drizzle + Better Authapps/web: public “edge web” Worker that proxies/api/*toapps/apivia Service Bindings so cookies are set on the public originpackages/shared: shared Zod schemas/types/helpers for API boundariespackages/db: shared Drizzle + D1 DB factory + schema (includes Better Auth tables)packages/ui: shared Tailwind v4 CSS entrypoint (framework-agnostic)
- Bindings: D1
DB, KVKV, Durable ObjectRATE_LIMITER, Rate Limiting APIAUTH_*, Service BindingAPI, optional assetsASSETS(seeapps/web/wrangler.jsonc) - Feature toggles:
AUTH_RATE_LIMIT_ENABLED,D1_SESSIONS_ENABLED,REQUEST_LOG_ENABLED - Required env:
BETTER_AUTH_SECRET,BETTER_AUTH_URL,CORS_ORIGINS,AUTH_TRUSTED_ORIGINS,ENVIRONMENT - Details in
docs/dev.md,docs/auth.md, anddocs/database.md
pnpm install
cp .dev.vars.example .dev.vars
pnpm db:migrate:local
pnpm devThen open http://localhost:8787 (try /demo).
Local dev:
cp .dev.vars.example .dev.vars- Required keys:
BETTER_AUTH_SECRET,BETTER_AUTH_URL - Production requirement (when you switch to prod):
BETTER_AUTH_URLmust behttps://
Production deploy:
- CI secrets:
CLOUDFLARE_API_TOKEN,CLOUDFLARE_ACCOUNT_ID,DEV_VARSDEV_VARSshould contain the full.dev.varscontent (same format as local, including newlines)- Create it in GitHub: Settings > Secrets and variables > Actions > New repository secret
- Keep
DEV_VARSin sync when you add new keys to.dev.vars - The deploy workflow writes
.dev.varsfromDEV_VARSand runspnpm secrets:bulkautomatically - Upload secrets (deploys immediately):
pnpm secrets:bulk - Deploy workers:
pnpm deploy - Optional env:
pnpm secrets:bulk -- --env <name>andpnpm deploy -- --env <name>
Optional CI-style gate:
pnpm checkSee docs/ for:
- Project structure (monorepo + vertical slices)
- D1/Drizzle migrations (local + remote)
- Better-Auth sessions + tokens
- KV + Durable Objects patterns
- Testing (Vitest + workerd + Playwright E2E)