Important: You only need to import dotenv ONCE at the entry point of your application. Once dotenv.config() is called, all process.env variables are available globally throughout your entire application.
// Line 1-2
import dotenv from "dotenv";
dotenv.config();
// Then all other imports...
import express from "express";
import cookieParser from "cookie-parser";
// etc...Status: ✅ CORRECT - dotenv loaded FIRST
// Line 4-5
import dotenv from "dotenv";
dotenv.config();
// Then all other imports...
import mongoose from "mongoose";
import Groq from "groq-sdk";
// etc...Status: ✅ CORRECT - dotenv loaded FIRST
import Redis from 'ioredis';
// Uses process.env directly
const config = {
host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT || 6379,
password: process.env.REDIS_PASSWORD || undefined,
// ...
};Status: ✅ CORRECT - No dotenv import needed (already loaded by index.js)
import Queue from 'bull';
// Uses process.env directly
const REDIS_CONFIG = {
host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT || 6379,
password: process.env.REDIS_PASSWORD || undefined,
// ...
};Status: ✅ CORRECT - No dotenv import needed (already loaded by index.js)
import crypto from 'crypto';
import { getRedisClient } from '../config/redis.js';
// No process.env usage in this file
// All configuration comes from function parametersStatus: ✅ CORRECT - No env vars used directly
import nodemailer from 'nodemailer';
// Uses process.env directly
static getTransporter() {
const emailService = process.env.EMAIL_SERVICE || 'smtp';
// ...
pass: process.env.SENDGRID_API_KEY
// ...
}Status: ✅ CORRECT - No dotenv import needed (already loaded by index.js)
import { User } from "../models/user.model.js";
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import { OTPService } from '../services/otpService.js';
import { EmailService } from '../services/emailService.js';
// Uses process.env directly
const token = jwt.sign(
{ userId: user._id },
process.env.JWT_SECRET, // ✅ Available
{ expiresIn: '7d' }
);
const maxAttempts = parseInt(process.env.OTP_RATE_LIMIT_MAX) || 3; // ✅ Available
const expiryMinutes = parseInt(process.env.OTP_EXPIRY_MINUTES) || 10; // ✅ AvailableStatus: ✅ CORRECT - No dotenv import needed (already loaded by index.js)
| Variable | Used In | Default | Required |
|---|---|---|---|
EMAIL_SERVICE |
emailService.js | smtp |
✅ Yes |
EMAIL_FROM |
emailService.js | noreply@intervai.com |
✅ Yes |
EMAIL_FROM_NAME |
emailService.js | IntervAI Support |
No |
SENDGRID_API_KEY |
emailService.js | - | If using SendGrid |
SMTP_HOST |
emailService.js | smtp.gmail.com |
If using SMTP |
SMTP_PORT |
emailService.js | 587 |
If using SMTP |
SMTP_USER |
emailService.js | - | If using SMTP |
SMTP_PASS |
emailService.js | - | If using SMTP |
OTP_EXPIRY_MINUTES |
userController.js | 10 |
No |
OTP_MAX_ATTEMPTS |
userController.js | 3 |
No |
OTP_RATE_LIMIT_MAX |
userController.js | 3 |
No |
PASSWORD_MIN_LENGTH |
userController.js | 6 |
No |
REDIS_HOST |
redis.js, queue.js | localhost |
✅ Yes |
REDIS_PORT |
redis.js, queue.js | 6379 |
✅ Yes |
REDIS_PASSWORD |
redis.js, queue.js | - | ✅ Yes (Production) |
| Variable | Used In | Required |
|---|---|---|
JWT_SECRET |
userController.js, auth.middleware.js | ✅ Yes |
NODE_ENV |
index.js, multiple files | No |
PORT |
index.js | No |
MONGO_URI |
worker.js, db.js | ✅ Yes |
GROQ_API_KEY |
questionController.js, worker.js | ✅ Yes |
CLIENT_URL |
index.js | No |
# Start the API server
npm run dev
# Check console output - should see:
# ✅ Database connected
# ✅ Redis Connected
# ✅ Server running on http://localhost:8000Create a test endpoint temporarily:
// Add to index.js (for testing only)
app.get('/test-env', (req, res) => {
res.json({
EMAIL_SERVICE: process.env.EMAIL_SERVICE || 'not set',
OTP_EXPIRY_MINUTES: process.env.OTP_EXPIRY_MINUTES || 'not set',
REDIS_HOST: process.env.REDIS_HOST || 'not set',
hasJWT: !!process.env.JWT_SECRET,
hasMongoURI: !!process.env.MONGO_URI
});
});curl http://localhost:8000/test-envExpected Output:
{
"EMAIL_SERVICE": "gmail",
"OTP_EXPIRY_MINUTES": "10",
"REDIS_HOST": "redis",
"hasJWT": true,
"hasMongoURI": true
}docker-compose logs api | grep RedisExpected Output:
✅ Redis Connected
✅ Redis Ready
If you see errors like NOAUTH Authentication required, then REDIS_PASSWORD is not being read correctly.
Symptom: process.env.VARIABLE_NAME is undefined
Causes:
.envfile doesn't exist.envfile is in wrong location- Variable name typo in
.env - dotenv not imported in entry point
Solution:
# Check .env exists
ls -la .env
# Check .env location (should be in project root)
pwd
# Should show: /path/to/intervai
# Check .env content
cat .env | grep EMAIL_SERVICE
# Restart server
docker-compose down
docker-compose up -dSymptom: NOAUTH Authentication required
Check:
# 1. Check .env has password
cat .env | grep REDIS_PASSWORD
# 2. Check docker-compose.yml uses password
cat docker-compose.yml | grep REDIS_PASSWORD
# 3. Restart services
docker-compose down
docker-compose up -d
# 4. Test Redis connection
docker exec -it intervai-redis redis-cli
> AUTH your-password
OKSymptom: Failed to send email
Check:
# 1. Check email config in .env
cat .env | grep EMAIL
cat .env | grep SMTP
# 2. Check logs
docker-compose logs api | grep Email
# 3. Test SMTP connection
node -e "
const nodemailer = require('nodemailer');
require('dotenv').config();
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS
}
});
transporter.verify().then(() => console.log('✅ SMTP OK')).catch(console.error);
"Symptom: OTP expires too quickly or rate limiting not working
Check:
# 1. Check OTP config in .env
cat .env | grep OTP
# 2. Check if defaults are being used
docker-compose logs api | grep OTP
# 3. Verify in code
# If you see "expires in 10 minutes" but set OTP_EXPIRY_MINUTES=5,
# then env var is not being read-
index.jsimports dotenv at the top -
worker.jsimports dotenv at the top - dotenv.config() called before other imports
-
config/redis.jsuses process.env (no dotenv import needed) -
config/queue.jsuses process.env (no dotenv import needed) -
config/db.jsuses process.env (no dotenv import needed)
-
services/otpService.js- No env vars used directly -
services/emailService.jsuses process.env (no dotenv import needed) -
services/cacheService.jsuses process.env (no dotenv import needed)
-
controllers/userController.jsuses process.env (no dotenv import needed) - All other controllers use process.env (no dotenv import needed)
-
middlewares/auth.middleware.jsuses process.env (no dotenv import needed) - All other middlewares use process.env (no dotenv import needed)
┌─────────────────────────────────────────┐
│ index.js (Entry Point) │
│ ┌─────────────────────────────────┐ │
│ │ import dotenv from "dotenv" │ │
│ │ dotenv.config() │ │
│ │ ↓ │ │
│ │ process.env is now populated │ │
│ └─────────────────────────────────┘ │
│ │
│ ↓ imports │
│ │
│ ├─ config/redis.js │
│ │ └─ uses process.env.REDIS_HOST │
│ │ │
│ ├─ config/queue.js │
│ │ └─ uses process.env.REDIS_PORT │
│ │ │
│ ├─ routes/user.routes.js │
│ │ └─ imports controllers │
│ │ └─ controllers/userController.js│
│ │ └─ uses process.env.JWT_SECRET│
│ │ └─ imports services │
│ │ └─ services/emailService.js│
│ │ └─ uses process.env.EMAIL_*│
│ │ │
│ └─ All imports have access to │
│ process.env variables │
└─────────────────────────────────────────┘
Why it works:
dotenv.config()is called at the very top ofindex.jsandworker.js- This populates
process.envwith all variables from.envfile - All subsequent imports (config, services, controllers) can access
process.env - No need to import dotenv in every file
Best Practice:
- ✅ Import dotenv ONLY in entry points (index.js, worker.js)
- ✅ Call dotenv.config() BEFORE any other imports
- ✅ Use process.env directly in all other files
- ❌ Don't import dotenv in config/service/controller files
Current Implementation:
- ✅ Follows best practices
- ✅ All env vars are accessible
- ✅ No redundant imports
- ✅ Clean and maintainable
Verification Date: February 14, 2026
Status: ✅ ALL CORRECT
Action Required: None - Implementation is correct!