typescript
37 lines · 8 steps
Validating env config at boot in NestJS
A NestJS ConfigModule that loads environment files and validates every variable with a Joi schema before the app starts.
Explained by
highlit
1import { Module } from '@nestjs/common';
2import { ConfigModule } from '@nestjs/config';
3import * as Joi from 'joi';
4
5@Module({
6 imports: [
7 ConfigModule.forRoot({
8 isGlobal: true,
9 cache: true,
10 expandVariables: true,
11 envFilePath: [`.env.${process.env.NODE_ENV ?? 'development'}`, '.env'],
12 validationSchema: Joi.object({
13 NODE_ENV: Joi.string()
14 .valid('development', 'test', 'staging', 'production')
15 .default('development'),
16 PORT: Joi.number().port().default(3000),
17 DATABASE_URL: Joi.string().uri({ scheme: ['postgres', 'postgresql'] }).required(),
18 DATABASE_POOL_SIZE: Joi.number().integer().min(1).max(50).default(10),
19 REDIS_URL: Joi.string().uri({ scheme: ['redis', 'rediss'] }).required(),
20 JWT_SECRET: Joi.string().min(32).required(),
21 JWT_EXPIRES_IN: Joi.string().default('15m'),
22 SMTP_HOST: Joi.string().hostname().required(),
23 SMTP_PORT: Joi.number().port().default(587),
24 SMTP_FROM: Joi.string().email().required(),
25 STRIPE_SECRET_KEY: Joi.string().pattern(/^sk_(test|live)_/).required(),
26 LOG_LEVEL: Joi.string()
27 .valid('error', 'warn', 'info', 'debug', 'verbose')
28 .default('info'),
29 }),
30 validationOptions: {
31 abortEarly: false,
32 allowUnknown: true,
33 },
34 }),
35 ],
36})
37export class AppConfigModule {}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Validating environment variables at startup turns silent misconfiguration into an immediate, descriptive crash.
- 2A single global config module lets every provider read validated settings without re-importing configuration everywhere.
- 3Layering env files by NODE_ENV keeps per-environment overrides separate while sharing a common base.
Related explainers
typescript
import { registerLocaleData } from '@angular/common'; import localeFr from '@angular/common/locales/fr'; import localeFrExtra from '@angular/common/locales/extra/fr'; import localeDe from '@angular/common/locales/de';
Locale-aware bootstrapping in Angular
i18n
localization
dependency-injection
Intermediate
8 steps
java
@Component @Converter public class EncryptedStringConverter implements AttributeConverter<String, String> {
Transparent column encryption in Spring & JPA
encryption
aes-gcm
jpa-converter
Advanced
10 steps
typescript
import { Inject, Injectable, Logger } from '@nestjs/common'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { Cache } from 'cache-manager'; import { InjectRepository } from '@nestjs/typeorm';
A cache-aside country lookup in NestJS
cache-aside
dependency-injection
batch-lookup
Intermediate
8 steps
java
package com.acme.billing.config; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties;
Feature-flagged beans with Spring @ConditionalOnProperty
feature-flags
conditional-beans
strategy-pattern
Intermediate
5 steps
typescript
import { Injectable, effect, signal, computed } from '@angular/core'; interface Preferences { theme: 'light' | 'dark';
A signal-based preferences store in Angular
signals
state-management
persistence
Intermediate
7 steps
typescript
import { useEffect, useState } from "react"; interface Section { id: string;
Building a scroll-spy hook in React
custom-hooks
intersectionobserver
dom-observation
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/validating-env-config-at-boot-in-nestjs-explained-typescript-9f1a/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.