nestjs-env-getter 0.1.0 → 1.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,56 @@
1
+ # Changelog
2
+
3
+ <!-- markdownlint-disable MD024 -->
4
+
5
+ All notable changes to this project will be documented in this file.
6
+
7
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
8
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
9
+
10
+ ## [1.0.0-beta.2] - 2025-10-17
11
+
12
+ ### Added
13
+
14
+ - **Inject Custom Providers**: Added the ability to inject custom providers into `AppConfigModule` via `forRoot` and `forRootAsync`. This allows `AppConfig` classes to depend on other services for dynamic configuration.
15
+
16
+ ### Changed
17
+
18
+ - **Updated Example**: The `nestjs-server` example was updated to demonstrate how to inject a custom `AppConfigOptionsService` to dynamically select configuration files based on the environment.
19
+
20
+ ## [1.0.0-beta.1] - 2025-10-08
21
+
22
+ ### Added
23
+
24
+ - **Configuration from Files**:
25
+ - Added `getRequiredConfigFromFile` and `getOptionalConfigFromFile` to read, parse, and validate JSON configuration files.
26
+ - Implemented automatic file watching with hot-reload, which updates configuration in-place without requiring an application restart.
27
+ - Configuration objects are enhanced with `on`, `once`, and `off` methods for subscribing to file change events.
28
+ - **Graceful Error Handling**:
29
+ - `getOptionalConfigFromFile` now gracefully handles missing files or JSON parsing errors by returning a default value or `undefined`, preventing process termination.
30
+ - Process now only terminates on critical errors, such as validation failures in class-based configs.
31
+ - **Project Scaffolding and Examples**:
32
+ - Added an example NestJS application demonstrating usage with a MongoDB connection.
33
+ - Included executable scripts for bootstrapping the library.
34
+ - Integrated `AppConfigModule` to streamline configuration setup.
35
+
36
+ ### Changed
37
+
38
+ - Updated all dependencies to their latest versions.
39
+
40
+ ## [0.1.0] - 2025-05-08
41
+
42
+ ### Changed
43
+
44
+ - Bumped version.
45
+ - Bumped development dependencies and changed the registry.
46
+
47
+ ### Fixed
48
+
49
+ - Updated the GitHub Action for publishing.
50
+ - Fixed issues in the CI/CD workflows.
51
+
52
+ ## [0.0.0-beta1] - 2025-02-04
53
+
54
+ ### Added
55
+
56
+ - Initial release with total refactoring.
package/README.md CHANGED
@@ -1,125 +1,401 @@
1
- # nestjs-env-getter
1
+ # NestJS ENV Getter
2
2
 
3
- The `nestjs-env-getter` is a NestJS module designed to simplify the process of retrieving and validating environment variables in your application. It provides a robust and type-safe way to handle environment variables, ensuring that your application starts with the correct configuration.
3
+ `nestjs-env-getter` is a powerful and lightweight NestJS module designed to streamline the process of retrieving and validating environment variables. It ensures your application starts with a correct and type-safe configuration by providing a robust API for handling various data types and validation scenarios.
4
4
 
5
- ## Features
5
+ ## Key Features
6
6
 
7
- - **Type-safe environment variable retrieval**: Retrieve environment variables as strings, numbers, booleans, URLs, objects, arrays, or time periods.
8
- - **Validation**: Validate environment variables against allowed values or custom validation functions.
9
- - **Default values**: Provide default values for optional environment variables.
10
- - **Error handling**: Automatically terminates the process with a descriptive error message if required environment variables are missing or invalid.
7
+ - **Type-Safe Retrieval**: Get environment variables as strings, numbers, booleans, URLs, objects, arrays, or time periods.
8
+ - **Built-in Validation**: Validate variables against allowed values or use custom validation functions.
9
+ - **Required vs. Optional**: Clearly distinguish between required variables that terminate the process if missing and optional ones with default values.
10
+ - **Error Handling**: Automatically terminates the process with a descriptive error message if a required variable is missing or invalid.
11
+ - **`.env` Support**: Automatically loads environment variables from a `.env` file using `dotenv`.
12
+ - **Configuration Scaffolding**: Includes a CLI helper to quickly set up a centralized configuration file.
11
13
 
12
14
  ## Installation
13
15
 
14
- Install the package using npm:
15
-
16
16
  ```bash
17
17
  npm install nestjs-env-getter
18
18
  ```
19
19
 
20
- Or using yarn:
20
+ ## Quick Start
21
21
 
