Multi-environment configuration
Configuration is a commonly used function, and different configuration information is often used in different environments.
In this article, we will introduce how Midway loads business configurations for different environments.
Configuration file
The simplest is to use the business configuration file capabilities provided by the framework.
This capability is available across all business code and components throughout the Midway lifecycle.
Configuration files can be exported in two formats, object form and function form.
After our practice, object form will be simpler and more friendly, and can avoid many wrong usages.
We will display it in this form in most documents.
Configuration file directory
We can customize a directory and put the configuration file in it.
For example, the src/config
directory.
➜ my_midway_app tree
.
├── src
│ ├── config
│ │ ├── config.default.ts
│ │ ├── config.prod.ts
│ │ ├── config.unittest.ts
│ │ └── config.local.ts
│ ├── interface.ts
│ └── service
├── test
├── package.json
└── tsconfig.json
There are some specific conventions for configuration file names.
config.default.ts
is the default configuration file, and all environments will load this configuration file.
For the rest of the file names, use config.environment
as the file name. For the concept of specific environment, please see Running Environment.
Configuration is not required, please add the environment configuration you need as appropriate.
Object form
The configuration file export format is object, for example:
// src/config/config.default.ts
import { MidwayConfig } from '@midwayjs/core';
export default {
keys: '1639994056460_8009',
koa: {
port: 7001,
},
} as MidwayConfig;
Function form
The configuration file is a function with appInfo
parameters. This function will be automatically executed when the framework is initialized, and the return value will be merged into the complete configuration object.
// src/config/config.default.ts
import { MidwayAppInfo, MidwayConfig } from '@midwayjs/core';
export default (appInfo: MidwayAppInfo): MidwayConfig => {
return {
keys: '1639994056460_8009',
koa: {
port: 7001,
},
view: {
root: path.join(appInfo.appDir, 'view'),
},
};
}
The parameter of this function is MidwayAppInfo
type, and the value is as follows.
appInfo | Description |
---|---|
pkg | package.json |
name | application name, same as pkg.name |
baseDir | src (local development) or dist (online) directory of the application code |
appDir | directory for application code |
HOME | user directory, such as /home/admin for the admin account |
root | Application root directory, baseDir only in local and unittest environments, and HOME in others. |
Configuration file definition
Midway provides MidwayConfig
as a unified configuration item definition, and all components will merge their definitions into this configuration item definition. Whenever a component is enabled (imports
in configuration.ts
), MidwayConfig
will automatically include the configuration definition for that component.
For this reason, please use the format recommended by the documentation as much as possible to achieve the best usage effect.
Whenever a new component is enabled, the configuration definition will automatically add the configuration items of the component. Through this behavior, you can also check whether a certain component is enabled in disguise.
For example, we enable the effect of the view component.
Why use objects instead of plain key exports?
-
If the user does not understand the configuration items, he still needs to check the document to understand the meaning of each item. Except for the first level of prompts, the subsequent levels of prompts do not have obvious efficiency improvements.
-
The form of key export has no advantage in displaying under an overly deep structure
-
The key export may be repeated, but there will be no warnings or errors at the code level, which is difficult to troubleshoot. The object form is more friendly
Load configuration file in object form
The framework provides the function of loading configuration files for different environments, which needs to be enabled in the src/configuration.ts
file.
There are two ways to load configuration, object format and specified directory format loading.
Starting from Midway v3, we will use object form as the main configuration loading form.
In scenarios such as single-file construction and ESM, only this standard module loading method is supported to load configurations.
The configuration file of each environment must explicitly specify to add, and subsequent frameworks will be merged according to the actual environment.
// src/configuration.ts
import { Configuration } from '@midwayjs/core';
import * as DefaultConfig from './config/config.default';
import * as LocalConfig from './config/config.local';
@Configuration({
importConfigs: [
{
default: DefaultConfig,
local: LocalConfig
}
]
})
export class MainConfiguration {
}
Configuration objects are passed in the array in importConfigs
. The key of each object is the environment, and the value is the configuration value corresponding to the environment. Midway will load the corresponding configuration according to the environment during startup.
Specify directory and file loading configuration
Specify a directory to load, and all config.*.ts
in the directory will be scanned and loaded.
ESM, single-file deployment, etc. do not support directory configuration loading.
importConfigs
here just specify the files that need to be loaded, and the actual runtime will automatically select the current environment to find the corresponding file suffix.
The rules for the configuration file are:
-
- You can specify a directory, the traditional
src/config
directory is recommended, or you can specify a file
- You can specify a directory, the traditional
-
- The file specification does not require the ts suffix
-
- Configuration file Must be explicitly specified to add
Example: specify directory
// src/configuration.ts
import { Configuration } from '@midwayjs/core';
import { join } from 'path';
@Configuration({
importConfigs: [
join(__dirname, './config/'),
]
})
export class MainConfiguration {
}
Example: specifying a specific file
When manually specifying a batch of files, if the files do not exist at this time, an error will be reported.
// src/configuration.ts
import { Configuration } from '@midwayjs/core';
import { join } from 'path';
@Configuration({
importConfigs: [
join(__dirname, './config/config.default'),
join(__dirname, './config/config.local'),
join(__dirname, './config/custom.local') // You can use custom naming, as long as the middle part has an environment
]
})
export class MainConfiguration {
}
You can also use the configuration outside the project, but please use the absolute path and *.js
suffix.
For example, the directory structure is as follows (note the customConfig.default.js
file):
base-app
├── package.json
├── customConfig.default.js
└── src
├── configuration.ts
└── config
└── config.default.ts
// src/configuration.ts
import { Configuration } from '@midwayjs/core';
import { join } from 'path';
@Configuration({
importConfigs: [
join(__dirname, './config/'),
join(__dirname, '../customConfig.default'),
]
})
export class MainConfiguration {
}
Configure loading order
There is a priority for configuration (Application Code > Component), and the priority will be higher relative to this running environment.
For example, the loading sequence for loading a configuration in the prod environment is as follows. The later loaded configuration will overwrite the previous configuration with the same name.
-> Component config.default.ts
-> Apply config.default.ts
-> component config.prod.ts
-> apply config.prod.ts
Configure merge rules
By default, the **/config.defaut.ts
file and the **/config.{environment}.ts
file will be loaded.
For example, the following code will search for config.default.*
and config.local.*
files in the local
environment. If it is in other environments, it will only search for config.default.*
and config.{Current environment}.*
, if the file does not exist, it will not be loaded and no error will be reported.
// src/configuration.ts
import { Configuration } from '@midwayjs/core';
import { join } from 'path';
@Configuration({
importConfigs: [
join(__dirname, './config/'),
]
})
export class MainConfiguration {
}
For forward compatibility, we have done some processing on the configuration reading of some special environments. The value of the environment here refers to the result based on the values of NODE_ENV
and MIDWAY_SERVER_ENV
.
Environment value | Configuration file read |
---|---|
prod | *.default.ts + *.prod.ts |
production | *.default.ts + *.production.ts + *.prod.ts |
unittest | *.default.ts + *.unittest.ts |
test | *.default.ts + *.test.ts + *.unittest.ts |
Except for the above table, the rest are the values of *.default.ts + *.{current environment}.ts
.
In addition, the configuration is merged using the extend2 module for deep copying, and the extend2 fork from [extend](https ://github.com/justmoon/node-extend), there will be differences when handling arrays.
const a = {
arr: [ 1, 2 ],
};
const b = {
arr: [ 3 ],
};
extend(true, a, b);
// => { arr: [ 3 ] }
According to the example above, the framework directly overwrites the array instead of doing the merge.
get configuration
Midway will save the configuration in the internal configuration service. The entire structure is an object, which is injected using the @Config
decorator when used by Midway business code.
Single configuration value
By default, it will be obtained from the configuration object according to the string parameter value of the decorator.
import { Config } from '@midwayjs/core';
export class IndexHandler {
@Config('userService')
userConfig;
async handler() {
console.log(this.userConfig); // { appname: 'test'}
}
}
Deep level configuration values
If the value of the configuration object is deep in the object, it can be obtained in a cascaded manner.
For example, the data source is:
{
"userService": {
"appname": {
"test": {
"data": "xxx"
}
}
}
}
You can write complex acquisition expressions to acquire values, examples are as follows.
import { Config } from '@midwayjs/core';
export class IndexHandler {
@Config('userService.appname.test.data')
data;
async handler() {
console.log(this.data); // xxx
}
}
The entire configuration object
You can also get the entire configuration object through the special attribute ALL
.
import { Config, ALL } from '@midwayjs/core';
export class IndexHandler {
@Config(ALL)
allConfig;
async handler() {
console.log(this.allConfig); // { userService: { appname: 'test'}}
}
}
Change setting
During the coding process, we have some places where the configuration can be dynamically modified for use in different scenarios.
Modification during life cycle
midway has added an asynchronous configuration loading life cycle, which can be executed after the configuration is loaded.
// src/configuration.ts
import { Configuration, IMidwayContainer } from '@midwayjs/core';
import { join } from 'path';
import { RemoteConfigService } from '../service/remote'; // Customized access to remote configuration service
@Configuration({
importConfigs: [
join(__dirname, './config/'),
]
})
export class MainConfiguration {
async onConfigLoad(container: IMidwayContainer) {
// Here you can modify the global configuration
const remoteConfigService = await container. getAsync(RemoteConfigService);
const remoteConfig = await remoteConfigService.getData();
// The return value here will be merged with the global config
// const remoteConfig = {
// typeorm: {
// dataSource: {
// default: {
// type: "mysql",
// host: "localhost",
// port: 3306,
// username: "root",
// password: "123456",
// database: "admin",
// synchronize: false,
// logging: false,
// entities: "/**/**.entity.ts",
// dateStrings: true
// }
// }
// }
// }
return remoteConfig;
}
}
The onConfigLoad
lifecycle is executed after the egg plugin (if any) is initialized and cannot be used to override the configuration of the egg plugin.
Modify at startup
You can add configuration using Bootstrap's configure
method before starting the code.
The configure
method can pass a globalConfig
attribute, which can pass a global configuration before the application starts.
If you pass an array, you can differentiate between environments.
// bootstrap.js
const { Bootstrap } = require('@midwayjs/bootstrap');
Bootstrap
.configure({
globalConfig: [
{
default: {
abc: '123'
},
unittest: {
abc: '321'
}
}
]
})
.run();
// in unittest, app.getConfig('abc') => '321'
If an object is passed, it is overridden directly.
// bootstrap.js
const { Bootstrap } = require('@midwayjs/bootstrap');
Bootstrap
.configure({
globalConfig: {
abc: 'text'
}
})
.run();
// app.getConfig('abc') => 'text'
Modify using API
To modify the configuration in other scenarios, you can use the API provided by midway.