Skip to main content

Define a service and error

users.ts
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

router.ts
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.
main.ts
import { router } from "./router";

const getUser = router.users.get.callable();

console.log(await getUser({ id: "1" }));
// { id: "1", name: "Ada Lovelace" }

What happened

1

`eos` wrapped an oRPC builder

eos exposes familiar oRPC builder methods and adds Effect-aware methods such as .provide(...), .effect(...), and .traced(...).
2

The service layer satisfied Effect requirements

UsersRepo.Default was provided before the handler, so yield*\ UsersRepo.findById(...) is available at compile-time.
3

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