22
- ```bash
23
- yarn add nestjs-env-getter
24
- ```
22
+ The recommended way to use `nestjs-env-getter` is by creating a centralized configuration service.
25
23
 
26
- ## Usage
24
+ ### 1. Create a Configuration Service
27
25
 
28
- ### Importing the Module
26
+ Create a file `src/app.config.ts` to define and validate all your environment variables.
27
+
28
+ ```typescript
29
+ // src/app.config.ts
30
+ import { Injectable } from "@nestjs/common";
31
+ import { EnvGetterService } from "nestjs-env-getter";
29
32
 
30
- To use the `EnvGetterService`, import the `EnvGetterModule` into your application's module:
33
+ @Injectable()
34
+ export class AppConfig {
35
+ readonly port: number;
36
+ readonly mongoConnectionString: string;
37
+ readonly nodeEnv: string;
38
+
39
+ constructor(private readonly envGetter: EnvGetterService) {
40
+ this.port = this.envGetter.getRequiredNumericEnv("PORT");
41
+ this.mongoConnectionString = this.envGetter.getRequiredEnv(
42
+ "MONGO_CONNECTION_STRING",
43
+ );
44
+ this.nodeEnv = this.envGetter.getRequiredEnv("NODE_ENV", [
45
+ "development",
46
+ "production",
47
+ ]);
48
+ }
49
+ }
50
+ ```
51
+
52
+ ### 2. Register the Configuration
53
+
54
+ In your `AppModule`, import `AppConfigModule` and provide your custom `AppConfig` class. This makes `AppConfig` a singleton provider available for dependency injection across your application.
31
55
 
32
56
  ```typescript
57
+ // src/app.module.ts
33
58
  import { Module } from "@nestjs/common";
34
- import { EnvGetterModule } from "nestjs-env-getter";
35
-
36
- @Module({ imports: [EnvGetterModule] })
59
+ import { AppConfigModule } from "nestjs-env-getter";
60
+ import { AppConfig } from "./app.config";
61
+
62
+ @Module({
63
+ imports: [
64
+ // Register the custom AppConfig class
65
+ AppConfigModule.forRoot({ useClass: AppConfig }),
66
+ ],
67
+ })
37
68
  export class AppModule {}
38
69
  ```
39
70
 
40
- ### Using the Service
71
+ ### 3. Use the Configuration
41
72
 
42
- Inject the `EnvGetterService` into your service or controller to retrieve environment variables:
73
+ Now you can inject `AppConfig` anywhere in your application.
43
74
 
44
75
  ```typescript
45
76
  import { Injectable } from "@nestjs/common";
46
- import { EnvGetterService } from "nestjs-env-getter";
77
+ import { AppConfig } from "./app.config";
47
78
 
48
79
  @Injectable()
49
- export class AppService {
50
- constructor(private readonly envGetter: EnvGetterService) {
51
- const port = this.envGetter.getRequiredNumericEnv("PORT");
52
- console.log(`Application will run on port: ${port}`);
80
+ export class DatabaseService {
81
+ private readonly connectionString: string;
82
+
83
+ constructor(private readonly config: AppConfig) {
84
+ this.connectionString = config.mongoConnectionString;
85
+ // Use the connection string...
53
86
  }
54
87
  }
55
88
  ```
56
89
 
57
- ### Examples
90
+ This approach centralizes your environment variable management, making your application cleaner and easier to maintain.
58
91
 
59
- #### Retrieving Required Environment Variables
92
+ ## CLI Helper
60
93
 
61
- ```typescript
62
- const dbHost = this.envGetter.getRequiredEnv("DB_HOST");
94
+ You can scaffold the configuration file and module setup automatically with the built-in CLI command. Run this in the root of your NestJS project:
95
+
96
+ ```bash
97
+ npx nestjs-env-getter init
63
98
  ```
64
99
 
65
- #### Retrieving Optional Environment Variables with Default Values
100
+ This command (shorthand `npx nestjs-env-getter i`) will:
66
101
 
67
- ```typescript
68
- const logLevel = this.envGetter.getOptionalEnv("LOG_LEVEL", "info");
69
- ```
102
+ 1. Create `src/app.config.ts` from a template.
103
+ 2. Import `AppConfigModule.forRoot({ useClass: AppConfig })` into your `src/app.module.ts`.
70
104
 
71
- #### Validating Environment Variables Against Allowed Values
105
+ ## API Reference (`EnvGetterService`)
72
106
 
73
- ```typescript
74
- const environment = this.envGetter.getRequiredEnv("NODE_ENV", [
75
- "development",
76
- "production",
77
- "test",
78
- ]);
79
- ```
107
+ Here are detailed examples of the methods available in `EnvGetterService`.
80
108
 
81
- #### Parsing Numeric Environment Variables
109
+ ### String
82
110
 
83
- ```typescript
84
- const timeout = this.envGetter.getRequiredNumericEnv("TIMEOUT");
85
- ```
111
+ - **`getRequiredEnv(name: string, allowed?: string[]): string`**
86
112
 
87
- #### Parsing Boolean Environment Variables
113
+ ```typescript
114
+ // Throws an error if DB_HOST is not set
115
+ const dbHost = this.envGetter.getRequiredEnv("DB_HOST");
88
116
 
