260223:1415 20260223 nextJS & nestJS Best pratices
All checks were successful
Build and Deploy / deploy (push) Successful in 4m44s
All checks were successful
Build and Deploy / deploy (push) Successful in 4m44s
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
---
|
||||
title: Use ConfigModule for Environment Configuration
|
||||
impact: LOW-MEDIUM
|
||||
impactDescription: Proper configuration prevents deployment failures
|
||||
tags: devops, configuration, environment, validation
|
||||
---
|
||||
|
||||
## Use ConfigModule for Environment Configuration
|
||||
|
||||
Use `@nestjs/config` for environment-based configuration. Validate configuration at startup to fail fast on misconfigurations. Use namespaced configuration for organization and type safety.
|
||||
|
||||
**Incorrect (accessing process.env directly):**
|
||||
|
||||
```typescript
|
||||
// Access process.env directly
|
||||
@Injectable()
|
||||
export class DatabaseService {
|
||||
constructor() {
|
||||
// No validation, can fail at runtime
|
||||
this.connection = new Pool({
|
||||
host: process.env.DB_HOST,
|
||||
port: parseInt(process.env.DB_PORT), // NaN if missing
|
||||
password: process.env.DB_PASSWORD, // undefined if missing
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Scattered env access
|
||||
@Injectable()
|
||||
export class EmailService {
|
||||
sendEmail() {
|
||||
// Different services access env differently
|
||||
const apiKey = process.env.SENDGRID_API_KEY || 'default';
|
||||
// Typos go unnoticed: process.env.SENDGRID_API_KY
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Correct (use @nestjs/config with validation):**
|
||||
|
||||
```typescript
|
||||
// Setup validated configuration
|
||||
import { ConfigModule, ConfigService, registerAs } from '@nestjs/config';
|
||||
import * as Joi from 'joi';
|
||||
|
||||
// config/database.config.ts
|
||||
export const databaseConfig = registerAs('database', () => ({
|
||||
host: process.env.DB_HOST,
|
||||
port: parseInt(process.env.DB_PORT, 10),
|
||||
username: process.env.DB_USERNAME,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
}));
|
||||
|
||||
// config/app.config.ts
|
||||
export const appConfig = registerAs('app', () => ({
|
||||
port: parseInt(process.env.PORT, 10) || 3000,
|
||||
environment: process.env.NODE_ENV || 'development',
|
||||
apiPrefix: process.env.API_PREFIX || 'api',
|
||||
}));
|
||||
|
||||
// config/validation.schema.ts
|
||||
export const validationSchema = Joi.object({
|
||||
NODE_ENV: Joi.string()
|
||||
.valid('development', 'production', 'test')
|
||||
.default('development'),
|
||||
PORT: Joi.number().default(3000),
|
||||
DB_HOST: Joi.string().required(),
|
||||
DB_PORT: Joi.number().default(5432),
|
||||
DB_USERNAME: Joi.string().required(),
|
||||
DB_PASSWORD: Joi.string().required(),
|
||||
DB_NAME: Joi.string().required(),
|
||||
JWT_SECRET: Joi.string().min(32).required(),
|
||||
REDIS_URL: Joi.string().uri().required(),
|
||||
});
|
||||
|
||||
// app.module.ts
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true, // Available everywhere without importing
|
||||
load: [databaseConfig, appConfig],
|
||||
validationSchema,
|
||||
validationOptions: {
|
||||
abortEarly: true, // Stop on first error
|
||||
allowUnknown: true, // Allow other env vars
|
||||
},
|
||||
}),
|
||||
TypeOrmModule.forRootAsync({
|
||||
inject: [ConfigService],
|
||||
useFactory: (config: ConfigService) => ({
|
||||
type: 'postgres',
|
||||
host: config.get('database.host'),
|
||||
port: config.get('database.port'),
|
||||
username: config.get('database.username'),
|
||||
password: config.get('database.password'),
|
||||
database: config.get('database.database'),
|
||||
autoLoadEntities: true,
|
||||
}),
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
// Type-safe configuration access
|
||||
export interface AppConfig {
|
||||
port: number;
|
||||
environment: 'development' | 'production' | 'test';
|
||||
apiPrefix: string;
|
||||
}
|
||||
|
||||
export interface DatabaseConfig {
|
||||
host: string;
|
||||
port: number;
|
||||
username: string;
|
||||
password: string;
|
||||
database: string;
|
||||
}
|
||||
|
||||
// Type-safe access
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
constructor(private config: ConfigService) {}
|
||||
|
||||
getPort(): number {
|
||||
// Type-safe with generic
|
||||
return this.config.get<number>('app.port');
|
||||
}
|
||||
|
||||
getDatabaseConfig(): DatabaseConfig {
|
||||
return this.config.get<DatabaseConfig>('database');
|
||||
}
|
||||
}
|
||||
|
||||
// Inject namespaced config directly
|
||||
@Injectable()
|
||||
export class DatabaseService {
|
||||
constructor(
|
||||
@Inject(databaseConfig.KEY)
|
||||
private dbConfig: ConfigType<typeof databaseConfig>,
|
||||
) {
|
||||
// Full type inference!
|
||||
const host = this.dbConfig.host; // string
|
||||
const port = this.dbConfig.port; // number
|
||||
}
|
||||
}
|
||||
|
||||
// Environment files support
|
||||
ConfigModule.forRoot({
|
||||
envFilePath: [
|
||||
`.env.${process.env.NODE_ENV}.local`,
|
||||
`.env.${process.env.NODE_ENV}`,
|
||||
'.env.local',
|
||||
'.env',
|
||||
],
|
||||
});
|
||||
|
||||
// .env.development
|
||||
// DB_HOST=localhost
|
||||
// DB_PORT=5432
|
||||
|
||||
// .env.production
|
||||
// DB_HOST=prod-db.example.com
|
||||
// DB_PORT=5432
|
||||
```
|
||||
|
||||
Reference: [NestJS Configuration](https://docs.nestjs.com/techniques/configuration)
|
||||
Reference in New Issue
Block a user