Configuration is a supplied, validated dependency

A service is a named interface. A Layer describes how to build it and supply its dependencies. Schema defines accepted values and checks data at runtime.ΒΆ

Read configuration when its Layer builds. Expose parsed values through a service rather than reading environment variables throughout domain code.ΒΆ

Define accepted values before reading them

Config.Literals limits APP_ENV to three values. The declared default handles a missing setting.ΒΆ

packages/core/src/app-config.ts Β· 65e9465f
import { Config } from "effect";

import { ConfigService } from "./config-service.js";

export class AppConfig extends ConfigService.Service<AppConfig>()(
  "@rat-stack/core/AppConfig",
  {
    appEnv: Config.Literals(
      ["development", "test", "production"],
      "APP_ENV"
    ).pipe(Config.withDefault("development")),
  }
) {}

The environment name and accepted values live together. The default does not make an invalid supplied value acceptable.ΒΆ

Effect’s Config.schema can describe more complex validated settings. Use the Schema-backed path instead of writing a second parser for the same domain rule.ΒΆ

Supply another configuration source

A ConfigProvider supplies raw configuration inputs. Tests can replace the source with fromUnknown while still exercising the production parsing Layer.ΒΆ

packages/core/test/app-config.test.ts Β· 65e9465f
const withProvider = (root: Parameters<typeof ConfigProvider.fromUnknown>[0]) =>
  Layer.provide(
    AppConfig.layer,
    ConfigProvider.layer(ConfigProvider.fromUnknown(root))
  );

it.effect("reads APP_ENV through the active ConfigProvider", () =>
  Effect.gen(function* readsFromProvider() {
    const config = yield* AppConfig;
    expect(config.appEnv).toBe("test");
  }).pipe(Effect.provide(withProvider({ APP_ENV: "test" })))
);

The suite also checks rejection:ΒΆ

packages/core/test/app-config.test.ts Β· 65e9465f
it.effect("rejects values outside the literal set", () =>
  Effect.gen(function* rejectsUnknownLiteral() {
    const error = yield* Effect.flip(
      Layer.build(withProvider({ APP_ENV: "staging" }))
    );

    expect(error._tag).toBe("ConfigError");
  })
);

Supply typed values when parsing is not the task

AppConfig.configLayer accepts already-typed values. It uses Layer.succeed and skips parsing. Use this for a test about domain behavior. Use a provider when the test is about configuration decoding.ΒΆ

rat-stack’s factory provides these two ways to supply configuration.ΒΆ

Common wrong approach

Reading process.env inside each domain method ties the method to the host and allows inconsistent parsing. A config service moves that choice to construction and gives tests a service they can replace.ΒΆ

Keep missing settings and rejected settings distinct. Let construction fail when a supplied value violates the schema.ΒΆ

Effect idiom and house rule

See also: services16, Layer composition13, failures36, and tests22.ΒΆ

Read why dependency injection matters14.ΒΆ

Sources

  1. Effect contributors. 2026. Config. Effect 4.0.0.. Effect-TS. Config.Literals, defaults, schema validation, and capitalized Config constructors. Accessed 2026-10-06.
  2. Effect contributors. 2026. ConfigProvider. Effect 4.0.0.. Effect-TS. Providers replace the config source; fromUnknown and layer support controlled inputs. Accessed 2026-10-06.
  3. rat-stack contributors. 2026. ConfigService.. rat-stack. The factory provides a parsing Layer and a typed configLayer that bypasses parsing for controlled tests. Accessed 2026-10-06.
  4. rat-stack contributors. 2026. AppConfig tests.. rat-stack. Tests cover supplied config, a missing-value default, an invalid value, and direct typed configuration. Accessed 2026-10-06.
  5. Langton, Kit. 2026. Config. Effect Solutions.. Kit Langton. Topic framing for config Layers and replaceable providers; lowercase constructor examples do not match Effect 4.0.0. Accessed 2026-10-06.

Last changed 2026-10-07 in e73c9d9. Source on GitHub. Change log.

Linked from

  1. Agent guide. An Effect stack so pure (aspirational) Kit Langton will blush.

    Configuration is a supplied, validated dependency β€” Parse configuration when a Layer builds, reject invalid values, and supply controlled settings in tests.

  2. Change log. What changed in the files served here, newest first.

    Configuration is a supplied, validated dependency β€” edited: feat(learn): teach the next twenty cards with plain lines, diagrams and snippets

  3. Dependency injection is the reason to choose Effect. Effect tracks service requirements in types and supplies replaceable implementations through Layers.

    Read configuration as a dependency.ΒΆ

  4. Effect basics. An Effect describes work, expected failures, and required services before a runtime executes it.

    Configuration: parse supplied settings.ΒΆ

  5. Full agent guide. An Effect stack so pure (aspirational) Kit Langton will blush.
  6. Rat Stack lore | rat-stack. Short, source-grounded notes on the ideas and decisions behind rat-stack.

    Configuration is a supplied, validated dependency β€” Parse configuration when a Layer builds, reject invalid values, and supply controlled settings in tests.

  7. Source change history (log.md). The generated change log as Markdown.

    Configuration is a supplied, validated dependency β€” edited: feat(learn): teach the next twenty cards with plain lines, diagrams and snippets

Unlinked mentions (1)
  • Agent rules for rat-stack (AGENTS.md) β†’ ts derives a service from Effect Config (production layer reads the ConfigProvider, configLayer takes parsed values for tests); AppConfig is the instance and mirrors .env.schema.