# 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](https://effect.plants.sh/introduction/installation/) guide first.

## The full program

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

```ts title="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:

```text
Hello, Ada!
No user with id 99
```

## What just happened

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.
**Tip:** For long-running applications (servers, workers, CLIs), prefer a platform
`runMain` over `runPromise`. On Node, that is `runMain` from
`@effect/platform-node`'s `NodeRuntime` — it installs signal handlers, runs
finalizers on shutdown, and reports errors. See [Runtime](https://effect.plants.sh/runtime/).

## Where to go from here

- [Effect Essentials](https://effect.plants.sh/essentials/) — creating, transforming, piping, and
  running effects in depth.
- [Error Management](https://effect.plants.sh/error-management/) — modeling failures and recovering with
  `Effect.catch`, `catchTag`, and friends.
- [Services & Layers](https://effect.plants.sh/services-and-layers/) — building real dependency graphs.
- [Schema](https://effect.plants.sh/schema/) — the validation and serialization toolkit used above.