Environment Variables
All configuration for CivicPulse is managed through environment variables. This page documents every variable for both the backend and frontend.
Backend Variablesβ
The backend reads from a .env file in the backend/ directory (or from the process environment in production). Variables marked Required will cause the server to exit with an error if not set.
Serverβ
| Variable | Required | Default | Description | Example |
|---|---|---|---|---|
PORT | No | 3001 | Port the Express server listens on | 3001 |
NODE_ENV | No | development | Runtime environment; affects logging verbosity and error detail | production |
Authenticationβ
| Variable | Required | Default | Description | Example |
|---|---|---|---|---|
JWT_SECRET | Yes | β | Secret key for signing and verifying access tokens (JWT HS256). Must be at least 32 characters. Recommend 64-character hex string. | a3f9b2c1... |
JWT_REFRESH_SECRET | Yes | β | Separate secret for signing refresh tokens. Must differ from JWT_SECRET. | d7e4a1b8... |
GOOGLE_CLIENT_ID | Yes | β | Google OAuth 2.0 client ID for verifying ID tokens from the frontend | 1234567890-abc.apps.googleusercontent.com |
Generate strong secrets with:
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
AWSβ
| Variable | Required | Default | Description | Example |
|---|---|---|---|---|
AWS_REGION | Yes | β | AWS region where DynamoDB and S3 resources are provisioned | ap-south-1 |
AWS_ACCESS_KEY_ID | No* | β | AWS access key. Not required if using IAM instance roles on EC2 | AKIAIOSFODNN7EXAMPLE |
AWS_SECRET_ACCESS_KEY | No* | β | AWS secret key. Not required if using IAM instance roles | wJalrXUtnFEMI/K7MDENG/... |
DYNAMO_TABLE | Yes | β | DynamoDB table name | civicpulse-issues |
S3_BUCKET | Yes | β | S3 bucket name for media uploads | civicpulse-media |
*On EC2 with an IAM instance role attached, AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are not needed β the AWS SDK automatically uses the instance role credentials.
CORSβ
| Variable | Required | Default | Description | Example |
|---|---|---|---|---|
CORS_ORIGIN | Yes | β | Comma-separated list of allowed origins for CORS. In production this should be your frontend domain only. | https://test.civicpulseindia.com or http://localhost:5174,https://test.civicpulseindia.com |
Multiple origins:
CORS_ORIGIN=https://test.civicpulseindia.com,https://staging.civicpulse.in
Rate Limitingβ
| Variable | Required | Default | Description | Example |
|---|---|---|---|---|
RATE_LIMIT_WINDOW_MS | No | 900000 | Rate limit window in milliseconds. Default is 15 minutes. | 900000 |
RATE_LIMIT_MAX | No | 100 | Max requests per IP per window (general endpoints). Auth endpoints use a separate, lower limit of 20. | 100 |
Admin Portalβ
| Variable | Required | Default | Description | Example |
|---|---|---|---|---|
ADMIN_JWT_SECRET | Yes | β | Separate secret for signing admin JWTs. Must be distinct from JWT_SECRET. Min 32 chars. | e1f2a3b4... |
DYNAMODB_ADMINS_TABLE | No | civicpulse-admins | DynamoDB table for admin accounts | civicpulse-admins |
DYNAMODB_SITE_CONFIG_TABLE | No | civicpulse-site-config | DynamoDB table for feature flags and site config | civicpulse-site-config |
DYNAMODB_DONORS_TABLE | No | civicpulse-donors | DynamoDB table for donor wall entries | civicpulse-donors |
X / Twitter Integrationβ
| Variable | Required | Default | Description | Example |
|---|---|---|---|---|
X_BEARER_TOKEN | No | β | X (Twitter) API v2 Bearer Token. Used to fetch live civic-issue mentions for the Social Wall. Without this, the app runs in demo mode with mock mentions. Requires Basic access or higher for search/recent. | AAAA... |
X_HANDLE | No | civicpulse16094 | X handle to monitor for civic-issue mentions (without the @). Override to use your own account. Must match VITE_X_HANDLE in the frontend. | civicpulseindia |
TWITTER_BEARER_TOKEN | No | β | Legacy name β Twitter API v2 Bearer Token for fetching donor profile pictures. Without this, donor profile images will not be fetched. | AAAA... |
WhatsApp Business Cloud APIβ
| Variable | Required | Default | Description | Example |
|---|---|---|---|---|
WHATSAPP_TOKEN | No | β | Meta access token for the WhatsApp Business Cloud API. Without this, the Social Wall WhatsApp tab runs in demo mode with mock messages. Obtain from Meta Developer portal β WhatsApp β API Setup. | EAABz... |
WHATSAPP_VERIFY_TOKEN | No | civicpulse_wa_verify | Secret string used to verify the webhook URL with Meta. Choose any string β it must match what you enter in the Meta Developer portal when registering your webhook. | my_secret_token |
WHATSAPP_PHONE_NUMBER_ID | No | demo_phone | WhatsApp Phone Number ID from Meta Business Suite β WhatsApp β API Setup. Used to identify your business number in API requests. | 123456789012345 |
Instagram Graph APIβ
| Variable | Required | Default | Description | Example |
|---|---|---|---|---|
IG_ACCESS_TOKEN | No | β | Long-lived user access token for the Instagram Graph API. Without this, the Social Wall Instagram tab runs in demo mode with mock mentions. Obtain from Meta Developer portal β Instagram Graph API β Token Generation. | IGQVJWZAk... |
IG_USER_ID | No | β | Instagram Business account user ID. Required alongside IG_ACCESS_TOKEN for live mode. Find via GET /me?fields=id,username with your token. | 17841400... |
IG_HANDLE | No | civicpulse | Instagram handle to display in the Social Wall (without the @). This is display-only β the API uses IG_USER_ID for fetching. | civicpulseindia |
Push Notificationsβ
| Variable | Required | Default | Description | Example |
|---|---|---|---|---|
VAPID_PUBLIC_KEY | Yes (if push enabled) | β | VAPID public key for Web Push. Generate with web-push generate-vapid-keys. | BFq3... |
VAPID_PRIVATE_KEY | Yes (if push enabled) | β | VAPID private key. Never expose this to the client. | abc1... |
VAPID_MAILTO | Yes (if push enabled) | β | Contact email included in VAPID header | mailto:admin@civicpulse.in |
DYNAMODB_PUSH_SUBS_TABLE | No | civicpulse-push-subscriptions | DynamoDB table for push subscription endpoints | civicpulse-push-subscriptions |
Seeding Superadmin (One-time Script)β
These variables are only used when running node backend/scripts/seed-superadmin.js:
| Variable | Required | Description |
|---|---|---|
SUPERADMIN_EMAIL | Yes | Email address for the initial superadmin |
SUPERADMIN_PASSWORD | Yes | Password (min 12 chars, upper+lower+digit) |
SUPERADMIN_NAME | Yes | Display name for the superadmin |
Complete Backend .env Exampleβ
# Server
PORT=3001
NODE_ENV=production
# Auth
JWT_SECRET=a3f9b2c1d4e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2
JWT_REFRESH_SECRET=d7e4a1b8c3f2e5d4a9b8c7f6e5d4a3b2c1f0e9d8a7b6c5d4f3e2a1b0c9d8e7f6
GOOGLE_CLIENT_ID=123456789012-abcdefghijklmnop.apps.googleusercontent.com
# Admin Portal
ADMIN_JWT_SECRET=e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2
# AWS
AWS_REGION=ap-south-1
AWS_ACCESS_KEY_ID=<your-access-key>
AWS_SECRET_ACCESS_KEY=<your-secret-key>
# DynamoDB β dev tables (civicpulse-dev-*) or prod tables (civicpulse-*)
DYNAMO_TABLE=civicpulse-dev-issues
DYNAMODB_ADMINS_TABLE=civicpulse-dev-admins
DYNAMODB_SITE_CONFIG_TABLE=civicpulse-dev-site-config
DYNAMODB_DONORS_TABLE=civicpulse-dev-donors
DYNAMODB_PUSH_SUBS_TABLE=civicpulse-dev-push-subscriptions
# S3
S3_BUCKET=civicpulse-dev-media
# Twitter API (optional β for donor profile pictures)
TWITTER_BEARER_TOKEN=AAAA...your-bearer-token
# Web Push (VAPID)
VAPID_PUBLIC_KEY=BFq3...your-public-key
VAPID_PRIVATE_KEY=abc1...your-private-key
VAPID_MAILTO=mailto:admin@civicpulse.in
# CORS
CORS_ORIGIN=https://test.civicpulseindia.com
# Rate Limiting
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX=100
Frontend Variablesβ
Vite exposes only variables prefixed with VITE_ to the browser bundle. Never put secrets in frontend environment variables β they are visible to all users in the built JavaScript.
| Variable | Required | Default | Description | Example |
|---|---|---|---|---|
VITE_API_URL | Yes | β | Full URL of the backend REST API including /api path | http://localhost:3001/api |
VITE_WS_URL | Yes | β | Base URL for the Socket.IO WebSocket connection (no /api suffix) | http://localhost:3001 |
VITE_APP_ENV | No | development | Environment name for build tagging (see Build Naming below). Also injected as __APP_ENV__ global in the bundle. | production |
VITE_GOOGLE_CLIENT_ID | No | β | Google OAuth client ID β enables Google sign-in on the admin login page. Without this, only email/password login is shown. | 1234567890-abc.apps.googleusercontent.com |
VITE_GA_MEASUREMENT_ID | No | β | Google Analytics 4 Measurement ID. Leave unset in development to avoid polluting analytics. | G-XXXXXXXXXX |
VITE_VAPID_PUBLIC_KEY | No | β | VAPID public key β same value as backend VAPID_PUBLIC_KEY. Required for push notifications. | BFq3... |
VITE_X_HANDLE | No | civicpulse16094 | X handle displayed in the /test-x page and Social Wall (without the @). Should match X_HANDLE in backend. | civicpulseindia |
VITE_WA_CHANNEL_NAME | No | Mumbai Civic Sense | Display name for your WhatsApp channel shown in the Social Wall WhatsApp tab and the /test-whatsapp page. No token needed β this is display-only. | Bangalore Civic Watch |
Build Naming (VITE_APP_ENV)β
The VITE_APP_ENV variable names your builds and git release tags. It is read by both Vite (__APP_ENV__ global) and the increment-build.js prebuild script which writes it to build.json and creates a build/<env>/<N> git tag.
# Development (default) β git tag: build/development/3
npm run build
# Staging β git tag: build/staging/3
npm run build:staging
# Production β git tag: build/production/3
npm run build:production
# Or inline (Unix):
VITE_APP_ENV=production npm run build
The prebuild:staging / prebuild:production lifecycle hooks in package.json pass --env <name> to the script automatically, so no manual VITE_APP_ENV export is needed when using the named scripts.
Complete Frontend .env Example (development)β
VITE_API_URL=http://localhost:3001/api
VITE_WS_URL=http://localhost:3001
# GA and push not set in dev β intentional
Complete Frontend .env Example (production β test env)β
# For test.civicpulseindia.com β API is on same domain, no separate subdomain
VITE_API_URL=https://test.civicpulseindia.com/api
VITE_WS_URL=https://test.civicpulseindia.com
VITE_GA_MEASUREMENT_ID=G-H9SKTTDTRD
VITE_VAPID_PUBLIC_KEY=BFq3...your-public-key
In the test environment the frontend is built with VITE_GA_MEASUREMENT_ID baked in at build time inside setup-ec2.sh. There is no separate frontend/.env.production file on the server β the variable is passed directly to npm run build.
Environment Variable Loading Orderβ
Backend (development)β
The backend uses the dotenv package to load .env at startup:
import 'dotenv/config'; // loads backend/.env
Values already present in process.env (set by the shell or OS) take precedence over .env values.
Frontend (Vite)β
Vite automatically loads environment variables from the following files in priority order (highest first):
.env.local # local overrides β gitignored
.env.production # production-only vars
.env.development # development-only vars
.env # base vars β committed to repo (no secrets!)
Security Notesβ
- Never commit
.envfiles containing real secrets to version control. Ensure.envis in.gitignore. - Rotate
JWT_SECRETandJWT_REFRESH_SECRETimmediately if they are ever exposed. Rotating these secrets invalidates all active sessions. - In production, prefer injecting secrets via your deployment platform's secret management (AWS Secrets Manager, Vercel Environment Variables, GitHub Actions Secrets) rather than
.envfiles on disk. - The
.env.examplefile in each package directory documents all variables with placeholder values and is safe to commit.