Skip to content

Configuration sources

Formerly known as ConfigSource, this module provides a way to load configuration from different sources like environment variables, JSON files, YAML files, and more.

It allows you to define a configuration schema and validate the configuration values at runtime. You can also use it to watch for changes in the configuration files and reload the configuration automatically, without reloading the server or application (excepting for port or specific options).

You can also create your own configuration sources by implementing the ConfigSource interface!

Supported config sources

TypeWatch modeImportDescription
EnvsNo@tsed/config/envsLoad environment variables from the process.env object.
DotEnvYes@tsed/config/dotenvLoad environment variables from .env files. Supports dotenv-flow and dotenv-expand.
JsonYes@tsed/config/jsonLoad configuration from a JSON file. Supports watch mode to reload the configuration when the file changes.
YAMLYes@tsed/config/yamlLoad configuration from a YAML file. Supports watch mode to reload the configuration when the file changes.

Installation

sh
npm install --save @tsed/config
sh
yarn add @tsed/config
sh
pnpm add @tsed/config
sh
bun add @tsed/config

Some config sources require additional packages to be installed. Here are the installation commands for each source:

Installation for dotenv

sh
npm install --save dotenv dotenv-expand dotenv-flow

```sh [yarn]
yarn add dotenv dotenv-expand dotenv-flow
sh
pnpm add dotenv dotenv-expand dotenv-flow
sh
bun add dotenv dotenv-expand dotenv-flow

Installation for yaml

sh
npm install --save js-yaml
sh
yarn add js-yaml
sh
pnpm add js-yaml
sh
bun add js-yaml

Usage

You need to import the @tsed/config module in your application. You can do this in your Server.ts file:

typescript
import {Configuration} from "@tsed/di";
import "@tsed/config"; // import the module to enable `.extends[]` options
import {EnvsConfigSource} from "@tsed/config/envs";
import {JsonConfigSource} from "@tsed/config/json";
import {withOptions} from "@tsed/config";

@Configuration({
  extends: [
    withOptions(JsonConfigSource, {
      path: "./config.json"
    }),
    EnvsConfigSource
  ]
})
export class Server {}

The extends option allows you to define multiple configuration sources. The order of the sources matters, as the last source will override the previous ones. In this example, the EnvsConfigSource will override any values defined in the JsonConfigSource.

Accessing configuration values

@tsed/config merge all configuration sources in a single object, but it also creates a configuration namespace for each source under the configs key. This allows you to access the configuration values from each source separately.

typescript
import {Injectable} from "@tsed/di";

@Injectable()
export class MyService {
  @Constant("myConfigKey")
  myConfigKey: string; // merge value from all sources

  @Constant("configs.json.myConfigKey")
  myConfigKeyFromJson: string; // get value from json file only

  @Constant("configs.env.myConfigKey")
  myConfigKeyFromEnv: string; // get value from env file only

  constructor() {
    console.log(this.myConfigKey); // "myValue"
  }
}

Naming a configuration source

You can name a configuration source by using the name option. This is useful when you have multiple sources and want to differentiate between them.

typescript
import {Configuration} from "@tsed/di";
import "@tsed/config";
import {EnvsConfigSource} from "@tsed/config/envs";
import {JsonConfigSource} from "@tsed/config/json";
import {withOptions} from "@tsed/config";

@Configuration({
  extends: [
    withOptions(JsonConfigSource, {
      name: "database",
      path: "./db.json"
    }),
    withOptions(JsonConfigSource, {
      name: "server",
      path: "./server.json"
    }),
    EnvsConfigSource
  ]
})
export class Server {}

Then you can use the @Constant decorator to inject the configuration value from a specific source:

typescript
import {Injectable} from "@tsed/di";

@Injectable()
export class MyService {
  @Constant("configs.database.host")
  databaseHost: string; // get value from json file only

  @Constant("configs.server.port")
  port: string; // get value from env file only
}

Validate configuration

You can validate your configuration using the validationSchema option. This is useful to ensure that your configuration values are of the expected type.

typescript
import {Configuration} from "@tsed/di";
import "@tsed/config";
import {string, object} from "@tsed/schema";
import {EnvsConfigSource} from "@tsed/config/envs";

@Configuration({
  extends: [
    withOptions(EnvsConfigSource, {
      validationSchema: object({
        DATABASE_HOST: string().required()
      })
    })
  ]
})
export class Server {}

Refresh strategy

Watch strategy

You can use the watch strategy to automatically reload your configuration when the file changes. This is useful for development environments where you want to see changes immediately.

typescript
import {Configuration} from "@tsed/di";
import {withOptions} from "@tsed/config";
import {JsonConfigSource} from "@tsed/config/json";

@Configuration({
  extends: [
    withOptions(JsonConfigSource, {
      watch: true
    })
  ]
})
export class Server {}

Refresh on request strategy

You can use the request strategy to reload your configuration on each request. This is useful for dynamic configurations that may change at runtime.

typescript
import {Configuration} from "@tsed/di";
import {withOptions} from "@tsed/config";
import {MyDbSourceConfig} from "./sources/DbSourceConfig.js";

@Configuration({
  extends: [
    withOptions(MyDbSourceConfig, {
      refreshOn: "request"
    })
  ]
})
export class Server {}

Important: Use the request strategy with caution, as it can impact performance. It's recommended to use it only for configurations that are expected to change frequently.

Refresh on response strategy

You can use the response strategy to reload your configuration on each response.

This is useful for dynamic configurations that may change at runtime. This version is more efficient than the request strategy, as it only reloads the configuration after a response is sent.

typescript
import {Configuration} from "@tsed/di";
import {withOptions} from "@tsed/config";
import {MyDbSourceConfig} from "./sources/DbSourceConfig.js";

@Configuration({
  extends: [
    withOptions(MyDbSourceConfig, {
      refreshOn: "response"
    })
  ]
})
export class Server {}

Enable config source on demand

You can enable a config source on demand by using the enable option. This is useful when you want to load a configuration source only when needed.

typescript
import {Configuration} from "@tsed/di";
import {withOptions} from "@tsed/config";
import {MyDbSourceConfig} from "./sources/DbSourceConfig.js";

@Configuration({
  extends: [
    withOptions(MyDbSourceConfig, {
      enable: process.env.NODE_ENV === "production"
    })
  ]
})
export class Server {}

Create a custom configuration source

Basic configuration source

You can create a custom configuration source by implementing the ConfigSource interface:

typescript
import type {ConfigSource} from "@tsed/config";

export interface MyCustomConfigSourceOptions {
  // Custom options
}

export class MyCustomConfigSource implements ConfigSource<MyCustomConfigSourceOptions> {
  options: MyCustomConfigSourceOptions;

  async getAll(): Promise<Record<string, unknown>> {
    return {
      key: value
    };
  }
}

Then use it in your configuration:

typescript
import {Configuration} from "@tsed/di";
import "@tsed/config";
import {MyCustomConfigSource} from "./MyCustomConfigSource.js";

@Configuration({
  extends: [
    MyCustomConfigSource,
    // or with options
    withOptions(MyCustomConfigSource, {
      // options
    })
  ]
})
export class Server {}

Implement watch method

You can implement the watch method to listen for changes in your configuration source. This is useful for dynamic configurations that may change at runtime.

typescript
import {ConfigSource} from "@tsed/config";
import {watch} from "node:fs";

export interface MyCustomProviderOptions {
  path: string;
}

export class MyCustomConfigSource implements ConfigSource<MyCustomConfigSourceOptions> {
  options: MyCustomConfigSourceOptions;

  async getAll(): Promise<Record<string, unknown>> {
    const {path, encoding = "utf8"} = this.options;
    // Read the file
    const fileContent = readFileSync(path, encoding);

    return JSON.parse(fileContent);
  }

  watch(onChange: () => void) {
    const {path} = this.options;
    const watcher = watch(path, onChange);

    return () => {
      watcher.close();
    };
  }
}

Inject configuration source

Given a configuration source, you can inject it in any service or controller using his name.

typescript
import {Configuration} from "@tsed/di";
import "@tsed/config";
import {withOptions} from "@tsed/config";
import {MyCustomConfigSource} from "./configs/MyCustomConfigSource.js";

@Configuration({
  extends: [
    withOptions(MyCustomConfigSource, {
      name: "myCustomConfig"
    })
  ]
})
export class Server {}

Then you can inject it in your service or controller using the @InjectConfigSource decorator:

typescript
import {Injectable, Inject} from "@tsed/di";
import {InjectConfigSource} from "@tsed/config/decorators/injectConfigSource.js";
import {MyCustomConfigSource} from "./configs/MyCustomConfigSource.js";

@Injectable()
export class MyService {
  @InjectConfigSource("myCustomConfig")
  configSource: MyCustomConfigSource;
}

Or using function injectConfigSource:

typescript
import {Injectable} from "@tsed/di";
import {injectConfigSource} from "@tsed/config/fn/injectConfigSource.js";
import {MyCustomConfigSource} from "./configs/MyCustomConfigSource.js";

@Injectable()
export class MyService {
  configSource = injectConfigSource<MyCustomConfigSource>("myCustomConfig");
}

Released under the MIT License.