Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config';
import { configValidationSchema } from './common/config/config.validation';
import { PrismaModule } from './prisma/prisma.module';
import { UserModule } from './user/user.module';
import { AuthModule } from './auth/auth.module';
Expand All @@ -18,6 +19,12 @@ import { QueueModule } from './queue/queue.module';
imports: [
ConfigModule.forRoot({
isGlobal: true,
validationSchema: configValidationSchema,
validationOptions: {
abortEarly: false, // report all errors at once
allowUnknown: true, // allow extra env vars
stripUnknown: false, // keep unknown vars
},
}),
ThrottlerModule.forRoot([
{
Expand Down
66 changes: 66 additions & 0 deletions backend/src/common/config/config.validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* Environment configuration validation schema using Joi.
*
* Required env vars: PORT, DATABASE_URL, JWT_SECRET
* Conditional: REDIS_URL (required unless QUEUE_DISABLED=true)
*
* Usage: import in AppModule and pass to ConfigModule.forRoot({ validationSchema })
*
* Install: npm install joi
*/
// eslint-disable-next-line @typescript-eslint/no-var-requires
const Joi = require('joi');

export const configValidationSchema = Joi.object({
// Server
PORT: Joi.number().default(3000).optional(),
NODE_ENV: Joi.string()
.valid('development', 'production', 'test')
.default('development'),

// Database (required)
DATABASE_URL: Joi.string().required().messages({
'any.required': 'Missing Configuration: DATABASE_URL',
'string.empty': 'Missing Configuration: DATABASE_URL',
}),

// Auth (required)
JWT_SECRET: Joi.string().min(16).required().messages({
'any.required': 'Missing Configuration: JWT_SECRET',
'string.empty': 'Missing Configuration: JWT_SECRET',
'string.min': 'JWT_SECRET must be at least 16 characters',
}),
JWT_EXPIRES_IN: Joi.string().default('1d').optional(),

// Redis / Queue (required unless queue disabled)
REDIS_URL: Joi.when('QUEUE_DISABLED', {
is: Joi.exist().valid('true', '1', 'true'),
then: Joi.string().optional(),
otherwise: Joi.string().required().messages({
'any.required': 'Missing Configuration: REDIS_URL (required when queue is enabled)',
}),
}),
QUEUE_DISABLED: Joi.string()
.valid('true', '1', 'false', '0')
.optional(),

// Storage
STORAGE_PROVIDER: Joi.string()
.valid('local', 's3', 'ipfs')
.default('local')
.optional(),
STORAGE_LOCAL_DIR: Joi.string().optional(),
AWS_ACCESS_KEY_ID: Joi.string().optional(),
AWS_SECRET_ACCESS_KEY: Joi.string().optional(),
AWS_S3_BUCKET: Joi.string().optional(),
AWS_REGION: Joi.string().optional(),

// Mail
SMTP_HOST: Joi.string().optional(),
SMTP_PORT: Joi.number().optional(),
SMTP_USER: Joi.string().optional(),
SMTP_PASS: Joi.string().optional(),

// App
FRONTEND_URL: Joi.string().optional(),
});