RPC
The effect/unstable/rpc modules let you describe a set of remote procedures
once, as schema-backed definitions, and then derive both a typed client and
a typed server from that single description. The client method signatures, the
server handler signatures, the wire encoding, and the error channel are all
generated from the same RpcGroup — so the two ends can never drift out of
sync, and a payload or success type change is a compile error rather than a
runtime surprise.
import { Schema } from "effect"import { Rpc, RpcGroup } from "effect/unstable/rpc"
// A single shared contract. The client and server both import this group,// guaranteeing they agree on tags, payloads, results, and errors.class UserNotFound extends Schema.TaggedErrorClass<UserNotFound>()( "UserNotFound", { id: Schema.String }) {}
export class UserRpcs extends RpcGroup.make( Rpc.make("GetUser", { payload: { id: Schema.String }, // what the client sends success: Schema.Struct({ id: Schema.String, name: Schema.String }), error: UserNotFound // typed, schema-encoded failure })) {}An RPC definition is just data. Nothing is sent or handled until the group is interpreted: a client reads the schemas to encode requests and decode responses, and a server reads the same schemas to decode requests, run your handlers, and encode results. The transport (HTTP, websocket, socket, worker, or an in-memory test harness) is chosen separately and never affects the typed surface.
When to use RPC
Section titled “When to use RPC”RPC is the right tool when you control both ends and want a function-call feel across a process boundary — a backend service consumed by your own frontend, a worker thread, or a child process. It gives you typed payloads, typed errors, streaming results, and middleware without writing any serialization or routing code by hand.
If instead you need a public, REST-shaped HTTP surface with explicit paths,
methods, and status codes — for third-party consumers or OpenAPI — reach for the
HTTP API modules. RPC and HTTP API share the same Schema and
Effect foundations, so the mental model carries over.
The four pieces
Section titled “The four pieces”Everything in this section imports from effect/unstable/rpc. A working RPC
setup is built from four roles:
- The contract —
RpcandRpcGroup.Rpc.makedeclares one procedure (tag, payload, success, error, optional streaming);RpcGroup.makecollects procedures into a single typed group that both ends import. This is pure data. - The implementation —
RpcServerplus handlers. You attach a handler to every procedure in the group (group.toLayer/group.toHandlers), thenRpcServerdecodes incoming requests, runs the matching handler, and encodes the results back over the transport. - The caller —
RpcClient.RpcClient.maketurns the same group into an object of callable methods; each method encodes its payload, sends a request, and decodes the response into anEffect(or aStreamfor streaming RPCs). - The wire —
RpcSerializationplus a transportProtocol. Serialization (RpcSerialization.layerJson,layerNdjson,layerMsgPack) chooses how values are encoded as bytes; the protocol layer (HTTP, websocket, socket, worker, orRpcTestin memory) chooses how those bytes move between client and server. Neither affects the typed surface from the contract.
These four roles also underpin higher-level runtimes: Cluster
entities, for example, are defined as RpcGroups and reuse the same
handler/client machinery.
In this section
Section titled “In this section”- Defining RPCs — declare a procedure with
Rpc.make, attach payload, success, and error schemas, model streaming results withRpcSchema.Stream, and compose procedures into anRpcGroup(add,merge,prefix,annotateRpcs). - Client and server — implement handlers with
group.toLayer, run them withRpcServer, build a typed client withRpcClient, and test the whole thing in-memory withRpcTest. - Middleware — cross-cutting concerns with
RpcMiddleware: authentication, logging, context propagation, and adding middleware error types to both the client and server signatures. - Transports and serialization — wire
the contract to a real channel with
RpcSerialization(JSON, NDJSON, MsgPack) and the protocol layers for HTTP, websocket, socket, stdio, and worker. - Advanced RPC — streaming back pressure,
asQueueclients, defect handling, request headers, primary-key payloads, and themakeNoSerializationescape hatch for already-decoded channels.
Related sections
Section titled “Related sections”RPC builds directly on concepts covered elsewhere: Schema for the wire contract, Error Management for typed failures, Services & Layers for wiring, Streaming for streaming RPCs, and the Platform / HTTP Client modules for transports. Cluster builds distributed entities on top of RPC groups.