89
- ```typescript
90
- const isDebugMode = this.envGetter.getRequiredBooleanEnv("DEBUG_MODE");
91
- ```
117
+ // Throws an error if NODE_ENV is not 'development' or 'production'
118
+ const nodeEnv = this.envGetter.getRequiredEnv("NODE_ENV", [
119
+ "development",
120
+ "production",
121
+ ]);
122
+ ```
92
123
 
93
- #### Parsing URLs
124
+ - **`getOptionalEnv(name: string, default?: string, allowed?: string[]): string | undefined`**
94
125
 
95
- ```typescript
96
- const apiUrl = this.envGetter.getRequiredURL("API_URL");
97
- ```
126
+ ```typescript
127
+ // Returns 'info' if LOG_LEVEL is not set
128
+ const logLevel = this.envGetter.getOptionalEnv("LOG_LEVEL", "info");
129
+ ```
98
130
 
99
- #### Parsing Time Periods
131
+ ### Number
100
132
 
101
- ```typescript
102
- const cacheDuration = this.envGetter.getRequiredTimePeriod(
103
- "CACHE_DURATION",
104
- "s",
105
- );
106
- ```
133
+ - **`getRequiredNumericEnv(name: string): number`**
134
+
135
+ ```typescript
136
+ // Throws an error if PORT is not a valid number
137
+ const port = this.envGetter.getRequiredNumericEnv("PORT");
138
+ ```
139
+
140
+ - **`getOptionalNumericEnv(name: string, default?: number): number | undefined`**
141
+
142
+ ```typescript
143
+ // Returns 3000 if TIMEOUT is not set or not a valid number
144
+ const timeout = this.envGetter.getOptionalNumericEnv("TIMEOUT", 3000);
145
+ ```
146
+
147
+ ### Boolean
148
+
149
+ - **`getRequiredBooleanEnv(name: string): boolean`**
150
+
151
+ ```typescript
152
+ // Throws an error if DEBUG_MODE is not 'true' or 'false'
153
+ const isDebug = this.envGetter.getRequiredBooleanEnv("DEBUG_MODE");
154
+ ```
155
+
156
+ - **`getOptionalBooleanEnv(name: string, default?: boolean): boolean | undefined`**
157
+
158
+ ```typescript
159
+ // Returns false if ENABLE_SSL is not set
160
+ const useSsl = this.envGetter.getOptionalBooleanEnv("ENABLE_SSL", false);
161
+ ```
162
+
163
+ ### URL
164
+
165
+ - **`getRequiredURL(name: string): URL`**
166
+
167
+ ```typescript
168
+ // Throws an error if API_URL is not a valid URL
169
+ const apiUrl = this.envGetter.getRequiredURL("API_URL");
170
+ ```
171
+
172
+ - **`getOptionalURL(name: string, default?: URL): URL | undefined`**
107
173
 
108
- #### Parsing JSON Objects
174
+ ```typescript
175
+ const defaultUrl = new URL("https://fallback.example.com");
176
+ // Returns defaultUrl if CDN_URL is not set or not a valid URL
177
+ const cdnUrl = this.envGetter.getOptionalURL("CDN_URL", defaultUrl);
178
+ ```
179
+
180
+ ### Time Period
181
+
182
+ Parses a string like `'10s'`, `'5m'`, `'2h'`, `'1d'` into a numeric value.
183
+
184
+ - **`getRequiredTimePeriod(name: string, resultIn: 'ms' | 's' | 'm' | 'h' | 'd' = 'ms'): number`**
185
+
186
+ ```typescript
187
+ // Parses '30s' into 30000
188
+ const cacheTtl = this.envGetter.getRequiredTimePeriod("CACHE_TTL");
189
+
190
+ // Parses '2h' into 2
191
+ const sessionHours = this.envGetter.getRequiredTimePeriod(
192
+ "SESSION_DURATION",
193
+ "h",
194
+ );
195
+ ```
196
+
197
+ - **`getOptionalTimePeriod(name: string, default: string, resultIn: ...): number`**
198
+
199
+ ```typescript
200
+ // Returns 60 (seconds) if JWT_EXPIRES_IN is not set
201
+ const expiresIn = this.envGetter.getOptionalTimePeriod(
202
+ "JWT_EXPIRES_IN",
203
+ "1m",
204
+ "s",
205
+ );
206
+ ```
207
+
208
+ ### Object (from JSON string)
209
+
210
+ - **`getRequiredObject<T>(name: string, cls?: ClassConstructor<T>): T`**
211
+
212
+ ```typescript
213
+ // ENV: '{"user": "admin", "port": 5432}'
214
+ const dbConfig = this.envGetter.getRequiredObject<{
215
+ user: string;
216
+ port: number;
217
+ }>("DB_CONFIG");
218
+ console.log(dbConfig.port); // 5432
219
+ ```
220
+
221
+ ### Array (from JSON string)
222
+
223
+ - **`getRequiredArray<T>(name: string, validate?: (el: T) => boolean | string): T[]`**
224
+
225
+ ```typescript
226
+ // ENV: '["192.168.1.1", "10.0.0.1"]'
227
+ const allowedIPs = this.envGetter.getRequiredArray<string>("ALLOWED_IPS");
228
+
229
+ // With validation
230
+ const servicePorts = this.envGetter.getRequiredArray<number>(
231
+ "SERVICE_PORTS",
232
+ (port) => {
233
+ if (typeof port !== "number" || port < 1024) {
234
+ return `Port ${port} is invalid. Must be a number >= 1024.`;
235
+ }
236
+ return true;
237
+ },
238
+ );
239
+ ```
240
+
241
+ ### Configuration from JSON Files
242
+
243
+ Load and validate configuration from JSON files with automatic file watching and hot-reload support.
244
+
245
+ - **`getRequiredConfigFromFile<T>(filePath: string, cls?: ClassConstructor<T>, watcherOptions?: FileWatcherOptions): T`**
246
+
247
+ Reads a required JSON configuration file. Automatically watches for file changes and updates the cached config.
248
+
249
+ ```typescript
250
+ // With class validation
251
+ class MongoCredentials {
252
+ connectionString: string;
253
+
254
+ constructor(data: any) {
255
+ if (!data.connectionString) {
256
+ throw new Error("connectionString is required");
257
+ }
258
+ this.connectionString = data.connectionString;
259
+ }
260
+ }
261
+
262
+ const mongoCreds = this.envGetter.getRequiredConfigFromFile(
263
+ "configs/mongo-creds.json",
264
+ MongoCredentials,
265
+ );
266
+ console.log(mongoCreds.connectionString);
267
+
268
+ // Disable file watching
269
+ const staticConfig = this.envGetter.getRequiredConfigFromFile(
270
+ "config.json",
271
+ undefined,
272
+ { enabled: false },
273
+ );
274
+
275
+ // Custom debounce timing (default is 200ms)
276
+ const config = this.envGetter.getRequiredConfigFromFile(
277
+ "config.json",
278
+ undefined,
279
+ { debounceMs: 1000 },
280
+ );
281
+ ```
282
+
283
+ - **`getOptionalConfigFromFile<T>(filePath: string, defaultValue?: T, cls?: ClassConstructor<T>, watcherOptions?: FileWatcherOptions): T | undefined`**
284
+
285
+ Reads an optional JSON configuration file. Returns the default value if the file doesn't exist or cannot be parsed as JSON. Only terminates the process if validation fails (when using a class constructor).
286
+
287
+ ```typescript
288
+ // With class validation
289
+ class TestConfig {
290
+ testConfigStringFromFile: string;
291
+
292
+ constructor(data: any) {
293
+ if (!data.testConfigStringFromFile) {
294
+ throw new Error("testConfigStringFromFile is required");
295
+ }
296
+ this.testConfigStringFromFile = data.testConfigStringFromFile;
297
+ }
298
+ }
299
+
300
+ const testConfig = this.envGetter.getOptionalConfigFromFile(
301
+ "configs/test-configs.json",
302
+ TestConfig,
303
+ );
304
+
305
+ // With default value - gracefully handles missing files and JSON parsing errors
306
+ const config = this.envGetter.getOptionalConfigFromFile(
307
+ "optional-config.json",
308
+ { fallbackValue: "default" },
309
+ );
310
+
311
+ // Note: Optional config files are graceful - missing files or JSON parsing errors
312
+ // return the default value (or undefined). Only validation errors crash the process.
313
+ ```
314
+
315
+ #### ⚠️ Important: Error Handling Behavior
316
+
317
+ **Required vs Optional Config Files:**
318
+
319
+ - **`getRequiredConfigFromFile`**: Crashes the process if the file is missing, has invalid JSON, or fails validation.
320
+ - **`getOptionalConfigFromFile`**: Only crashes the process on validation errors (when using class constructors). Missing files or JSON parsing errors gracefully return the default value or `undefined`.
321
+
322
+ This design ensures that optional configurations truly are optional - your application won't crash due to missing or malformed optional config files, but validation constraints are still enforced when files are present and parsable.
323
+
324
+ #### ⚠️ Important: Using Config Values by Reference
325
+
326
+ To ensure your application automatically receives updated config values when files change, you **must store config objects by reference**, not by extracting primitive values:
109
327
 
110
328
  ```typescript
