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: services30, Layer composition21, failures38, 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 β€” new page: Initial commit from joelhooks/rat-stack template

  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 β€” new page: Initial commit from joelhooks/rat-stack template

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.