Skip to content

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.

Put this in src/index.ts. Each piece is explained below.

src/index.ts
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 99
  1. The Effect type carried everything. greet had the type Effect<void, UserNotFound, Users>. The compiler knew it could fail with UserNotFound and required Users — and it would not let us run the program until both were addressed.

  2. Errors were values, not exceptions. UserNotFound was returned with return yield* new UserNotFound(...), flowed through the type system in the E channel, and was recovered with Effect.catchTag. There was no try/catch and nothing thrown.

  3. The dependency was injected via a Layer. greet never imported a concrete Users. We supplied one with Effect.provide(Users.layer). To test greet, you would provide a different Layer with an in-memory or mock implementation — no change to the business logic.

  4. Nothing ran until the edge. Building program did no work; it was a description. Effect.runPromise executed it. This laziness is what lets effects compose so freely.

  • 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.