Quickstart
In a few minutes you will build a small but realistic Effect program: a service that looks up users, with a typed error for the missing case, a Layer that provides the implementation, error recovery in the calling code, and a runtime at the edge to run it. This is the shape of nearly every Effect program, scaled down — once you can read this, you can read real Effect codebases.
If you haven’t set up a project yet, follow the Installation guide first.
The full program
Section titled “The full program”Put this in src/index.ts. Each piece is explained below.
import { Console, Context, Effect, Layer, Schema } from "effect"
// 1. Domain models, defined with Schema so they are typed and serializable.class User extends Schema.Class<User>("User")({ id: Schema.Number, name: Schema.String}) {}
// 2. A typed error. `TaggedErrorClass` gives it a `_tag` ("UserNotFound") that// we can match on later, and structured fields instead of a string message.class UserNotFound extends Schema.TaggedErrorClass<UserNotFound>()( "UserNotFound", { id: Schema.Number }) {}
// 3. A service: an interface of effects, declared as a class. The string key// "app/Users" uniquely identifies it in the context.class Users extends Context.Service<Users, { readonly findById: (id: number) => Effect.Effect<User, UserNotFound>}>()("app/Users") { // 4. The live implementation, wrapped in a Layer. `Layer.sync` builds the // service synchronously; use `Layer.effect` when construction is effectful. static layer = Layer.sync(Users, () => { const store = new Map<number, User>([ [1, new User({ id: 1, name: "Ada" })], [2, new User({ id: 2, name: "Lin" })] ])
return Users.of({ // `Effect.fn` is how you write a function returning an Effect: it names // the function for stack traces and tracing spans. findById: Effect.fn("Users.findById")(function* (id: number) { const user = store.get(id) if (user === undefined) { // Always `return yield*` a failure so TypeScript knows control stops. return yield* new UserNotFound({ id }) } return user }) }) })}
// 5. Business logic. It asks for the `Users` service by `yield*`-ing it, so the// requirement shows up in the type — it never imports the implementation.//// ┌─── succeeds with void// │ ┌─── can fail with UserNotFound// │ │ ┌─── needs the Users service// ▼ ▼ ▼// Effect<void, UserNotFound, Users>const greet = Effect.fn("greet")(function* (id: number) { const users = yield* Users const user = yield* users.findById(id) yield* Console.log(`Hello, ${user.name}!`)})
// 6. Compose the program. We greet a known user and an unknown one, recovering// from the failure with `Effect.catchTag` so the program completes cleanly.const program = Effect.gen(function* () { yield* greet(1)
yield* greet(99).pipe( // Match the specific error by its tag. Inside the handler the error is // narrowed to `UserNotFound`, so `error.id` is fully typed. Effect.catchTag("UserNotFound", (error) => Console.log(`No user with id ${error.id}`) ) )})
// 7. Provide the dependency, discharging the `Users` requirement (R -> never),// then run it. `Effect.runPromise` is the boundary to the outside world.program.pipe(Effect.provide(Users.layer), Effect.runPromise)Running it (bun run src/index.ts or npx tsx src/index.ts) prints:
Hello, Ada!No user with id 99What just happened
Section titled “What just happened”-
The
Effecttype carried everything.greethad the typeEffect<void, UserNotFound, Users>. The compiler knew it could fail withUserNotFoundand requiredUsers— and it would not let us run the program until both were addressed. -
Errors were values, not exceptions.
UserNotFoundwas returned withreturn yield* new UserNotFound(...), flowed through the type system in theEchannel, and was recovered withEffect.catchTag. There was notry/catchand nothing thrown. -
The dependency was injected via a Layer.
greetnever imported a concreteUsers. We supplied one withEffect.provide(Users.layer). To testgreet, you would provide a different Layer with an in-memory or mock implementation — no change to the business logic. -
Nothing ran until the edge. Building
programdid no work; it was a description.Effect.runPromiseexecuted it. This laziness is what lets effects compose so freely.
Where to go from here
Section titled “Where to go from here”- Effect Essentials — creating, transforming, piping, and running effects in depth.
- Error Management — modeling failures and recovering with
Effect.catch,catchTag, and friends. - Services & Layers — building real dependency graphs.
- Schema — the validation and serialization toolkit used above.