Tsed: Loading custom configurations

Created on 3 Apr 2018  路  39Comments  路  Source: tsedio/tsed

Informations

Type |聽Version
---|---
Question | 4.x


Description

Hi!

Just starting to learn this framework and I am starting to love it!
One question though,

I want to load custom configurations into my server from an external file and/or from environment specific files (dev/qa/test/prd). I saw the @Const and @Value decorators, so I was wondering how can I load custom values into them.

This project really reminds me of SpringBoot (which I also love), and they also have this functionality.

Thanks for your help,

Tomer

enhancement question

All 39 comments

Hi @TomerAmirV,

I think you can do that:

// conf.ts
import {deepExtends, EnvType} from "@tsed/core";

export const conf = {
     myConstant: "test",
     my: {
        value: "test"
     }
     // ... defaultConfig
};

deepExtends(conf, require('./conf/settings.json'));

if (process.env.NODE_ENV === EnvType.PROD) {
    deepExtends(conf, require('./conf/prod.settings.json'));
}

In your server Loader

import {conf} from "./config/conf";

@ServerSettings(conf)
export class Server extends ServerLoader{

}

Then:


@Service()
export class MyService {
    @Constant("myConstant")
    prop1: string;

    @Value("my.value")
    prop2: string;
}

I should work :)
See you

Also, You can create a wrapper for your config

@Service()
class ConfigService {

   private config: Map<string, any>;

   async $onInit() {
       this.load();
   }

   get(key) {
     this.load();
     return this.config.get(key);
   }

   private load() {
      if (!this.config) {
          // load config
      }
   }
}

Other way, there is a great place to create predefined service which we can override by @OverrideService() decorator. Such predefined service should be initialized before other services, becouse other services should have an access to config on initialization stage.
Config data loaded from that service should be accessible from @Value() and @Constant().
It is also a great place to set server settings.
The main idea is to manage appliaction's config in one place.

What do you think @Romakita?

Is there possibility to override ServerSettingsService?

@kalu111 It's possible but not recommended. The ServerSettingsService contain some methods use by the framework.
It also possible to use the globalServerSettings instance like this:

import {globalServerSettings, ServerSettingsService} from "@tsed/common";

@Service()
class ConfigService {
   constructor(private settingsService: ServerSetttingsService){

   }

  get myProp(){
     return this.settingsService.get('myProp');
  } 
  static load(){
       const conf = require('../configs/settings.default.json');

       if (process.env.NODE_ENV === EnvType.PROD) {
          deepExtends(conf, require('./conf/prod.settings.json'));
       }
       globalServerSettings.set(conf);
  }
}

ConfigService.load();

I thinks this approach is better :)

See you,
Romain

Thanks for your responses!
I'll check both ways and see what works best for me 馃槃

Hi,
Thanks @Romakita for your example.
I have a similar solution.

But I try to override ServerSettingServer only for read purposes, and my code does not work.
What am I doing wrong?

import { OverrideService, ServerSettingsService } from '@tsed/common';
import * as config from 'nconf';

@OverrideService(ServerSettingsService)
export class OverrideServerSettingsService extends ServerSettingsService {

    get<T>(propertyKey: string): T {
        let value = super.get(propertyKey);

        if (undefined === value) {
            value = config.get(propertyKey);
        }

        return <T>value;
    }
}

@kalu111 I think the ServerSettingsService is a bit specific because this service is built manually when the server is started.
Actually, I work on the containerization in Ts.ED. I would look for the override of the configuration is possible.
See you

Hi,
Did you check if it is possible?

Currently isn't possible. This service is built manually. I need to change the part to allow the OverrideService on this class.

Why not simply use an external dependency Config package for your projet?

I'm using an external config package, but I want to have posibility to read config values by using @Value() decorator. But to do that I need to override ServerSettingsService.

For me is the best, cleanest way to use class instead creating more complicated logic :)

@Romakita When to expect a release ?

@kalu111 You can already use @Value() without override the ServerSettingsService.

This code works:

const conf = require('./conf.dev.json'); // Something like that {'myCustomKey': 'value'}

@ServerSettings({
   mount: {
   },
   ...conf,
   // or
  conf
})

Then:

@Service()
class MyService {
   @Value('myCustomKey')
   myCustomKey: string;

  @Value("conf.myCustomKey")
  myCustomKey: string;
}

So I don't know why you need to override the service, if your need. is to use this decorator ;).

Well, to answer, I work on. This week, I guess, the feature will be released.

See you,
Romain

Thanks @Romakita
I need it because it allows me to inject more complex config logic (from db or micro service).
That's all :)

PR #376 will allow to override the ServerSettingsService (and more). I need help to review it and test the branch with a real project :)

Hi @Romakita ,
I will try to test it tomorrow :)

Bye.

Which branch should I checkout?

@kalu111 it has been merged to master already, have a try see if the latest ^4.20.2 works for you

Hi, Thanks for info :)
But my code is not called

