Documentation Index
Fetch the complete documentation index at: https://effect-ts-effect-smol.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
The Layer module provides a powerful dependency injection system for building modular, testable Effect applications. Layers describe how to construct services and their dependencies.
Overview
A Layer<ROut, E, RIn> describes how to build services:
ROut: The services this layer provides
E: Possible errors during layer construction
RIn: The services this layer requires as dependencies
Layers are the idiomatic way to create services that depend on other services in Effect. By default, layers are shared, meaning the same layer instance is reused across the application.
Creating Layers
Basic Layer from a Service
import { Effect, Layer, ServiceMap } from "effect"
class Database extends ServiceMap.Service<Database, {
query(sql: string): Effect.Effect<Array<unknown>, Error>
}>()("Database") {
static readonly layer = Layer.effect(
Database,
Effect.gen(function*() {
yield* Effect.log("Initializing database")
const query = (sql: string) =>
Effect.gen(function*() {
yield* Effect.log("Executing:", sql)
return [{ id: 1, name: "Alice" }]
})
return Database.of({ query })
})
)
}
Layer with Dependencies
import { Effect, Layer, ServiceMap } from "effect"
// Config service
class Config extends ServiceMap.Service<Config, {
connectionString: string
}>()("Config") {
static readonly layer = Layer.succeed(Config)({
connectionString: "postgresql://localhost/mydb"
})
}
// Database depends on Config
class Database extends ServiceMap.Service<Database, {
query(sql: string): Effect.Effect<Array<unknown>, Error>
}>()("Database") {
static readonly layer = Layer.effect(
Database,
Effect.gen(function*() {
const config = yield* Config
yield* Effect.log("Connecting to:", config.connectionString)
return Database.of({
query: (sql) => Effect.succeed([{ result: "data" }])
})
})
)
}
Layer Constructors
Layer.succeed
Create a layer that succeeds with a value:
import { Layer, ServiceMap } from "effect"
class Logger extends ServiceMap.Service<Logger, {
log(message: string): void
}>()("Logger") {
static readonly layer = Layer.succeed(Logger)({
log: (message) => console.log(message)
})
}
Layer.effect
Create a layer from an Effect:
import { Effect, Layer, ServiceMap } from "effect"
class Cache extends ServiceMap.Service<Cache, {
get(key: string): Effect.Effect<string | undefined>
}>()("Cache") {
static readonly layer = Layer.effect(
Cache,
Effect.gen(function*() {
const storage = new Map<string, string>()
return Cache.of({
get: (key) => Effect.succeed(storage.get(key))
})
})
)
}
Layer.effectDiscard
Create a layer that runs side effects without providing a service:
import { Effect, Layer } from "effect"
// Background task layer
const heartbeatLayer = Layer.effectDiscard(
Effect.gen(function*() {
yield* Effect.log("Starting heartbeat")
yield* Effect.repeat(
Effect.log("Heartbeat tick"),
Schedule.spaced("30 seconds")
).pipe(Effect.fork)
})
)
Composing Layers
Layer.provide
Provide dependencies to a layer:
import { Layer } from "effect"
// Database layer depends on Config
const DatabaseLayer = Database.layer.pipe(
Layer.provide(Config.layer)
)
// Now DatabaseLayer has no dependencies
Layer.provideMerge
Merge provided layers with the layer’s output:
import { Layer } from "effect"
// Provide Config but also expose it
const AppLayer = Database.layer.pipe(
Layer.provideMerge(Config.layer)
)
// AppLayer provides both Database and Config
Combining Multiple Layers
import { Layer } from "effect"
// Merge multiple independent layers
const AppLayer = Layer.merge(
Database.layer,
Cache.layer,
Logger.layer
)
// All three services are now available
Using Layers
Providing Layers to Effects
import { Effect } from "effect"
const program = Effect.gen(function*() {
const db = yield* Database
const cache = yield* Cache
const result = yield* db.query("SELECT * FROM users")
return result
})
// Provide the layer to the program
const runnable = program.pipe(
Effect.provide(AppLayer)
)
Layer.launch
Launch a layer as the application entry point:
import { Effect, Layer, NodeRuntime } from "effect"
const MainLayer = Layer.effectDiscard(
Effect.gen(function*() {
const db = yield* Database
yield* Effect.log("Application started")
// Long-running server or application logic
yield* Effect.never // Keep running forever
})
).pipe(
Layer.provide(Database.layer)
)
// Launch the layer
NodeRuntime.runMain(Layer.launch(MainLayer))
Resource Management
Layer with Cleanup
import { Effect, Layer, Scope, ServiceMap } from "effect"
class Connection extends ServiceMap.Service<Connection, {
execute(cmd: string): Effect.Effect<void>
}>()("Connection") {
static readonly layer = Layer.effect(
Connection,
Effect.gen(function*() {
// Acquire resource
yield* Effect.log("Opening connection")
// Register cleanup
yield* Scope.addFinalizer(
Effect.log("Closing connection")
)
return Connection.of({
execute: (cmd) => Effect.log(`Executing: ${cmd}`)
})
})
)
}
Layer Memoization
Layers are memoized by default:
import { Effect, Layer } from "effect"
// This layer is only constructed once
const program = Effect.gen(function*() {
const db1 = yield* Database
const db2 = yield* Database
// db1 and db2 are the same instance
})
To disable memoization:
import { Layer } from "effect"
const freshLayer = Layer.fresh(Database.layer)
// Each use creates a new instance
Testing with Layers
import { Effect, Layer, ServiceMap } from "effect"
// Mock layer for testing
class MockDatabase extends ServiceMap.Service<Database, {
query(sql: string): Effect.Effect<Array<unknown>, Error>
}>()("Database") {
static readonly layer = Layer.succeed(Database)({
query: (sql) => Effect.succeed([{ mock: "data" }])
})
}
// Use mock in tests
const testProgram = program.pipe(
Effect.provide(MockDatabase.layer)
)
Advanced Patterns
Dynamic Layer Construction
import { Effect, Layer } from "effect"
const makeDatabaseLayer = (connectionString: string) =>
Layer.effect(
Database,
Effect.gen(function*() {
yield* Effect.log("Connecting to:", connectionString)
return Database.of({
query: (sql) => Effect.succeed([])
})
})
)
// Use with Layer.unwrap
const DatabaseLayer = Layer.unwrap(
Effect.gen(function*() {
const config = yield* Config
return makeDatabaseLayer(config.connectionString)
})
)
Conditional Layers
import { Effect, Layer } from "effect"
const DatabaseLayer = Layer.unwrap(
Effect.gen(function*() {
const env = yield* getEnvironment()
if (env === "test") {
return MockDatabase.layer
} else {
return Database.layer
}
})
)
Best Practices
- Define layers as static members: Attach layers to service classes
- Use Layer.provide for dependencies: Make dependencies explicit
- Keep layers focused: Each layer should provide a single service or small group of related services
- Use Layer.merge for independent services: Combine services that don’t depend on each other
- Test with mock layers: Replace real implementations with mocks for testing
Next Steps
- Learn about Effect for building effectful computations
- Explore Config for configuration management
- Understand how to create services with ServiceMap