111
- const config = this.envGetter.getRequiredObject<{ key: string }>("CONFIG");
112
- ```
329
+ // CORRECT - Store the object reference
330
+ @Injectable()
331
+ export class AppConfig {
332
+ mongoConfigs: MongoCredentials; // Store the entire object
333
+ testConfig?: TestConfig; // Store the entire object
334
+
335
+ constructor(protected readonly envGetter: EnvGetterService) {
336
+ // Store references to config objects
337
+ this.mongoConfigs = this.envGetter.getRequiredConfigFromFile(
338
+ "configs/mongo-creds.json",
339
+ MongoCredentials,
340
+ );
341
+ this.testConfig = this.envGetter.getOptionalConfigFromFile(
342
+ "configs/test-configs.json",
343
+ TestConfig,
344
+ );
345
+ }
346
+ }
113
347
 
114
- #### Parsing Arrays with Validation
348
+ // Access via the reference - values will update automatically
349
+ @Module({
350
+ imports: [
351
+ MongooseModule.forRootAsync({
352
+ useFactory: (config: AppConfig) => ({
353
+ uri: config.mongoConfigs.connectionString, // Access via reference
354
+ }),
355
+ inject: [AppConfig],
356
+ }),
357
+ ],
358
+ })
359
+ export class AppModule {}
360
+ ```
115
361
 
