Skip to content

APIs

Every Function and Server in alchemy returns { fetch, ...rpcs } from its Effectful Constructor — you already saw the “return what you expose” half. The rpcs are the other half: plain functions returning Effect or Stream, no schema, no decorators:

import * as Cloudflare from "alchemy/Cloudflare";
import * as Effect from "effect/Effect";
import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
export default class BindingTargetWorker extends Cloudflare.Worker<BindingTargetWorker>()(
"BindingTargetWorker",
{
main: import.meta.url,
},
Effect.gen(function* () {
return {
greet: (name: string) => Effect.succeed(`hello ${name}`),
fetch: Effect.gen(function* () {
return HttpServerResponse.text("hello from BindingTargetWorker");
}),
};
}),
) {}

Any other resource that binds this Worker gets a fully typed client: yield* target.greet("world") is an ordinary Effect call, the return type is inferred from the class, and serialization is handled automatically. The same model holds across every pairing — a Durable Object bound into a Worker, a Container called from a Durable Object, a MicroVM called from a Lambda Function.

That schemaless path is the default for internal communication. Schemas enter only when data crosses a trust boundary — a web app, an external service, anything you don’t deploy yourself.

ModalitySchemaPer-request costUse whenClients
Schemaless RPCNoneProxy dispatch + structured clone (native Bindings) or JSON args (fetch transport) + an envelope-tag check — zero validationInternal service-to-service: Worker → Worker, Worker → Durable Object, Durable Object → Container, Lambda → MicroVMAny resource that binds the class (MicroVMs connect explicitly via connectMicrovm)
Effect RPCRpcGroupWire-frame parse + Schema decode of the payload + handler + Schema encode of the result — mirrored on the clientData needs sanitizing or crosses a trust boundary (web app, external service) and consumers are Effect/TypeScriptThe schema + a reference to the Fetcher — a Binding or just a URL
Effect HTTPHttpApiSame profile as Effect RPCSame trust-boundary case, but consumers are non-Effect / non-TypeScriptPlain HTTP — real URLs, path params, query strings, headers, content types, curl

Default to Schemaless RPC for anything internal. There is no schema to write, no runtime checking, and no per-request validation cost — and internal callers already get end-to-end types for free, because the client type is the class you bound.

Effect RPC and Effect HTTP are for trust boundaries. They are discouraged for internal service-to-service calls: every request pays a Schema decode and encode on both sides, which buys you nothing when you control both ends. Reach for them when data needs sanitizing — a browser, a partner service, anything outside your deploy.

Between the two schema’d modalities the choice is about the external consumer: an Effect/TypeScript client combines your schema with a Fetcher and gets a typed RPC client — use Effect RPC; anyone else wants a real HTTP surface they can hit with a plain HTTP client — use Effect HTTP.

The three interoperate on one host: the schema’d modalities build an HttpEffect returned from fetch, so a single Worker or Function can serve schemaless methods internally and a schema’d surface externally.

  1. Schemaless RPC — the internal default: typed clients, Streams, and error propagation with no schema.
  2. Effect RPCRpcGroup schemas, handler Layers, and clients built from the schema + a Fetcher.
  3. Effect HTTP — the same idea as real REST endpoints for non-Effect consumers.

Then see it wired up in the hubs: