node-cooper-nestjs - v0.2.0
    Preparing search index...

    node-cooper-nestjs - v0.2.0

    cooper-nestjs

    cooper-nestjs is a NestJS module that loads your application's CASC configuration once, at startup, and feeds it to @nestjs/config.

    ConfigService stays the way you read your configuration, and so does every third-party module you configure through it. What CASC replaces is where the values come from: .env loading and configuration factories. .env files are read by cooper itself. The loading and reading are cooper-config's, so ConfigService, this package and require('@joetjen/cooper-config') all read the same configuration.

    # config/config.casc
    #@version = 1.0
    
    db {
      host      = "db.internal"
      port      = 5432
      *password = ${DB_PASSWORD}
      timeout   = 30s
    }
    
    mail {
      from = "noreply@example.com"
    }
    
    import "${COOPER_ENV}/*.casc"
    
    @Module({
    imports: [CooperConfigModule.forRoot()], // loads config/config.casc, once; global
    })
    export class AppModule {}

    @Injectable()
    export class Mailer {
    constructor(
    private readonly config: ConfigService, // @nestjs/config, as always
    @InjectCooper('mail') private readonly mail: MailConfig, // one block
    ) {}

    send() {
    const host = this.config.getOrThrow<string>('db.host');
    const from = this.mail.from;
    }
    }
    npm install @joetjen/cooper-nestjs @nestjs/config
    

    NestJS 11 and 12 are supported, with @nestjs/config 4 and 12 respectively. Both are peer dependencies, so your own are used. @nestjs/config is optional; see Without @nestjs/config. cooper-config and cooper come along. You need Node.js 20.19 or later.

    The package is an ES module, because NestJS 12 is one. A CommonJS application, such as a NestJS 11 app compiled from TypeScript, loads it with require() as usual.

    Import it once, in the root module. It is global: every module sees what it provides without importing it.

    options given loads with them, cooper-config's load options (path, root, env, dotenvDir, modules, ...)
    no options, something loaded reads that: by cooper-config itself or --import @joetjen/cooper-config/register
    no options, nothing loaded loads config/config.casc under the project root
    an unknown option TypeError
    the document does not load NestFactory.create() rejects with CooperConfigError; the app never starts

    The project root is the directory of the nearest package.json above the working directory.

    It provides three things:

    • ConfigService, from @nestjs/config, with the whole document loaded into it. @nestjs/config reads no .env file of its own.
    • Each top-level block under getCooperToken(name), injected with @InjectCooper(name).
    • CooperConfig, cooper-config's readers.
    config.get('db.host')               // the value, or undefined
    config.get('db.port', 5432) // or a fallback
    config.getOrThrow('db.host') // or an error
    config.get('db') // a whole block

    A path that is in the document is read from the document, before process.env. Byte sizes arrive as bytes and durations as milliseconds. The cooper-config README has the full table, and how each block's secrets arrive. Every value is deep-frozen.

    Third-party modules read it the way they always have:

    TypeOrmModule.forRootAsync({
    inject: [ConfigService],
    useFactory: (config: ConfigService) => config.getOrThrow('db'),
    });
    constructor(@InjectCooper('db') private readonly db: DbConfig) {}
    

    The block is provided under getCooperToken('db'), for inject: [getCooperToken('db')] in a factory provider, or moduleRef.get(...).

    constructor(private readonly reader: CooperConfig) {}

    reader.get('db.port', 5432); // the value, or the fallback
    reader.require('db.host'); // the value, or a CooperConfigError naming the path
    reader.has('db.replica'); // whether anything, null included, is there
    reader.all(); // the whole configuration

    Paths are dotted ('db.pool.size') or an array of segments for a key holding a dot (['dotted.key', 'inner']).

    @nestjs/config is an optional peer. Without it, forRoot() provides CooperConfig and the blocks, and there is no ConfigService.

    ${COOPER_ENV} names the environment (dev, test, prod), falling back to NODE_ENV, and .env.<env> is chosen by it. See the cooper-config README.

    Point forRoot() at a fixture project:

    const moduleRef = await Test.createTestingModule({
    imports: [CooperConfigModule.forRoot({ root: fixtureDir, dotenv: false })],
    }).compile();
    npm install
    npm run precommit

    npm run precommit type-checks the JSDoc (tsc --checkJs --noEmit) and the TypeScript declarations, and runs the Mocha suite. See CONTRIBUTING.md and the cheatsheet.

    Apache-2.0, see LICENSE.