Define a service and error
import { Effect } 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 Effect.Service<UsersRepo>()("UsersRepo", {
accessors: true,
sync: () => ({
findById: (id: string) => 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 } from "./users";
const effectProcedure = eos
.provide(UsersRepo.Default)
.errors({ UserNotFoundError });
export const router = {
health: os.handler(() => "ok"),
users: {
get: effectProcedure
.input(z.object({ id: z.string() }))
.output(User)
.effect(function* ({ input }) {
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
UsersRepo.Default was provided before the handler, so yield*\ UsersRepo.findById(...) 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