Skip to main content
This page documents all environment variables required for the Millennium Potters application. Configuration differs between local development and production environments.

Environment Files

FileLocationPurpose
.env.localbackend/Local development with PostgreSQL
.env.localfrontend/Local development frontend config
.env.productionbackend/Production with CockroachDB
Platform VariablesVercel/Render/PleskProduction frontend config
Never commit .env files to version control. Add them to .gitignore.

Backend Environment Variables

Database Configuration

# PostgreSQL connection for local development
DATABASE_URL="postgresql://postgres:YOUR_PASSWORD@localhost:5432/millenium_local?schema=public"
DIRECT_URL="postgresql://postgres:YOUR_PASSWORD@localhost:5432/millenium_local?schema=public"
DATABASE_URL
string
required
Primary database connection string. In production, this should include connection pooling parameters:
  • connection_limit=20 - Max connections (20-30 for 50-100 users)
  • pool_timeout=20 - Connection timeout in seconds
DIRECT_URL
string
required
Direct database connection for migrations. Do not include pooling parameters.

Server Configuration

PORT=5000
CORS_ORIGIN="http://localhost:3000"
NODE_ENV="development"  # or "production"
PORT
number
default:"5000"
Port the backend server listens on.
CORS_ORIGIN
string
required
Allowed frontend origin for CORS. In production, set to your frontend URL (e.g., https://app.millenniumpotters.com).
NODE_ENV
string
default:"development"
Environment mode. Set to production for production deployments.

Authentication

JWT_SECRET="your-super-secret-jwt-key-min-32-chars"
JWT_EXPIRES_IN="7d"
REFRESH_TOKEN_EXPIRES_IN="30d"

# Supabase (optional, for OAuth)
NEXT_PUBLIC_SUPABASE_URL="https://your-project.supabase.co"
NEXT_PUBLIC_SUPABASE_ANON_KEY="your-anon-key"
JWT_SECRET
string
required
Secret key for signing JWT tokens. Must be at least 32 characters. Generate with:
openssl rand -base64 32
JWT_EXPIRES_IN
string
default:"7d"
Access token expiration time (e.g., 1h, 7d, 30d).
REFRESH_TOKEN_EXPIRES_IN
string
default:"30d"
Refresh token expiration time.

Cloud Storage (Cloudinary)

CLOUDINARY_CLOUD_NAME="your-cloud-name"
CLOUDINARY_API_KEY="123456789012345"
CLOUDINARY_API_SECRET="your-api-secret"
CLOUDINARY_UPLOAD_PRESET="your-preset"  # optional
CLOUDINARY_CLOUD_NAME
string
required
Your Cloudinary cloud name from the dashboard.
CLOUDINARY_API_KEY
string
required
Cloudinary API key for authenticated uploads.
CLOUDINARY_API_SECRET
string
required
Cloudinary API secret.

Email Configuration (Optional)

EMAIL_HOST="smtp.gmail.com"
EMAIL_PORT=587
EMAIL_USER="[email protected]"
EMAIL_PASSWORD="your-app-password"
EMAIL_FROM="Millennium Potters <[email protected]>"
EMAIL_HOST
string
SMTP server hostname (e.g., smtp.gmail.com, smtp.sendgrid.net).
EMAIL_PORT
number
default:"587"
SMTP port (587 for TLS, 465 for SSL).
EMAIL_USER
string
SMTP username (usually your email address).
EMAIL_PASSWORD
string
SMTP password or app-specific password.
EMAIL_FROM
string
Sender name and email in format: Name <[email protected]>.

Frontend Environment Variables

NEXT_PUBLIC_API_URL=http://localhost:5000/api
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
NEXT_PUBLIC_API_URL
string
required
Backend API base URL. Must include /api suffix.
In production, ensure this points to your deployed backend HTTPS URL.
NEXT_PUBLIC_SUPABASE_URL
string
required
Supabase project URL from your project settings.
NEXT_PUBLIC_SUPABASE_ANON_KEY
string
required
Supabase anonymous key (safe to expose in frontend).

Complete Environment File Examples

Backend .env.local (Development)

# Database
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/millenium_local?schema=public"
DIRECT_URL="postgresql://postgres:postgres@localhost:5432/millenium_local?schema=public"

# Server
PORT=5000
CORS_ORIGIN="http://localhost:3000"
NODE_ENV="development"

# JWT
JWT_SECRET="dev-secret-key-change-in-production-min-32-characters"
JWT_EXPIRES_IN="7d"
REFRESH_TOKEN_EXPIRES_IN="30d"

# Cloudinary
CLOUDINARY_CLOUD_NAME="your-cloud"
CLOUDINARY_API_KEY="123456789012345"
CLOUDINARY_API_SECRET="your-secret"

# Email (optional)
EMAIL_HOST="smtp.gmail.com"
EMAIL_PORT=587
EMAIL_USER="[email protected]"
EMAIL_PASSWORD="your-app-password"
EMAIL_FROM="Dev Server <[email protected]>"

Frontend .env.local (Development)

NEXT_PUBLIC_API_URL=http://localhost:5000/api
NEXT_PUBLIC_SUPABASE_URL=https://abcdefghijklmnop.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Environment Variable Validation

The backend validates required environment variables on startup. Missing variables will cause the server to fail with clear error messages.

Testing Your Configuration

1

Backend health check

curl http://localhost:5000/health
Expected response:
{
  "status": "ok",
  "timestamp": "2026-03-11T10:00:00.000Z",
  "database": "connected"
}
2

Frontend connection test

Open http://localhost:3000 and check browser console for errors.Look for:
  • No CORS errors
  • Successful API connection messages
  • Supabase client initialization

Production Deployment

Platform-Specific Setup

Add environment variables in Project Settings → Environment Variables:
  1. Go to your project dashboard
  2. Click “Settings” → “Environment Variables”
  3. Add each variable from the frontend configuration
  4. Select “Production” environment
  5. Click “Save”
Vercel automatically prefixes exposed variables with NEXT_PUBLIC_.

Security Best Practices

  • Change JWT_SECRET every 90 days
  • Rotate database passwords quarterly
  • Update API keys if compromised
  • JWT secrets: minimum 32 characters
  • Database passwords: 16+ characters with mixed case, numbers, symbols
  • Never use default or example values in production
  • Use read-only database users where possible
  • Restrict Cloudinary upload presets
  • Enable IP whitelisting on CockroachDB
  • Scan repositories for exposed secrets
  • Review server logs for credential exposure
  • Enable GitHub secret scanning

Troubleshooting

Symptoms: Error: connect ECONNREFUSED or timeout errorsSolutions:
  • Verify DATABASE_URL format is correct
  • Check PostgreSQL is running: sudo systemctl status postgresql
  • Confirm database exists: psql -l
  • Test connection: psql "$DATABASE_URL"
Symptoms: Access-Control-Allow-Origin errorsSolutions:
  • Ensure CORS_ORIGIN matches your frontend URL exactly
  • Include protocol (http:// or https://)
  • Remove trailing slashes
  • Restart backend after changing env vars
Symptoms: 401 Unauthorized or token validation failsSolutions:
  • Verify JWT_SECRET is at least 32 characters
  • Check secret matches between environments
  • Clear browser localStorage/cookies
  • Generate new secret: openssl rand -base64 32
Symptoms: Document uploads return 401 or 403Solutions:
  • Verify all three Cloudinary variables are set
  • Check API key and secret for typos
  • Test credentials in Cloudinary dashboard
  • Ensure cloud name is correct (no URL, just the name)

Next Steps

Database Setup

Initialize schema and run migrations

Production Deployment

Deploy to production environment

Build docs developers (and LLMs) love