Configuration
Effect.Config + Layer — validated at startup, accessed via context
The Pattern
Configuration is a Layer. Define it with Effect.Config, expose it via Context.Tag, and yield from context in handlers. No global config object.
Defining Config
import { Context, Config, Effect, Layer } from "effect"
// 1) Define the config shape
const AppConfigSchema = Config.all({
DATABASE_URL: Config.string("DATABASE_URL"),
REDIS_URL: Config.string("REDIS_URL"),
PORT: Config.integer("PORT").pipe(Config.withDefault(3000)),
NODE_ENV: Config.literal("development", "production", "test")("NODE_ENV").pipe(
Config.withDefault("development")
)
})
// 2) Infer the type
type AppConfig = Config.Config.Success<typeof AppConfigSchema>
// 3) Create the Context.Tag
class Config extends Context.Tag("Config")<Config, AppConfig>() {}
// 4) Create the Layer — reads from environment
const ConfigLive = Layer.effect(
Config,
Effect.config(AppConfigSchema)
)Using in Layers
// Other layers can depend on Config
const PgPoolLive = Layer.scoped(
PgPool,
Effect.gen(function* () {
const cfg = yield* Config
const pool = new Pool({ connectionString: cfg.DATABASE_URL })
yield* Effect.addFinalizer(() =>
Effect.tryPromise(() => pool.end()).pipe(Effect.orDie)
)
return pool
})
).pipe(Layer.provide(ConfigLive))Using in Handlers
HttpRouter.get("/info", Effect.gen(function* () {
const cfg = yield* Config
return HttpServerResponse.json({
environment: cfg.NODE_ENV,
port: cfg.PORT
})
}))Nested Config
// Group related config with Config.nested
const DatabaseConfig = Config.all({
url: Config.string("URL"),
poolMin: Config.integer("POOL_MIN").pipe(Config.withDefault(2)),
poolMax: Config.integer("POOL_MAX").pipe(Config.withDefault(10)),
ssl: Config.boolean("SSL").pipe(Config.withDefault(false))
}).pipe(Config.nested("DATABASE"))
// Reads: DATABASE_URL, DATABASE_POOL_MIN, DATABASE_POOL_MAX, DATABASE_SSLSecret Values
// Config.secret wraps the value — won't leak in logs
const SecureConfig = Config.all({
apiKey: Config.secret("API_KEY"),
dbPassword: Config.secret("DB_PASSWORD")
})
// Access the underlying value
const cfg = yield* Effect.config(SecureConfig)
const key = Secret.value(cfg.apiKey) // stringValidation at Startup
Config is validated when the Layer is built. Missing or invalid values fail fast with clear error messages:
ConfigError: Missing data at NODE_ENV: Expected one of
("development" | "production" | "test") but received "staging"Testing with Config
// Override config in tests
const ConfigTest = Layer.succeed(Config, {
DATABASE_URL: "postgres://localhost/test",
REDIS_URL: "redis://localhost",
PORT: 3001,
NODE_ENV: "test" as const
})
await Effect.runPromise(
myEffect.pipe(Effect.provide(ConfigTest))
)