npm package: @nam088/nestjs-configx
Description: Typed, validated, multi-source configuration for NestJS (>=10).
Author: Nam088
Latest version: 0.1.1

# @nam088/nestjs-configx

Typed, validated, multi-source configuration for NestJS (>=10) powered by Zod.

- **Type-safe access**: Strongly-typed getters with autocomplete for your schema paths
- **Validation at startup**: Parse and validate config before your app runs
- **Multiple sources (extensible)**: Loads from environment by default, easy to extend
- **Developer-friendly utils**: Helpers for boolean, number, JSON, enums, namespacing, and caching

## Installation

```bash
npm install @nam088/nestjs-configx zod
```

Peer dependencies:
- `@nestjs/common` and `@nestjs/core` (>=10 <12)
- Node.js >= 18

## Quick start

1) Define your configuration schema with Zod

```ts
// config.schema.ts
import { z } from 'zod';

export const ConfigSchema = z.object({
  app: z.object({
    name: z.string().default('my-app'),
    env: z.enum(['development', 'test', 'production']).default('development'),
    port: z.coerce.number().int().min(1).max(65535).default(3000),
  }),
  database: z.object({
    url: z.string().url(),
    ssl: z.coerce.boolean().optional(),
  }),
});

export type Config = z.infer<typeof ConfigSchema>;
```

2) Register the module

```ts
// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigxModule } from '@nam088/nestjs-configx';
import { ConfigSchema } from './config.schema';

@Module({
  imports: [
    ConfigxModule.forRoot({
      schema: ConfigSchema,
      isGlobal: true,
      // optional
      // envFilePath: ['.env', `.env.${process.env.NODE_ENV}`],
      // ignoreEnvFile: false,
      // cache: true,
    }),
  ],
})
export class AppModule {}
```

3) Read configuration anywhere

```ts
import { Injectable } from '@nestjs/common';
import { ConfigxService } from '@nam088/nestjs-configx';
import type { Config } from './config.schema';

@Injectable()
export class AppService {
  constructor(private readonly config: ConfigxService) {}

  getPort(): number {
    // Strongly-typed path access when using generics
    return this.config.get<number>('app.port') ?? 3000;
  }

  getDbUrl(): string {
    return this.config.getOrThrow('database.url');
  }
}
```

Or inject via decorator:

```ts
import { Injectable } from '@nestjs/common';
import { InjectConfigx, ConfigxService } from '@nam088/nestjs-configx';

@Injectable()
export class Example {
  constructor(@InjectConfigx() private readonly cfg: ConfigxService) {}
}
```

## .env example

```env
APP_NAME=my-app
APP_ENV=production
APP_PORT=8080
DATABASE_URL=https://example.com
DATABASE_SSL=true
```

By default, variables are loaded from `.env` (if present) and expanded (dotenv + dotenv-expand). You can customize with `envFilePath` or disable with `ignoreEnvFile`.

## Async configuration

```ts
ConfigxModule.forRootAsync({
  inject: [SomeService],
  useFactory: (svc: SomeService) => ({
    schema: ConfigSchema,
    isGlobal: true,
    envFilePath: svc.getEnvFiles(),
    cache: true,
  }),
});
```

## API

### Module
- `ConfigxModule.forRoot(options)`
- `ConfigxModule.forRootAsync(options)`

`options`:
- `schema` (required): Zod schema
- `isGlobal` (default: false)
- `envFilePath?: string | string[]` (default: '.env')
- `ignoreEnvFile?: boolean` (default: false)
- `cache?: boolean` (default: false) — enables `get()` result caching per path

### Service: `ConfigxService`
- `get<T>(path: string): T | undefined`
- `getOrThrow<T>(path: string, message?: string): T`
- `getString(path, defaultValue?)`
- `getNumber(path, defaultValue?)`
- `getBoolean(path, defaultValue?)`
- `getJSON<T>(path, defaultValue?)`
- `getEnum<T extends string>(path, values: readonly T[], defaultValue?)`
- `getAll<T = Record<string, unknown>>(): T` — returns the validated tree
- `has(path: string): boolean`
- `set<T>(path: string, value: T): void` — updates in-memory validated config and `process.env[path]`
- `namespaced(prefix: string)` — returns the same getters scoped by prefix
- `refresh(): Promise<void>` — reloads from source and re-validates (clears cache when enabled)

Type-safe path access is supported via generics when you thread your schema shape through your service typing.

## Examples

See a runnable example in `examples/basic-use`.

## Scripts

```bash
npm run build        # build to dist
npm run test         # unit tests
npm run test:cov     # coverage
npm run lint         # eslint
npm run format       # prettier
```

## License

MIT © Nam088