Skip to content

Managing Layers

A layer is the recipe that builds a service. Layer<Out, Error, In> reads like an effect: it produces the services Out, may fail with Error while constructing them, and requires the services In to do so. Choosing the right constructor depends on how the implementation comes to exist — a plain value, an effectful setup, or a resource that must be released.

import { Context, Layer } from "effect"
class Greeter extends Context.Service<Greeter, {
readonly greeting: string
}>()("myapp/Greeter") {}
// `Layer.succeed` — the implementation is a value you already have.
const GreeterLive = Layer.succeed(Greeter, { greeting: "Hello" })

Use it when the service is just a plain object, with no setup work:

import { Context, Effect, Layer } from "effect"
class Config extends Context.Service<Config, {
readonly apiUrl: string
}>()("myapp/Config") {}
const ConfigLive = Layer.succeed(Config, { apiUrl: "https://api.example.com" })

Use it when building the service requires running an effect: reading config, allocating state, or depending on another service. The effect runs once, when the layer is built, and its result is memoized:

import { Context, Effect, Layer, Ref } from "effect"
class Counter extends Context.Service<Counter, {
readonly increment: Effect.Effect<number>
}>()("myapp/Counter") {
static readonly layer = Layer.effect(
Counter,
Effect.gen(function*() {
// Setup work runs once at build time — here, allocating shared state.
const ref = yield* Ref.make(0)
const increment = Ref.updateAndGet(ref, (n) => n + 1)
return Counter.of({ increment })
})
)
}

Scoped layers — resources that must be released

Section titled “Scoped layers — resources that must be released”

In Effect v4 there’s no separate Layer.scoped: Layer.effect already accepts an effect that requires a Scope. Acquire the resource with Effect.acquireRelease and the layer ties its cleanup to the layer’s own lifetime — when the layer is torn down, the resource is released.

import { Context, Effect, Layer } from "effect"
class Pool extends Context.Service<Pool, {
readonly run: <A>(work: () => A) => Effect.Effect<A>
}>()("myapp/Pool") {
static readonly layer = Layer.effect(
Pool,
Effect.gen(function*() {
// acquireRelease pairs setup with a guaranteed release. The `Scope` it
// introduces is absorbed by `Layer.effect`, so it never leaks into the
// layer's requirements.
const connections = yield* Effect.acquireRelease(
Effect.sync(() => {
// open the pool
return { close: () => {} }
}),
(pool) => Effect.sync(() => pool.close())
)
const run = <A>(work: () => A) => Effect.sync(() => work())
return Pool.of({ run })
})
)
}

A layer is inert until you attach it to a program. Effect.provide discharges the matching requirements and returns an effect that no longer needs them:

import { Effect } from "effect"
import { Counter } from "./Counter.ts"
const program = Effect.gen(function*() {
const counter = yield* Counter
yield* counter.increment
return yield* counter.increment // 2
})
// After providing, the `Counter` requirement is gone and the effect can run.
const runnable: Effect.Effect<number> = program.pipe(
Effect.provide(Counter.layer)
)
Effect.runFork(runnable)

For programs whose whole point is the layer’s lifecycle — a server that should stay up until interrupted — Layer.launch builds the layer and keeps it alive:

import { Effect, Layer } from "effect"
import { Pool } from "./Pool.ts"
// Builds Pool.layer, holds it open forever, and releases on interruption.
Effect.runFork(Layer.launch(Pool.layer))

| Constructor | When | | --- | --- | | Layer.succeed | The service is a plain value, no setup needed | | Layer.sync | Same, but build it lazily at layer-build time | | Layer.effect | Construction is effectful or depends on other services | | Layer.effect + Effect.acquireRelease | The service owns a resource to release | | Layer.mergeAll | Combine several independent layers into one |

Next, see how layers depend on one another in Layer composition.