Skip to content
 
 

Repository files navigation

Effect Worker Monorepo

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

Summary of Changes

  1. 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.

  2. 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.

  3. Unified API Access from the Svelte Client The SvelteKit client can easily interact with both effect-api and effect-rpc, offering a consistent and ergonomic way to call backend logic from the frontend.

  4. 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.

  5. 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.

Get Started

Prerequisites

  • Node.js (recommended LTS)
  • pnpm
  • Cloudflare account with D1 enabled

Configure the D1 Database

  1. Open the following files and connect your D1 database:

    • apps/effect-worker-api/wrangler.jsonc
    • apps/effect-worker-rpc/wrangler.jsonc
    • apps/svelte-client/wrangler.jsonc

    Make sure the D1 bindings and database IDs are correctly set in both files.

  2. Set up environment variables:

    • Go to packages/db
    • Rename .env.example to .env
    • Fill in all required environment variables
  3. Install dependencies (if you haven’t already):

    pnpm install
  4. Generate and push the database schema:

    pnpm db:generate
    pnpm db:push

Install, Build, and Verify

From the repository root:

pnpm install          # Install dependencies
pnpm build            # Build all packages
pnpm check            # Type-check the codebase
pnpm test             # Run tests

Local Development

Run a single app

cd apps/effect-worker-api
pnpm dev              # Start the dev server

Run all apps from the root

pnpm dev:apps

Architecture Overview

┌─────────────────────────────────────────────────────────────────┐
│                        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)   │   │
│    └───────────┘ └───────────┘ └───────────┘ └──────────────┘   │
└─────────────────────────────────────────────────────────────────┘

Packages

All packages use the @repo/* namespace for internal monorepo imports.

@repo/domain

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 UserId

@repo/contracts

API 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 endpoints
  • UsersGroup - User CRUD operations

RPC Procedures:

  • UsersRpc - User operations via RPC

@repo/cloudflare

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"

@repo/db

Drizzle ORM schema definitions.

import { users } from "@repo/db"

Applications

effect-worker-api

REST HTTP API built with @effect/platform.

cd apps/effect-worker-api
pnpm dev        # Local dev server
pnpm deploy     # Deploy to Cloudflare

Endpoints:

  • GET /health - Health check
  • GET /users - List users
  • GET /users/:id - Get user by ID
  • POST /users - Create user

effect-worker-rpc

RPC API using @effect/rpc for procedure-based communication.

cd apps/effect-worker-rpc
pnpm dev        # Local dev server
pnpm deploy     # Deploy to Cloudflare

Endpoints:

  • POST /rpc - RPC endpoint

svelte-client

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 Cloudflare

Features:

  • 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()
    }
  })
)

Core Patterns

FiberRef Bridge

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.
})

Middleware Pattern

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
  })
)

Handler Implementation

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
        })
      )
)

Error Handling

Typed errors with automatic HTTP status mapping:

export class UserNotFoundError extends S.TaggedError<UserNotFoundError>()(
  "UserNotFoundError",
  { id: UserIdSchema, message: S.String },
  HttpApiSchema.annotations({ status: 404 })
) {}

Project Structure

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

Configuration

TypeScript

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"]
    }
  }
}

Cloudflare Bindings

Configure in wrangler.jsonc:

{
  "kv_namespaces": [{ "binding": "MY_KV", "id": "xxx" }],
  "r2_buckets": [{ "binding": "MY_R2", "bucket_name": "xxx" }],
  "d1_databases": [{ "binding": "db", "database_id": "xxx" }]
}

D1

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)

Scripts

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

Tech Stack

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

Key Dependencies

  • 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

Development Workflow

  1. Make changes to packages or apps
  2. Build packages if contract/domain/infra changed: pnpm build
  3. Type check: pnpm check
  4. Run tests: pnpm test
  5. Dev server: cd apps/effect-worker-api && pnpm dev
  6. Deploy: pnpm deploy

Database Operations

cd packages/db

# Push schema changes
pnpm db:push

# Open Drizzle Studio
pnpm db:studio

# Generate migrations
pnpm db:generate

# Run migrations
pnpm db:migrate

License

See LICENSE for details.

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages