Skip to content
GitHub

Sources

Sources define where the library reads your configuration from, such as files or environment variables.

For loading source from files, use new FileSource('[file-name].[ext]'):

import { ConfigBuilder, FileSource } from "@layerfig/config";
import { schema } from "./schema";

export const config = new ConfigBuilder({
  validate: (finalConfig) => schema.parse(finalConfig),
})
  .addSource(new FileSource("base.json"))
  .build();

In this case, base.json is a file located in the <root-project-dir>/config directory (or the one specified via absoluteConfigFolderPath).

To support various project needs and preferences, we provide parsers for other common configuration file formats:

… or you can create your own parser.

The ObjectSource is a simple yet powerful tool for creating client-side configurations with Layerfig. It’s ideal when you want to avoid using files or need to override/debug your configuration. It also supports slots, allowing you to use environment variables:

import { ConfigBuilder, ObjectSource } from "@layerfig/config/client";
import { schema } from "./schema";

export const config = new ConfigBuilder({
  validate: (finalConfig) => schema.parse(finalConfig),
})
  .addSource(
    new ObjectSource({
      baseURL: "$PUBLIC_BASE_URL",
      randomValue: true,
    })
  )
  .build();

Since a framework like Vite adds PUBLIC_* environment variables to import.meta.env and makes them available on the client, your config object will contain all the type-checked values.

Consider the following scenario:

Your application’s configuration files are committed to version control and used to build a Docker image for production. When running this image locally for debugging, any change to a configuration value requires rebuilding the image. This process can be slow and results in testing a modified image rather than the actual production build.

To avoid this, use EnvironmentVariableSource to override configuration values at runtime without modifying the source files:

import {
  ConfigBuilder,
  FileSource,
  EnvironmentVariableSource,
} from "@layerfig/config";
import { schema } from "./schema";

export const config = new ConfigBuilder({
  validate: (finalConfig) => schema.parse(finalConfig),
})
  .addSource(new FileSource("base.json"))
  .addSource(new EnvironmentVariableSource())
  .build();

By default, the library expects environment variables with the following structure:

  • Prefix: "APP"
  • Prefix separator: "_"
  • Nested key separator: "__"

For example, the environment variable APP_port overrides the port key in your configuration.

Suppose your base.json contains:

{
  "appURL": "http://localhost:4444",
  "port": 4444,
  "appEnv": "local"
}

Define your schema and load the sources:

import { ConfigBuilder, FileSource } from "@layerfig/config";

export const config = new ConfigBuilder({
  validate: (finalConfig, z) => {
    const schema = z.object({
      appURL: z.url(),
      port: z.coerce.number().check(z.int(), z.positive()),
      appEnv: z.enum(["local", "dev", "prod"]),
    });

    return schema.parse(finalConfig);
  },
})
  .addSource(new FileSource("base.json"))
  .addSource(new EnvironmentVariableSource())
  .build();

Run the application with overrides:

APP_appEnv=prod node index.js

Since the environment variable source is added last, its values take precedence.

Use __ (double underscores) as a separator to target nested keys:

import {
  ConfigBuilder,
  FileSource,
  EnvironmentVariableSource,
  z,
} from "@layerfig/config";

const schema = z.object({
  api: z.object({
    vendor: z.object({
      aws: z.object({
        apiToken: z.string(),
      }),
    }),
  }),
});

const config = new ConfigBuilder({
  validate: (finalConfig) => schema.parse(finalConfig),
})
  .addSource(new FileSource("base.json"))
  .addSource(new EnvironmentVariableSource())
  .build();
APP_api__vendor__aws__apiToken=12345 node index.js

You can customize the prefix, prefix separator, and nested-key separator:

import { ConfigBuilder, FileSource } from "@layerfig/config";
import { schema } from "./schema";

const config = new ConfigBuilder({
  validate: (finalConfig) => schema.parse(finalConfig),
})
  .addSource(new FileSource("base.json"))
  .addSource(
    new EnvironmentVariableSource({
      prefix: "VULCAN",
      prefixSeparator: "--",
      separator: "----",
    })
  )
  .build();
VULCAN--api----vendor----aws----apiToken=12345 node index.js