Lesson 20 of 49 intermediate

Environment Variables & Configuration

Different Settings for Different Worlds

Open interactive version (quiz + challenge)

Real-world analogy

Think of your app like you at different places. At home, you wear pajamas, eat from the fridge, and have your own WiFi password. At the office, you wear business clothes, eat from the cafeteria, and use the company network. Same YOU, different settings! Environment variables let your app behave differently in development (home) vs production (office) without changing the code.

What is it?

Environment variables are key-value pairs that configure your application without hardcoding values in source code. They allow the same codebase to behave differently across environments (development, staging, production). The @nestjs/config module provides a clean, type-safe way to manage configuration in NestJS.

Real-world relevance

Every deployed application uses environment variables. Your local development uses a local database URL. Staging points to a test database. Production points to the real database. The code is identical — only the environment variables change.

Key points

Code example

// .env file (NEVER commit this!)
DATABASE_URL=mongodb://localhost:27017/myapp
JWT_SECRET=super-secret-key-change-in-production
PORT=3000
NODE_ENV=development

// .env.example (DO commit this)
DATABASE_URL=
JWT_SECRET=
PORT=3000
NODE_ENV=development

// .gitignore
.env
.env.local
.env.production

// app.module.ts — Load config
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true, // available everywhere
      envFilePath: '.env',
    }),
  ],
})
export class AppModule {}

// any.service.ts — Use config values
import { ConfigService } from '@nestjs/config';

@Injectable()
export class AuthService {
  constructor(private config: ConfigService) {}

  getJwtSecret(): string {
    return this.config.get<string>('JWT_SECRET');
  }
}

// Config validation with Joi
import * as Joi from 'joi';

ConfigModule.forRoot({
  validationSchema: Joi.object({
    DATABASE_URL: Joi.string().required(),
    JWT_SECRET: Joi.string().min(32).required(),
    PORT: Joi.number().default(3000),
    NODE_ENV: Joi.string()
      .valid('development', 'production', 'test')
      .default('development'),
  }),
});
// If DATABASE_URL is missing, app crashes at startup:
// Error: "DATABASE_URL" is required

Line-by-line walkthrough

  1. 1. .env file (NEVER commit this!)
  2. 2.
  3. 3.
  4. 4.
  5. 5.
  6. 6.
  7. 7. .env.example (DO commit this)
  8. 8.
  9. 9.
  10. 10.
  11. 11.
  12. 12.
  13. 13. .gitignore
  14. 14. Method chaining on the previous expression
  15. 15. Method chaining on the previous expression
  16. 16. Method chaining on the previous expression
  17. 17.
  18. 18. app.module.ts — Load config
  19. 19. Importing required dependencies
  20. 20.
  21. 21. Decorator that adds metadata or behavior
  22. 22.
  23. 23.
  24. 24.
  25. 25.
  26. 26.
  27. 27.
  28. 28.
  29. 29. Exporting for use in other files
  30. 30.
  31. 31. any.service.ts — Use config values
  32. 32. Importing required dependencies
  33. 33.
  34. 34. Decorator that adds metadata or behavior
  35. 35. Exporting for use in other files
  36. 36.
  37. 37.
  38. 38.
  39. 39. Returning a value
  40. 40. Closing block
  41. 41. Closing block
  42. 42.
  43. 43. Config validation with Joi
  44. 44. Importing required dependencies
  45. 45.
  46. 46.
  47. 47.
  48. 48.
  49. 49.
  50. 50.
  51. 51.
  52. 52. Method chaining on the previous expression
  53. 53. Method chaining on the previous expression
  54. 54.
  55. 55.
  56. 56. If DATABASE_URL is missing, app crashes at startup:
  57. 57. Error: "DATABASE_URL" is required

Spot the bug

// .env committed to Git!
DATABASE_URL=mongodb://admin:password123@production-server:27017/myapp
JWT_SECRET=my-super-secret-key
Need a hint?
Should .env files with real credentials be in your Git repository?
Show answer
NEVER commit .env files with real credentials to Git! Anyone with repo access can see your passwords. Fix: add .env to .gitignore and use .env.example with empty values instead.

Explain like I'm 5

Think of your app like getting dressed. At home you wear pajamas (development). At school you wear a uniform (production). Same you, different outfits! Environment variables are like outfit lists for each place - you don't sew clothes into your body, you just change what you wear.

Fun fact

In 2016, an Uber engineer accidentally committed AWS credentials to a public GitHub repo. Hackers found them within minutes and accessed data of 57 million users. This is why .env files exist and why you NEVER commit secrets to Git!

Hands-on challenge

Create a .env file with DATABASE_URL, JWT_SECRET, and PORT. Set up ConfigModule with Joi validation that requires DATABASE_URL and JWT_SECRET. Add a .env.example and update .gitignore. What happens when you remove DATABASE_URL?

More resources

Open interactive version (quiz + challenge) ← Back to course: Full-Stack Playbook