From f47028f91c9cbb23f7ea37d05b183b11f07fea6d Mon Sep 17 00:00:00 2001 From: Shreenath Tambe Date: Wed, 11 Feb 2026 15:50:48 +0530 Subject: [PATCH 1/2] feat: add environment variable validation for backend --- backend/.env.example | 18 ++++-------- backend/package-lock.json | 12 +++++++- backend/package.json | 10 +++++-- backend/src/config/env.ts | 43 +++++++++++++++++++++++++++ backend/src/config/index.ts | 58 +++++++------------------------------ 5 files changed, 78 insertions(+), 63 deletions(-) create mode 100644 backend/src/config/env.ts diff --git a/backend/.env.example b/backend/.env.example index 8fa3f1d..0b99aae 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,21 +1,15 @@ -# Backend Environment Variables -# Copy this to .env and fill in your actual values - -# Supabase Configuration (get from https://app.supabase.com → Project Settings → API) +# Supabase Configuration SUPABASE_URL=https://your-project-id.supabase.co -SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... -SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +SUPABASE_ANON_KEY=your-anon-key +SUPABASE_SERVICE_ROLE_KEY=your-service-role-key -# HuggingFace API Token (optional - get from https://huggingface.co/settings/tokens) -# If not provided, crisis detection will use keyword-based fallback +# HuggingFace API Token (Optional) HUGGINGFACE_API_TOKEN=hf_YourTokenHere -# Frontend URL (for CORS whitelist) -FRONTEND_URL=http://localhost:3000 - # Server Configuration PORT=3001 +FRONTEND_URL=http://localhost:3000 -# Rate Limiting (optional - defaults shown) +# Rate Limiting RATE_LIMIT_WINDOW_MS=900000 RATE_LIMIT_MAX_REQUESTS=100 diff --git a/backend/package-lock.json b/backend/package-lock.json index 8e7a659..849b461 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -15,7 +15,8 @@ "express": "^4.18.2", "express-rate-limit": "^7.1.5", "helmet": "^7.1.0", - "ws": "^8.16.0" + "ws": "^8.16.0", + "zod": "^4.3.6" }, "devDependencies": { "@types/cors": "^2.8.17", @@ -1900,6 +1901,15 @@ "engines": { "node": ">=6" } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/backend/package.json b/backend/package.json index 1c6f262..652deb1 100644 --- a/backend/package.json +++ b/backend/package.json @@ -9,7 +9,12 @@ "start": "node dist/index.js", "setup-db": "ts-node src/scripts/setupDatabase.ts" }, - "keywords": ["mental-health", "websocket", "express", "crisis-detection"], + "keywords": [ + "mental-health", + "websocket", + "express", + "crisis-detection" + ], "author": "OpenMindWell Contributors", "license": "MIT", "dependencies": { @@ -19,7 +24,8 @@ "express": "^4.18.2", "express-rate-limit": "^7.1.5", "helmet": "^7.1.0", - "ws": "^8.16.0" + "ws": "^8.16.0", + "zod": "^4.3.6" }, "devDependencies": { "@types/cors": "^2.8.17", diff --git a/backend/src/config/env.ts b/backend/src/config/env.ts new file mode 100644 index 0000000..d0ed16d --- /dev/null +++ b/backend/src/config/env.ts @@ -0,0 +1,43 @@ +import { z } from 'zod'; +import dotenv from 'dotenv'; + +dotenv.config(); + +const envSchema = z.object({ + // Supabase Configuration + SUPABASE_URL: z.string().url(), + SUPABASE_ANON_KEY: z.string().min(1), + SUPABASE_SERVICE_ROLE_KEY: z.string().min(1), + + // HuggingFace API Token (Optional) + HUGGINGFACE_API_TOKEN: z.string().optional(), + + // Server Configuration + FRONTEND_URL: z.string().url().default('http://localhost:3000'), + PORT: z.coerce.number().default(3001), + + // Rate Limiting + RATE_LIMIT_WINDOW_MS: z.coerce.number().default(900000), + RATE_LIMIT_MAX_REQUESTS: z.coerce.number().default(100), +}); + +const formatErrors = (errors: z.ZodFormattedError, string>) => { + return Object.entries(errors) + .map(([name, value]) => { + if (value && '_errors' in value) { + return `${name}: ${value._errors.join(', ')}`; + } + return null; + }) + .filter(Boolean); +}; + +const _env = envSchema.safeParse(process.env); + +if (!_env.success) { + console.error('Invalid environment variables:'); + console.error(JSON.stringify(_env.error.format(), null, 4)); + process.exit(1); +} + +export const env = _env.data; diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 3337f48..ac5d48f 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -1,60 +1,22 @@ -import dotenv from 'dotenv'; +import { env } from './env'; -dotenv.config(); - -interface Config { +const config = { supabase: { - url: string; - anonKey: string; - serviceRoleKey: string; - }; - huggingface: { - apiToken?: string; - }; - server: { - port: number; - frontendUrl: string; - }; - rateLimit: { - windowMs: number; - maxRequests: number; - }; -} - -const config: Config = { - supabase: { - url: process.env.SUPABASE_URL || '', - anonKey: process.env.SUPABASE_ANON_KEY || '', - serviceRoleKey: process.env.SUPABASE_SERVICE_ROLE_KEY || '', + url: env.SUPABASE_URL, + anonKey: env.SUPABASE_ANON_KEY, + serviceRoleKey: env.SUPABASE_SERVICE_ROLE_KEY, }, huggingface: { - apiToken: process.env.HUGGINGFACE_API_TOKEN, + apiToken: env.HUGGINGFACE_API_TOKEN, }, server: { - port: parseInt(process.env.PORT || '3001', 10), - frontendUrl: process.env.FRONTEND_URL || 'http://localhost:3000', + port: env.PORT, + frontendUrl: env.FRONTEND_URL, }, rateLimit: { - windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS || '900000', 10), - maxRequests: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS || '100', 10), + windowMs: env.RATE_LIMIT_WINDOW_MS, + maxRequests: env.RATE_LIMIT_MAX_REQUESTS, }, }; -// Validation -const requiredEnvVars = [ - 'SUPABASE_URL', - 'SUPABASE_ANON_KEY', - 'SUPABASE_SERVICE_ROLE_KEY', - 'FRONTEND_URL', -]; - -const missingVars = requiredEnvVars.filter((varName) => !process.env[varName]); - -if (missingVars.length > 0) { - throw new Error( - `Missing required environment variables: ${missingVars.join(', ')}\n` + - 'Please check your .env file and ensure all required variables are set.' - ); -} - export default config; From b43ac7cdb416e169cddc665cc8700e6c558b3303 Mon Sep 17 00:00:00 2001 From: Ayush Sharma Date: Tue, 17 Feb 2026 21:22:54 +0530 Subject: [PATCH 2/2] Update backend/src/config/env.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- backend/src/config/env.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/backend/src/config/env.ts b/backend/src/config/env.ts index d0ed16d..0f56a99 100644 --- a/backend/src/config/env.ts +++ b/backend/src/config/env.ts @@ -21,17 +21,6 @@ const envSchema = z.object({ RATE_LIMIT_MAX_REQUESTS: z.coerce.number().default(100), }); -const formatErrors = (errors: z.ZodFormattedError, string>) => { - return Object.entries(errors) - .map(([name, value]) => { - if (value && '_errors' in value) { - return `${name}: ${value._errors.join(', ')}`; - } - return null; - }) - .filter(Boolean); -}; - const _env = envSchema.safeParse(process.env); if (!_env.success) {