t3-code-android-nightly/.repos/alchemy-effect/examples/cloudflare-tanstack-rpc-drizzle
Julius Marminge 6f9cea00ae
chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 13:22:30 -07:00
..
migrations chore(refs): sync Alchemy reference to beta.76 (#10654) 2026-09-07 21:54:45 -07:00
src chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170) 2026-10-05 13:22:30 -07:00
test chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170) 2026-10-05 13:22:30 -07:00
.gitignore Upgrade Effect and Alchemy betas (#4643) 2026-07-27 16:21:02 +02:00
alchemy.run.ts chore(refs): sync Effect and Alchemy references to rc.115 and beta.78 (#12327) 2026-09-17 23:21:25 -07:00
package.json chore(refs): sync Effect and Alchemy references to rc.115 and beta.78 (#12327) 2026-09-17 23:21:25 -07:00
README.md chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170) 2026-10-05 13:22:30 -07:00
tsconfig.json Upgrade Effect and Alchemy betas (#4643) 2026-07-27 16:21:02 +02:00
vite.config.ts Upgrade Effect and Alchemy betas (#4643) 2026-07-27 16:21:02 +02:00

cloudflare-tanstack-rpc-drizzle

A full-stack example that wires together four pieces:

  • TanStack Start (React) frontend, deployed as a Cloudflare Worker + assets via Cloudflare.Website.Vite.
  • Effect RPC backend, deployed as a separate Cloudflare.Workers.RpcWorker.
  • Drizzle + Neon Postgres, reached through a Cloudflare.Hyperdrive.Connection pool, with migrations generated by Drizzle.Schema.
  • Atom RPC — Effect 4's native effect/reactivity/AtomRpc plus the React bindings from @effect/atom-react — for reactive queries and mutations in the browser.

Architecture

Browser (React)
  │  useAtomValue / useAtomSet
  ▼
AtomRpc client (TodoRpcs)        src/rpc-client.ts
  │  HTTP POST /rpc (JSON)
  ▼
TanStack Start worker            src/routes/rpc.ts  (server route)
  │  env.BACKEND.fetch(...)      (private service binding)
  ▼
Backend RpcWorker                src/backend/api.ts
  │  Drizzle.Postgres over Hyperdrive
  ▼
Neon Postgres branch

The browser cannot use a Cloudflare service binding directly, so the AtomRpc client points at a same-origin /rpc route, and the TanStack Start server forwards that request to the private BACKEND worker over the service binding. This keeps the backend off the public internet and avoids CORS.

Atom RPC

Effect 4 ships atom RPC in core — no third-party @effect-atom package is needed (that one targets Effect 3). The only extra dependency is @effect/atom-react, which provides the React hooks (useAtomValue, useAtomSet, RegistryProvider) and is versioned in lockstep with effect.

// src/rpc-client.ts
export class TodoClient extends AtomRpc.Service<TodoClient>()("TodoClient", {
  group: TodoRpcs,
  protocol: RpcClient.layerProtocolHttp({ url: "/rpc" }).pipe(
    Layer.provide(FetchHttpClient.layer),
    Layer.provide(RpcSerialization.layerJson),
  ),
}) {}

export const listTodosAtom = TodoClient.query("listTodos", undefined, {
  reactivityKeys: ["todos"],
});
export const createTodoAtom = TodoClient.mutation("createTodo");
// src/routes/index.tsx
const todos = AsyncResult.getOrElse(useAtomValue(listTodosAtom), () => []);
const createTodo = useAtomSet(createTodoAtom);
// ...
createTodo({ payload: { text }, reactivityKeys: ["todos"] });

reactivityKeys: ["todos"] ties the list query to the mutations: when a mutation runs with the same key, the list query is invalidated and refetched automatically.

The same TodoRpcs group is the single source of truth — it is served by the backend and consumed by the browser client, so one Schema codec round-trips every value over the wire.

Running

Requires Cloudflare and Neon credentials (same as the other Neon examples):

  • CLOUDFLARE_API_TOKEN (or alchemy profile edit --add Cloudflare)
  • NEON_API_KEY
bun install

# Deploy the backend RpcWorker, Neon branch, Hyperdrive, and the TanStack site.
bun alchemy deploy

# Tear everything down.
bun alchemy destroy

Drizzle.Schema generates migration SQL into ./migrations on deploy, and Neon.Branch applies any pending migrations transactionally before the workers go live.

Tests

bun test                 # deploys, exercises /rpc end to end, then destroys
NO_DESTROY=1 bun test    # keep the deployment around between runs