简体   繁体   中英

How to use node-config in typescript?

After installing node-config and @types/config :

yarn add config
yarn add --dev @types/config

And adding config as described in lorenwest/node-config :

// default.ts
export default {
  server: {
    port: 4000,
  },
  logLevel: 'error',
};

When I am trying to use in my app:

import config from 'config';

console.log(config.server);

I am getting the error:

src/app.ts(19,53): error TS2339: Property 'server' does not exist on type 'IConfig'.

config.get utility can be used to get the config values like so:

import config from 'config';

const port: number = config.get('server.port');

I'm taking a slightly different approach - defining the variables in JavaScript, and accessing them in TypeScript.

Using the following folder structure:

├── config
│   ├── custom-environment-variables.js
│   ├── default.js
│   ├── development.js
│   └── production.js
└── server
    ├── config.ts
    └── main.ts

I define the configuration in the root config/ folder. For example:

// config/default.js
module.exports = {
  cache: false,
  port: undefined  // Setting to undefined ensures the environment config must define it
};

// config/development.js
module.exports = {
  port: '3000'
}

// config/production.js
module.exports = {
  cache: true
}

// config/custom-environment-variables.js
module.exports = {
  port: 'PORT'
}

Now, in TypeScript land, I define an interface to provide nicer autocomplete & documentation, and write some bridging code to pull in the config from node-config into my config map:

// server/config.ts
import nodeConfig from 'config';

interface Config {
  /** Whether assets should be cached or not. */
  cache: boolean;

  /** The port that the express server should bind to. */
  port: string;
}

const config: Config = {
  cache: nodeConfig.get<boolean>('cache'),
  port: nodeConfig.get<string>('port')
};

export default config;

Finally, I can now import and use my config variables inside any TypeScript code.

// server/main.ts
import express from 'express';
import config from './config';

const { port } = config;

const app = express();

app.listen(port);

This approach has the following benefits:

  • We can use the rich and battle-tested features available from node-config without needing to re-invent the wheel
  • We have a strongly-typed, well documented config map which can be imported and used from anywhere inside our TS code

From the previous, I was still having trouble where config was not able to find the server key from default.ts .

