Deepkit App

Configuration

In Deepkit applications, modules and your application can have configuration options. For example, a configuration can consist of database URLs, passwords, IPs, and so on. Services, HTTP/RPC/CLI controllers, and template functions can read these configuration options via dependency injection.

A configuration can be defined by defining a class with properties. This is a typesafe way to define a configuration for your entire application, and its values are automatically serialized and validated.

Example

import { MinLength } from '@deepkit/type';
import { App } from '@deepkit/app';

class Config {
    pageTitle: string & MinLength<2> = 'Cool site';
    domain: string = 'example.com';
    debug: boolean = false;
}

const app = new App({
    config: Config
});


app.command('print-config', (config: Config) => {
    console.log('config', config);
})

app.run();
$ curl http://localhost:8080/
Hello from Cool site via example.com

When no configuration loader is used, the default values will be used. To change the configuration, you can either use the app.configure({domain: 'localhost'}) method or use an environment configuration loader.

Set configuration values

By default, no values are overwritten, so default values are used. There are several ways to set configuration values.

  • Via app.configure({})
  • Environment variables for each option
  • Environment variable via JSON
  • dotenv-Files

You can use several methods to load the configuration at the same time. The order in which they are called is important.

Environment variables

To allow setting each configuration option via its own environment variable, use loadConfigFromEnv. The default prefix is APP_, but you can change it. It also automatically loads .env files. By default, it uses an uppercase naming strategy, but you can change that too.

For configuration options like pageTitle above, you can use APP_PAGE_TITLE="Other Title" to change the value.

new App({
    config: config,
    controllers: [MyWebsite],
})
    .loadConfigFromEnv({prefix: 'APP_'})
    .run();
APP_PAGE_TITLE="Other title" ts-node app.ts server:start

JSON environment variable

To change multiple configuration options via a single environment variable, use loadConfigFromEnvVariable. The first argument is the name of the environment variable.

new App({
    config: config,
    controllers: [MyWebsite],
})
    .loadConfigFromEnvVariable('APP_CONFIG')
    .run();
APP_CONFIG='{"pageTitle": "Other title"}' ts-node app.ts server:start

DotEnv Files

To change multiple configuration options via a dotenv file, use loadConfigFromEnv. The first argument is either a path to a dotenv (relative to cwd) or multiple paths. If it is an array, each path is tried until an existing file is found.

new App({
    config: config,
    controllers: [MyWebsite],
})
    .loadConfigFromEnv({envFilePath: ['production.dotenv', 'dotenv']})
    .run();
$ cat dotenv
APP_PAGE_TITLE=Other title
$ ts-node app.ts server:start

Module Configuration

Each imported module can have a module name. This name is used for the configuration paths used above.

For example, for configuring environment variables, the path for the FrameworkModule option port is FRAMEWORK_PORT. All names are written in uppercase by default. If a prefix of APP_ is used, the port can be changed via the following:

$ APP_FRAMEWORK_PORT=9999 ts-node app.ts server:start
2021-06-12T18:59:26.363Z [LOG] Start HTTP server, using 1 workers.
2021-06-12T18:59:26.365Z [LOG] HTTP MyWebsite
2021-06-12T18:59:26.366Z [LOG]     GET / helloWorld
2021-06-12T18:59:26.366Z [LOG] HTTP listening at http://localhost:9999/

In dotenv files it would also be APP_FRAMEWORK_PORT=9999.

In JSON environment variables via loadConfigFromEnvVariable('APP_CONFIG') on the other hand, it is the structure of the actual configuration class. framework becomes an object.

$ APP_CONFIG='{"framework": {"port": 9999}}' ts-node app.ts server:start

This works the same for all modules. No module prefix is required for your application configuration option (new App).

Configuration class

import { MinLength } from '@deepkit/type';

export class Config {
    title!: string & MinLength<2>; //this makes it required and needs to be provided
    host?: string;

    debug: boolean = false; //default values are supported as well
}
import { createModule } from '@deepkit/app';
import { Config } from './module.config.ts';

export class MyModule extends createModule({
   config: Config
}) {}

The values for the configuration options can be provided either in the constructor of the module, with the .configure() method, or via configuration loaders (e.g. environment variable loader).

import { MyModule } from './module.ts';

new App({
   imports: [new MyModule({title: 'Hello World'})],
}).run();

To dynamically change the configuration options of an imported module, you can use the process hook. This is a good place to either redirect configuration options or set up an imported module depending on the current module configuration or other module instance information.

import { MyModule } from './module.ts';

export class MainModule extends createModule({
}) {
    process() {
        this.getImportedModuleByClass(MyModule).configure({title: 'Changed'});
    }
}

At the application level, it works a little differently:

new App({
    imports: [new MyModule({title: 'Hello World'}],
})
    .setup((module, config) => {
        module.getImportedModuleByClass(MyModule).configure({title: 'Changed'});
    })
    .run();

When the root application module is created from a regular module, it works similarly to regular modules.

class AppModule extends createModule({
}) {
    process() {
        this.getImportedModuleByClass(MyModule).configure({title: 'Changed'});
    }
}

App.fromModule(new AppModule()).run();

Read configuration values

To use a configuration option in a service, you can use normal dependency injection. It is possible to inject either the entire configuration object, a single value, or a portion of the configuration.

Partial

To inject only a subset of the configuration values, use the Pick type.

import { Config } from './module.config';

export class MyService {
     constructor(private config: Pick<Config, 'title' | 'host'}) {
     }

     getTitle() {
         return this.config.title;
     }
}


//In unit tests, it can be instantiated via
new MyService({title: 'Hello', host: '0.0.0.0'});

//or you can use type aliases
type MyServiceConfig = Pick<Config, 'title' | 'host'};
export class MyService {
     constructor(private config: MyServiceConfig) {
     }
}

Single value

To inject only a single value, use the index access operator.

import { Config } from './module.config';

export class MyService {
     constructor(private title: Config['title']) {
     }

     getTitle() {
         return this.title;
     }
}

All

To inject all config values, use the class as dependency.

import { Config } from './module.config';

export class MyService {
     constructor(private config: Config) {
     }

     getTitle() {
         return this.config.title;
     }
}