import { OverrideService, ServerSettingsService } from '@tsed/common';
import * as config from 'nconf';

@OverrideService(ServerSettingsService)
export class OverrideServerSettingsService extends ServerSettingsService {

    get<T>(propertyKey: string): T {
        let value = super.get(propertyKey);

        if (undefined === value) {
            value = config.get(propertyKey);
        }

        return <T>value;
    }
}

Hi,
Did you test it properly?

With @OverrideService an instance of class is never created.
With @Service an instance of class is created but get() method is called only once with logRequestFields key - strange :(

How to properly override ServerSettingsService?
I have found an @OverrideProvider decorator but it doesn't help too.

Fixed

Latest version :)

You can use OverrideService or OverrideProvider. Then import your service in the Server.ts like that:

import "./services/CustomConfigService";

@ServerSettings()
class ServerLoader {

}

I just upgraded to version 4.22.1 and I am still using

const conf = require('./conf.dev.json'); // Something like that {'myCustomKey': 'value'}

@ServerSettings({
   mount: {
   },
   ...conf,
   // or
  conf
})

and not it seems like @Constant and @Value stopped working for me...

Is this method deprecated?

What is your log error ? Code using the decorators ? etc...

No error from tsed, I just get an undefined value

Ok, can you send me the code with the @Constant or @Value please ?

Sure 馃槃

Code using decorators:

@Service()
export default class RedisService {
    @Constant('redis.host')
    private static redisHost;

    constructor() {
        this.redis = new Redis(RedisService.redisHost);
    }
}

Error:

[ioredis] Unhandled error event: Error: connect ECONNREFUSED 127.0.0.1:6379
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1174:14)

logging the value prints: undefined

Notice that it's trying to access 127.0.0.1 which is not the value in my configuration.

Also, it's working in version 4.17.5

Ok, I'll try to solve it :)

ho shit... I know what is the problem. Now, the injector create your instance and bind your properties with Constant and Value after calling the constructor. I introduced a breaking change without thinking about it. My work about containerization impact some decorator like Constant or Value.

You can fix your code with the $onInit hook:

import {Env} from "@tsed/core";
import {Constant, Value} from "@tsed/common";

export class MyClass {

    @Constant("env")
    env: Env;

    @Value("swagger.path")
    swaggerPath: string;

    @Value("swagger.path", "defaultValue")
    swaggerPath: string;

    constructor() {
       console.log(this.swaggerPath) // undefined. Not available on constructor
    }
    $onInit() {
      console.log(this.swaggerPath)  // something
    }
 }

Is it possible for you ?

I tried and it doesn't seem to work... I'll try again tomorrow (it's late here 馃槃)

I don't this that I'll have any issues to switch (if it's working) if there will be no issues with dependency injection... (not sure in what order things are done)

I'll test and report back here

@Romakita
I tried to use onInit, but it doesn't seem to work... tried tsed 4.22.1 and 4.23.0

$onInit ?

yes, and I checked it's called (printed a message and saw in the logs)

Strange...

@TomerAmirV For me it works fine.
Here code example:
Server => https://github.com/Romakita/example-ts-express-decorator/blob/4.0.0/example-basic/src/Server.ts#L16
Service => https://github.com/Romakita/example-ts-express-decorator/blob/4.0.0/example-basic/src/services/calendars/CalendarsService.ts#L10-L19

The log display correctly the value of the constant.
See you

@Romakita
you are working with version: @tsed/common": "^4.19.0 馃槃
look here https://github.com/Romakita/example-ts-express-decorator/blob/4.0.0/example-basic/package.json

@TomerAmirV ^4.19.0 mean the latest version can be installed locally too. And my test work with the latest version :)

https://github.com/Romakita/example-ts-express-decorator/blob/4.0.0/example-basic/yarn.lock#L103
Yarn.lock use the 4.23.1 ;)

Ok... found out why it's not working for me...
I declared the field as private static:

@Service()
export class MyService {
    @Constant('redis.host')
    private static redisHost; // <-- here is the problem

    private redis: Redis.Redis;

    $onInit() {
        this.redis = new Redis(MyService.redisHost);
    }
}

When I removed the static modifier it started working...

@Service()
export class MyService {
    @Constant('redis.host')
    private redisHost; // <--- Changed here

    private redis: Redis.Redis;

    $onInit() {
        this.redis = new Redis(this.redisHost); // <--- Changed here
    }
}

I will change it for now, but what do you think about this?

Before it worked because the configuration was accessible globally. Now the configuration is in a container and is not accessible from a static property.

But isn't a problem if you change remote the static keyword, because the service is a singleton :).

Thanks @Romakita !
Awesome work 馃憤

Was this page helpful?
0 / 5 - 0 ratings

Related issues

m0uneer picture m0uneer  路  5Comments

yangukmo picture yangukmo  路  6Comments

vorph1 picture vorph1  路  6Comments

Ionaru picture Ionaru  路  5Comments

royibernthal picture royibernthal  路  4Comments