Below is how I am using npm config module. Updated export default { to export = :

// default.ts
export = {
  server: {
    port: 4000,
  },
  logLevel: 'error',
};

Usage within the app [Same]:

import config from 'config';

console.log(config.get('server'));

Use this "import * as config from 'config';" instead of "import config from 'config';"

    import * as config from 'config';

    const port = config.get('server.port');
    console.log('port', port);
    // port 4000

config/development.json

    {
      "server": {
          "port": 4000
      }
    }

and set NODE_ENV=development

 export NODE_ENV=development

note: No need this NODE_ENV set if you use default

I use IConfig interface, so I can set the config path first:

import { IConfig } from 'config';

export function dosomething() {

  process.env["NODE_CONFIG_DIR"] = 'path to config dir';

  //using get
  const config: IConfig = require("config");
  const port = config.get('server.port');
  console.log('port', port);

  //using custom schema
  const config2: { server: { port: number } } = require("config");
  console.log('config2.server.port', config2.server.port);

}

//port 4000
//config2.server.port 4000

您可以使用any返回类型。

const serverConfig: any = config.get('server');

The only way I could make this work is by uninstalling @types/config and modifying the type definitions to include my config files.

config.d.ts

    declare module 'config' {
    
      // Importing my config files
      import dev from '#config/development.json'
      import test from '#config/test.json'
      import prod from '#config/production.json'
    
      // Creating a union of my config
      type Config = typeof dev | typeof test | typeof prod
    
      var c: c.IConfig;
    
      namespace c {
    
        // see https://github.com/lorenwest/node-config/wiki/Using-Config-Utilities
        interface IUtil {
            // Extend an object (and any object it contains) with one or more objects (and objects contained in them).
            extendDeep(mergeInto: any, mergeFrom: any, depth?: number): any;
    
            // Return a deep copy of the specified object.
            cloneDeep(copyFrom: any, depth?: number): any;
    
            // Return true if two objects have equal contents.
            equalsDeep(object1: any, object2: any, dept?: number): boolean;
    
            // Returns an object containing all elements that differ between two objects.
            diffDeep(object1: any, object2: any, depth?: number): any;
    
            // Make a javascript object property immutable (assuring it cannot be changed from the current value).
            makeImmutable(object: any, propertyName?: string, propertyValue?: string): any;
    
            // Make an object property hidden so it doesn't appear when enumerating elements of the object.
            makeHidden(object: any, propertyName: string, propertyValue?: string): any;
    
            // Get the current value of a config environment variable
            getEnv(varName: string): string;
    
            // Return the config for the project based on directory param if not directory then return default one (config).
            loadFileConfigs(configDir?: string): any;
    
            // Return the sources for the configurations
            getConfigSources(): IConfigSource[];
            
            // Returns a new deep copy of the current config object, or any part of the config if provided.
            toObject(config?: any): any;
    
            /**
             * This allows module developers to attach their configurations onto
             * the 6 years agoInitial 0.4 checkin default configuration object so
             * they can be configured by the consumers of the module.
             */
            setModuleDefaults(moduleName:string, defaults:any): any;
        }
    
        interface IConfig {
            // Changed the get method definition.
            get<K extends keyof Config>(setting: K): Config[K];
            has(setting: string): boolean;
            util: IUtil;
        }
    
        interface IConfigSource {
            name: string;
            original?: string;
            parsed: any;
        }
      }
    
      export = c;
    
    }

Then I can do something like this:

在此处输入图片说明

Alternative 1

Use node-config-ts

Alternative 2

node-config-ts only updates the types every time you npm i , but alternative 2 is good if you want to be more explicit about which configuration files you are reading from, as they are imported directly in your project. This also means that tools such as nx knows to recompile your project if the config files change.

src/Config.ts

import config from 'config';
import DefaultConfig from '../config/default';
import CustomEnvironmentVariables from '../config/custom-environment-variables.json';
// If you have more configuration files (eg. production.ts), you might want to add them here.

export const Config = config.util.toObject() as typeof DefaultConfig & typeof CustomEnvironmentVariables;

src/app.ts

import {Config} from './Config';

// Has the correct type of "string"
console.log(Config.server);

tsconfig.json

{
  // ...
  "compilerOptions": {
    // ...
    // add these if you want to import .json configs
    "esModuleInterop": true,
    "resolveJsonModule": true,
  },
}

If you are using a monorepo and try to import default.ts in a child project, you might get the error...

error TS6059: File 'config/default.ts' is not under 'rootDir' 'my-project'. 'rootDir' is expected to contain all source files`. 

...then you might have to implement this answer .

In my opinion the biggest drawback of type safety is that it can create a false sense of security due to the common confusion between compile-time safety and runtime safety. It is especially true for node-config where the config is the product of merging multiple files and environment variables. This is why any solution that applies a type to your config without checking that it actually maps that type at runtime can create problems down the line. To solve this you could have a look at type guarding solutions like typia or zod for example.

Personally I use tools that are usually already present in my projects: JTD + Ajv . Here is a recipe if somebody is interested.

config
├── config.jtd.json
├── custom-environment-variables.js
├── default.json
└── development.json
src
├── config.ts
types
└── index.ts

The file config/config.jtd.json is written by hand, it looks like this (optionalProperties is used to ignore some things added by node-config, merging the interface with IConfig provided by node-config might be a better way to do the same thing):

{
  "properties": {
    "port": { "type": "uint32"}
  },
  "optionalProperties": { "util": {}, "get": {}, "has": {} }
}

The file types/index.ts contains a Config interface, it was created using jtd-codegen :

jtd-codegen config/config.jtd.json --typescript-out types

Then src/config.ts performs the validation and type casting:

import fs from 'fs'
import config from 'config'
import Ajv from 'ajv/dist/jtd'
import { type Config } from '../types'

const validate = new Ajv().compile(JSON.parse(fs.readFileSync('config/config.jtd.json', 'utf8')))
if (!validate(config)) throw new Error('invalid config', { cause: validate.errors })

config.util.makeImmutable(config)
const typedConfig = config as unknown as Config

export default typedConfig

Now when importing src/config.ts you have both compile-time type safety and runtime validation no matter what. The config object is also immutable so it will stay safe. Note that the config.get method is not covered, but I don't think it is necessary now that safety is ensured. You might even remove it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM