A production-ready monorepo for building Cloudflare Workers with Effect-TS, featuring shared domain models, type-safe API contracts, and database integration.
What’s Changed
-
Cloudflare D1 as the Database This PR migrates persistence to Cloudflare D1, providing a fully serverless, edge-friendly SQL database tightly integrated with Cloudflare Workers.
-
Frontend Built with SvelteKit The frontend is now implemented using SvelteKit, enabling fast builds, modern routing, and excellent performance for both SSR and client-side rendering.
-
Unified API Access from the Svelte Client The SvelteKit client can easily interact with both
effect-apiandeffect-rpc, offering a consistent and ergonomic way to call backend logic from the frontend. -
MsgPack-Based RPC Protocol The RPC layer now uses MsgPack as its serialization protocol, resulting in more compact payloads and improved performance compared to JSON-based communication.
-
Simple TanStack Query Wrapper for Svelte A lightweight wrapper around TanStack Query is provided for Svelte, simplifying data fetching, caching, and synchronization with minimal boilerplate.
- Node.js (recommended LTS)
- pnpm
- Cloudflare account with D1 enabled
-
Open the following files and connect your D1 database:
apps/effect-worker-api/wrangler.jsoncapps/effect-worker-rpc/wrangler.jsoncapps/svelte-client/wrangler.jsonc
Make sure the D1 bindings and database IDs are correctly set in both files.
-
Set up environment variables:
- Go to
packages/db - Rename
.env.exampleto.env - Fill in all required environment variables
- Go to
-
Install dependencies (if you haven’t already):
pnpm install
-
Generate and push the database schema:
pnpm db:generate pnpm db:push
From the repository root:
pnpm install # Install dependencies
pnpm build # Build all packages
pnpm check # Type-check the codebase
pnpm test # Run testscd apps/effect-worker-api
pnpm dev # Start the dev serverpnpm dev:apps┌─────────────────────────────────────────────────────────────────┐
│ Applications │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │effect-worker-api │ │ effect-worker-rpc│ │ sveltekit │ │
│ │ (HTTP REST) │ │ (RPC MsgPack) │ │ (Full-Stack UI) │ │
│ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Shared Packages │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌──────────────┐ │
│ │ domain │ │ contracts │ │cloudflare │ │ db │ │
│ │ (types) │ │ (API) │ │ (infra) │ │ (schema) │ │
│ └───────────┘ └───────────┘ └───────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
All packages use the @repo/* namespace for internal monorepo imports.
Core domain types, branded schemas, and errors.
import { UserId, UserSchema, UserNotFoundError } from "@repo/domain"
// Branded types for type-safe IDs
const id: UserId = "usr_abc123" as UserIdAPI definitions for HTTP and RPC endpoints. Defines the contract between client and server.
import { WorkerApi, UsersGroup, UsersRpc } from "@repo/contracts"HTTP Groups:
HealthGroup- Health check endpointsUsersGroup- User CRUD operations
RPC Procedures:
UsersRpc- User operations via RPC
Infrastructure layer for Cloudflare Workers integration with Effect.
import {
withCloudflareBindings, // Wrap effects with env/ctx
CloudflareBindings, // Service tag
SqliteDrizzle, // Database connection
currentEnv // FiberRef for env access
} from "@repo/cloudflare"Drizzle ORM schema definitions.
import { users } from "@repo/db"REST HTTP API built with @effect/platform.
cd apps/effect-worker-api
pnpm dev # Local dev server
pnpm deploy # Deploy to CloudflareEndpoints:
GET /health- Health checkGET /users- List usersGET /users/:id- Get user by IDPOST /users- Create user
RPC API using @effect/rpc for procedure-based communication.
cd apps/effect-worker-rpc
pnpm dev # Local dev server
pnpm deploy # Deploy to CloudflareEndpoints:
POST /rpc- RPC endpoint
Full-stack Svelte application with Sveltekit, featuring Effect-TS integration on tanstack query and server query.
cd apps/svelte-client
pnpm dev # Local dev server
pnpm deploy # Deploy to CloudflareFeatures:
- TanStack Query (server state management)
- Simple Effect wrapper for tanstack query and server query
- Tailwind CSS v4 + Shadcn/UI components
Effect Integration Pattern:
import { Effect } from "effect"
import { effectQuery } from "$lib/effect-wrapper/server"
export const getHealth = effectQuery(
Effect.gen(function* () {
return {
status: "ok",
timestamp: Date.now()
}
})
)Request-scoped Cloudflare bindings via Effect's FiberRef:
// Entry point wraps effect with bindings
const effect = handleRequest(request).pipe(withCloudflareBindings(env, ctx))
return runtime.runPromise(effect)
// Handlers access via service
Effect.gen(function* () {
const { env } = yield* CloudflareBindings
// Use env.MY_KV, env.MY_R2, etc.
})Contracts define abstract middleware tags, apps provide implementations:
// In contracts (abstract)
export class DatabaseMiddleware extends HttpApiMiddleware.Tag<DatabaseMiddleware>()(
"DatabaseMiddleware",
{ failure: DatabaseConnectionError, provides: SqliteDrizzle }
) {}
// In app (implementation)
export const DatabaseMiddlewareLive = Layer.effect(
DatabaseMiddleware,
Effect.gen(function* () {
const drizzle = yield* makeDrizzle()
return drizzle
})
)Type-safe handlers using Effect generators:
export const UsersGroupLive = HttpApiBuilder.group(
WorkerApi,
"users",
(handlers) =>
handlers
.handle("list", () =>
Effect.gen(function* () {
const drizzle = yield* SqliteDrizzle
return yield* drizzle.select().from(users)
})
)
.handle("get", ({ path: { id } }) =>
Effect.gen(function* () {
const drizzle = yield* SqliteDrizzle
const user = yield* drizzle
.select()
.from(users)
.where(eq(users.id, id))
if (!user) return yield* Effect.fail(new UserNotFoundError({ id }))
return user
})
)
)Typed errors with automatic HTTP status mapping:
export class UserNotFoundError extends S.TaggedError<UserNotFoundError>()(
"UserNotFoundError",
{ id: UserIdSchema, message: S.String },
HttpApiSchema.annotations({ status: 404 })
) {}effect-worker-mono/
├── apps/
│ ├── effect-worker-api/ # HTTP REST API
│ │ ├── src/
│ │ │ ├── index.ts # Worker entry point
│ │ │ ├── runtime.ts # Effect runtime
│ │ │ ├── handlers/ # Handler implementations
│ │ │ └── services/ # Middleware implementations
│ │ └── wrangler.jsonc # Cloudflare config
│ ├── effect-worker-rpc/ # RPC API
│ └── svelte-client/ # Full-stack Svelte app
├── packages/
│ ├── domain/ # Domain types & schemas
│ │ └── src/
│ │ ├── schemas/ # Branded types
│ │ └── errors/ # Domain errors
│ ├── contracts/ # API definitions
│ │ └── src/
│ │ ├── http/ # HTTP endpoints
│ │ └── rpc/ # RPC procedures
│ ├── cloudflare/ # Worker infrastructure
│ │ └── src/
│ │ ├── fiber-ref.ts # FiberRef bridge
│ │ ├── services.ts # Service tags
│ │ └── database.ts # Connection factory
│ └── db/ # Database schema
│ └── src/schema.ts # Drizzle tables
└── reports/ # Architecture decisions
Strict mode enabled with path aliases for all packages:
{
"compilerOptions": {
"paths": {
"@repo/domain": ["./packages/domain/src"],
"@repo/contracts": ["./packages/contracts/src"],
"@repo/cloudflare": ["./packages/cloudflare/src"],
"@repo/db": ["./packages/db/src"]
}
}
}Configure in wrangler.jsonc:
Cloudflare D1 provides connection to a Sqlite db. to start configure:
{
"d1_databases": [{ "binding": "db", "database_id": "xxx", "remote": true }]
}Usaging :
return yield * makeDrizzle(env.db)| Command | Description |
|---|---|
pnpm build |
Build all packages |
pnpm check |
Type check all packages |
pnpm test |
Run all tests |
pnpm coverage |
Generate coverage report |
pnpm clean |
Remove dist folders |
| Category | Technology |
|---|---|
| Runtime | Cloudflare Workers |
| Framework | Effect-TS |
| HTTP | @effect/platform |
| RPC | @effect/rpc |
| Full-Stack UI | TanStack Start + TanStack Router + TanStack Query |
| Database | Drizzle ORM + SqliteSql |
| Build | pnpm workspaces + TypeScript |
| Testing | Vitest + @effect/vitest |
| Deployment | Wrangler |
- effect - Core functional effects runtime
- @effect/platform - HTTP server & middleware
- @effect/rpc - RPC protocol
- @effect/sql-drizzle - Database integration
- drizzle-orm - Type-safe ORM
- wrangler - Cloudflare Workers CLI
- Make changes to packages or apps
- Build packages if contract/domain/infra changed:
pnpm build - Type check:
pnpm check - Run tests:
pnpm test - Dev server:
cd apps/effect-worker-api && pnpm dev - Deploy:
pnpm deploy
cd packages/db
# Push schema changes
pnpm db:push
# Open Drizzle Studio
pnpm db:studio
# Generate migrations
pnpm db:generate
# Run migrations
pnpm db:migrateSee LICENSE for details.
{ "kv_namespaces": [{ "binding": "MY_KV", "id": "xxx" }], "r2_buckets": [{ "binding": "MY_R2", "bucket_name": "xxx" }], "d1_databases": [{ "binding": "db", "database_id": "xxx" }] }