feat: add Zod environment validation#2
Conversation
- Add src/config/env.validation.ts with typed Zod schema - Validate all env vars at app startup with clear error messages - Update ConfigModule to use validation and make it global - Update .env.example with all future env vars documented - Add zod dependency 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Test valid configurations with required and optional fields - Test default values for all fields with defaults - Test type coercion (PORT, REDIS_PORT string to number) - Test transforms (TYPEORM_LOGGING boolean, ALLOWED_ORIGINS array) - Test validation errors for missing/invalid required fields - Test NODE_ENV enum values - Test validateEnv error formatting 37 tests covering all Zod schema behavior 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
WalkthroughThis PR introduces environment validation infrastructure for the backend application. It adds a Zod-based validation schema for configuration variables, integrates validation into the application startup, updates the example environment file with new configuration entries, adds the zod dependency, and includes a comprehensive test suite. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (4)
apps/backend/src/config/index.ts (1)
1-1: Good use of barrel export for config utilitiesRe-exporting from
./env.validationviaindex.tsmatches the barrel-export convention and keeps config APIs centralized.Based on learnings, this pattern is exactly what we want for config modules.
apps/backend/.env.example (1)
1-43: Example env matches schema; consider reordering keys to satisfy dotenv-linterThe example file now exposes all keys required by
envSchema(includingFRONTEND_URLandALLOWED_ORIGINS), so it should pass validation out of the box, which is great.Static analysis only flags key ordering (e.g., IDP_ISSUER/IDP_AUDIENCE/AUTH0_* and REDIS_*). If dotenv-linter runs in CI, you may want to reorder those keys to clear the warnings; otherwise this is purely cosmetic.
apps/backend/src/config/env.validation.spec.ts (1)
1-323: Comprehensive env validation tests; only minor polish possibleThe suite does a good job exercising defaults, coercions, transforms, error aggregation, and type inference for
EnvConfig. This should give high confidence in the Zod schema andvalidateEnv.The only tiny nit is in the
"should throw Error with formatted message"test, wherevalidateEnv(invalidConfig)is invoked twice in separateexpectcalls; you could assign aconst call = () => validateEnv(invalidConfig);and reuse it to avoid duplicate work, but this is purely cosmetic.apps/backend/src/config/env.validation.ts (1)
1-76: Schema and validator design are strong; consider tightening types and a small edge caseThe Zod schema is well-structured (defaults, coercions, transforms) and lines up with both
.env.exampleand the tests.validateEnv’s aggregated error message with field paths is also very helpful for debugging.Two small refinements you might consider:
Return
EnvConfigfromvalidateEnv
Since you already exportEnvConfig = z.infer<typeof envSchema>, you could change the signature to:export function validateEnv(config: Record<string, unknown>): EnvConfig { const result = envSchema.safeParse(config); // ... return result.data; }This gives you end‑to‑end type safety wherever
validateEnvis consumed.Optional ALLOWED_ORIGINS edge case
With.transform((s) => s.split(',').map((origin) => origin.trim())), settingALLOWED_ORIGINSto an empty string would yield['']. If you want to avoid that, you could filter out empty entries or special‑case'*'vs non‑'*'values.These are polish-level improvements; behavior is already correct for the tested scenarios.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (6)
apps/backend/.env.example(1 hunks)apps/backend/package.json(1 hunks)apps/backend/src/app.module.ts(1 hunks)apps/backend/src/config/env.validation.spec.ts(1 hunks)apps/backend/src/config/env.validation.ts(1 hunks)apps/backend/src/config/index.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (10)
apps/backend/src/**/*.spec.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Include unit tests for all backend services using Jest
Files:
apps/backend/src/config/env.validation.spec.ts
**/*.{js,ts,tsx,jsx,css,scss,json,yaml,yml,md}
📄 CodeRabbit inference engine (.cursor/rules/cursor.json)
**/*.{js,ts,tsx,jsx,css,scss,json,yaml,yml,md}: Maximum line length should not exceed 100 characters
Trim trailing whitespace from all lines
Insert a final newline at the end of every file
Files:
apps/backend/src/config/env.validation.spec.tsapps/backend/package.jsonapps/backend/src/config/env.validation.tsapps/backend/src/config/index.tsapps/backend/src/app.module.ts
**/*.{js,ts,tsx,jsx,css,scss,json,yaml,yml}
📄 CodeRabbit inference engine (.cursor/rules/cursor.json)
Use 2-space indentation for all files
Files:
apps/backend/src/config/env.validation.spec.tsapps/backend/package.jsonapps/backend/src/config/env.validation.tsapps/backend/src/config/index.tsapps/backend/src/app.module.ts
**/*
📄 CodeRabbit inference engine (.cursor/rules/cursor.json)
**/*: Use LF (line feed) as end-of-line character
Use UTF-8 character encoding for all files
Files:
apps/backend/src/config/env.validation.spec.tsapps/backend/package.jsonapps/backend/src/config/env.validation.tsapps/backend/src/config/index.tsapps/backend/src/app.module.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/cursor.json)
**/*.{ts,tsx,js,jsx}: Use single quotes for string literals in TypeScript/JavaScript
Semicolons must be present at the end of statements in TypeScript/JavaScript
Use trailing commas in multiline constructs following ES5 convention
Enable bracket spacing in object literals (e.g., { foo: bar } instead of {foo: bar})
Omit parentheses around single arrow function parameters when possible
Files:
apps/backend/src/config/env.validation.spec.tsapps/backend/src/config/env.validation.tsapps/backend/src/config/index.tsapps/backend/src/app.module.ts
**/*.{ts,tsx,js,jsx,vue}
📄 CodeRabbit inference engine (.cursor/rules/etus-methodology.mdc)
**/*.{ts,tsx,js,jsx,vue}: All UI component imports must use@/components/ui/*from @ETUS Design System, fail with error if components are imported from outside this path
Do not create components outside @ETUS Design System
Files:
apps/backend/src/config/env.validation.spec.tsapps/backend/src/config/env.validation.tsapps/backend/src/config/index.tsapps/backend/src/app.module.ts
apps/backend/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/project-context.mdc)
All operations must include account context (accountId) for account isolation
Files:
apps/backend/src/config/env.validation.spec.tsapps/backend/src/config/env.validation.tsapps/backend/src/config/index.tsapps/backend/src/app.module.ts
apps/**/*.{vue,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/project-context.mdc)
Implement comprehensive error handling scenarios in Vue components and backend services
Files:
apps/backend/src/config/env.validation.spec.tsapps/backend/src/config/env.validation.tsapps/backend/src/config/index.tsapps/backend/src/app.module.ts
**/*.{ts,tsx,d.ts}
📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)
**/*.{ts,tsx,d.ts}: Prefer interfaces over types for object definitions
Use type for unions, intersections, and mapped types
Avoid usingany, preferunknownfor unknown types
Leverage TypeScript's built-in utility types
Use generics for reusable type patterns
Use PascalCase for type names and interfaces
Use camelCase for variables and functions
Use UPPER_CASE for constants
Use descriptive names with auxiliary verbs for boolean variables and functions (e.g., isLoading, hasError)
Prefix React component prop interfaces with 'Props' (e.g., ButtonProps)
Keep type definitions close to where they're used
Export types and interfaces from dedicated type files when shared
Use explicit return types for public functions
Use arrow functions for callbacks and methods
Implement proper error handling with custom error types
Use function overloads for complex type scenarios
Prefer async/await over Promises
Use readonly for immutable properties
Leverage discriminated unions for type safety
Use type guards for runtime type checking
Implement proper null checking
Avoid type assertions unless necessary
Create custom error types for domain-specific errors
Use Result types for operations that can fail
Use try-catch blocks with typed catch clauses
Handle Promise rejections properly
Files:
apps/backend/src/config/env.validation.spec.tsapps/backend/src/config/env.validation.tsapps/backend/src/config/index.tsapps/backend/src/app.module.ts
**/index.ts
📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)
Use barrel exports (index.ts) for organizing exports
Files:
apps/backend/src/config/index.ts
🧠 Learnings (7)
📚 Learning: 2025-11-28T18:13:02.933Z
Learnt from: CR
Repo: etusdigital/boilerplate PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-28T18:13:02.933Z
Learning: Applies to apps/backend/src/**/*.spec.ts : Include unit tests for all backend services using Jest
Applied to files:
apps/backend/src/config/env.validation.spec.ts
📚 Learning: 2025-11-28T18:13:02.933Z
Learnt from: CR
Repo: etusdigital/boilerplate PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-28T18:13:02.933Z
Learning: Applies to apps/backend/src/modules/**/dto/*.dto.ts : Define DTOs with validation decorators in dto/ subdirectory
Applied to files:
apps/backend/src/config/env.validation.tsapps/backend/src/app.module.ts
📚 Learning: 2025-11-28T18:13:02.933Z
Learnt from: CR
Repo: etusdigital/boilerplate PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-28T18:13:02.933Z
Learning: Applies to apps/backend/src/modules/**/*.controller.ts : Implement input validation with DTOs and class-validator in controllers
Applied to files:
apps/backend/src/config/env.validation.ts
📚 Learning: 2025-11-28T18:14:14.233Z
Learnt from: CR
Repo: etusdigital/boilerplate PR: 0
File: .cursor/rules/typescript.mdc:0-0
Timestamp: 2025-11-28T18:14:14.233Z
Learning: Applies to **/index.ts : Use barrel exports (index.ts) for organizing exports
Applied to files:
apps/backend/src/config/index.ts
📚 Learning: 2025-11-28T18:14:14.233Z
Learnt from: CR
Repo: etusdigital/boilerplate PR: 0
File: .cursor/rules/typescript.mdc:0-0
Timestamp: 2025-11-28T18:14:14.233Z
Learning: Applies to **/*.{ts,tsx,d.ts} : Export types and interfaces from dedicated type files when shared
Applied to files:
apps/backend/src/config/index.ts
📚 Learning: 2025-11-28T18:13:02.933Z
Learnt from: CR
Repo: etusdigital/boilerplate PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-28T18:13:02.933Z
Learning: Applies to apps/backend/src/modules/**/ : Organize backend modules in feature structure: [entity].module.ts, [entity].controller.ts, [entity].service.ts, [entity].spec.ts, dto/
Applied to files:
apps/backend/src/app.module.ts
📚 Learning: 2025-11-28T18:13:02.933Z
Learnt from: CR
Repo: etusdigital/boilerplate PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-28T18:13:02.933Z
Learning: Applies to apps/backend/src/modules/**/*.controller.ts : Use JwtAuthGuard on all protected controller endpoints
Applied to files:
apps/backend/src/app.module.ts
🧬 Code graph analysis (1)
apps/backend/src/app.module.ts (1)
apps/backend/src/config/env.validation.ts (1)
validateEnv(59-76)
🪛 dotenv-linter (4.0.0)
apps/backend/.env.example
[warning] 11-11: [UnorderedKey] The IDP_ISSUER key should go before the JWKS_URI key
(UnorderedKey)
[warning] 12-12: [UnorderedKey] The IDP_AUDIENCE key should go before the IDP_ISSUER key
(UnorderedKey)
[warning] 13-13: [UnorderedKey] The AUTH0_DOMAIN key should go before the IDP_AUDIENCE key
(UnorderedKey)
[warning] 14-14: [UnorderedKey] The AUTH0_CLIENT_ID key should go before the AUTH0_DOMAIN key
(UnorderedKey)
[warning] 15-15: [UnorderedKey] The AUTH0_CLIENT_SECRET key should go before the AUTH0_DOMAIN key
(UnorderedKey)
[warning] 16-16: [UnorderedKey] The AUTH0_ROLES_NAME key should go before the IDP_AUDIENCE key
(UnorderedKey)
[warning] 17-17: [UnorderedKey] The AUTH0_CONNECTION_ID key should go before the AUTH0_DOMAIN key
(UnorderedKey)
[warning] 18-18: [UnorderedKey] The AUTH0_CONNECTION_TYPE key should go before the AUTH0_DOMAIN key
(UnorderedKey)
[warning] 37-37: [UnorderedKey] The JWT_EXPIRES_IN key should go before the JWT_ISSUER key
(UnorderedKey)
[warning] 41-41: [UnorderedKey] The REDIS_HOST key should go before the REDIS_URL key
(UnorderedKey)
[warning] 42-42: [UnorderedKey] The REDIS_PORT key should go before the REDIS_URL key
(UnorderedKey)
[warning] 43-43: [UnorderedKey] The REDIS_PASSWORD key should go before the REDIS_PORT key
(UnorderedKey)
🔇 Additional comments (2)
apps/backend/package.json (1)
22-47: Zod dependency wiring looks correctAdding
"zod": "^4.1.13"to backend dependencies cleanly matches the new env validation module; no issues from a package/config perspective.apps/backend/src/app.module.ts (1)
13-20: Env validation integration into ConfigModule looks solidUsing
ConfigModule.forRoot({ validate: validateEnv, isGlobal: true })cleanly hooks the Zod schema into app bootstrap and centralizes config validation; no issues spotted here.
Summary
Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit
Chores
Tests
✏️ Tip: You can customize this high-level summary in your review settings.