116
362
  ```typescript
117
- const allowedIPs = this.envGetter.getRequiredArray<string>(
118
- "ALLOWED_IPS",
119
- (ip) => ip.startsWith("192.") || "IP must start with 192.",
120
- );
363
+ // WRONG - Extracting primitive values breaks hot-reload
364
+ @Injectable()
365
+ export class AppConfig {
366
+ mongoConnectionString: string; // Primitive value - won't update!
367
+
368
+ constructor(protected readonly envGetter: EnvGetterService) {
369
+ const mongoCreds = this.envGetter.getRequiredConfigFromFile(
370
+ "configs/mongo-creds.json",
371
+ MongoCredentials,
372
+ );
373
+ // This extracts the VALUE at startup time - it won't update!
374
+ this.mongoConnectionString = mongoCreds.connectionString;
375
+ }
376
+ }
121
377
  ```
122
378
 
379
+ When the file watcher detects changes, it updates the **same object instance** in memory. If you store the object reference, your application automatically sees the updated values. If you extract primitive values (strings, numbers, booleans), they become snapshots that never update.
380
+
381
+ **File Watcher Options:**
382
+
383
+ - `enabled` (boolean, default: `true`): Enable or disable automatic file watching
384
+ - `debounceMs` (number, default: `200`): Delay in milliseconds before re-reading the file after a change
385
+
386
+ **Features:**
387
+
388
+ - ✅ Supports both absolute and relative paths (relative to `process.cwd()`)
389
+ - ✅ Automatic JSON parsing and validation
390
+ - ✅ **Hot-reload with reference preservation** - updates existing object instances in-place
391
+ - ✅ File watching with automatic change detection
392
+ - ✅ Debouncing to prevent excessive re-reads
393
+ - ✅ Class-based validation with constructor pattern
394
+ - ✅ **Graceful error handling for optional configs** - missing files or JSON parsing errors return default values
395
+ - ✅ Process termination only on validation errors (required configs crash on any error)
396
+ - ✅ **Event methods attached to default values** - consistent API even when files don't exist
397
+ - ✅ No application restart needed - changes apply immediately when using object references
398
+
123
399
  ## License
124
400
 
125
401
  This project is licensed under the MIT License. See the [LICENSE](./LICENSE) file for details.