Define a service and error
import { Context, Effect, Layer } from "effect";
import { ORPCTaggedError } from "effect-orpc";
import * as z from "zod";
export const User = z.object({
id: z.string(),
name: z.string(),
});
const users: Array<z.infer<typeof User>> = [
{ id: "1", name: "Ada Lovelace" },
{ id: "2", name: "Grace Hopper" },
];
export class UsersRepo extends Context.Service<
UsersRepo,
{
readonly findById: (
id: string,
) => Effect.Effect<z.infer<typeof User> | undefined>;
}
>()("UsersRepo") {}
export const UsersRepoLive = Layer.succeed(UsersRepo, {
findById: (id) => Effect.succeed(users.find((user) => user.id === id)),
});
export class UserNotFoundError extends ORPCTaggedError("UserNotFoundError", {
status: 404,
message: "User not found",
schema: z.object({ id: z.string() }),
}) {}
Create the router
import { os } from "@orpc/server";
import { eos } from "effect-orpc";
import * as z from "zod";
import { User, UserNotFoundError, UsersRepo, UsersRepoLive } from "./users";
const effectProcedure = eos
.provide(UsersRepoLive)
.errors({ UserNotFoundError });
export const router = {
health: os.handler(() => "ok"),
users: {
get: effectProcedure
.input(z.object({ id: z.string() }))
.output(User)
.effect(function* ({ input }) {
const usersRepo = yield* UsersRepo;
const user = yield* usersRepo.findById(input.id);
if (!user) {
return yield* new UserNotFoundError({
data: { id: input.id },
});
}
return user;
}),
},
};
export type Router = typeof router;
Call it!
Use .callable() when you want to invoke a procedure directly in tests, scripts, or local examples.
import { router } from "./router";
const getUser = router.users.get.callable();
console.log(await getUser({ id: "1" }));
// { id: "1", name: "Ada Lovelace" }
What happened
`eos` wrapped an oRPC builder
eos exposes familiar oRPC builder methods and adds Effect-aware methods
such as .provide(...), .effect(...), and .traced(...).
The service layer satisfied Effect requirements
UsersRepoLive was provided before the handler, so yield* UsersRepo is
available at compile-time.
The tagged error became an oRPC error
UserNotFoundError is yieldable in Effect code and serializable as an oRPC
error.
Next steps
Mental model
Learn how builders, layers, runtimes, and request boundaries fit together.
Effect procedures
Focus on .effect(...) handlers and their typed handler options.
Last modified on June 15, 2026