Skip to main content

Configuration

Sunschool is highly configurable through environment variables and feature flags. This guide covers all configuration options.

Environment Variables

All configuration is managed through environment variables in the .env file.

Core Settings

DATABASE_URL
string
required
PostgreSQL connection string
DATABASE_URL="postgresql://user:password@host:port/database"
Format: postgresql://[user]:[password]@[host]:[port]/[database]Examples:
  • Local: postgresql://postgres:postgres@localhost:5432/sunschool
  • Neon: postgresql://user:pass@ep-xyz.us-east-2.aws.neon.tech/sunschool?sslmode=require
  • Railway: Automatically provided via $DATABASE_URL
DATABASE_SSL
boolean
default:"true"
Enable SSL for database connections
DATABASE_SSL=true
Set to false for local development without SSL.
PORT
number
default:"5000"
Port for the Express server
PORT=5000
Most hosting platforms provide $PORT automatically.
NODE_ENV
string
default:"development"
Environment mode
NODE_ENV=production
Values: development, production

Authentication

JWT_SECRET
string
required
Secret key for signing JWT tokens
JWT_SECRET="your-jwt-secret-key"
Generate securely:
openssl rand -base64 32
From server/config/env.ts:41:
export const JWT_SECRET = getEnv('JWT_SECRET', SESSION_SECRET);
JWT_EXPIRY
string
default:"7d"
JWT token expiration time
JWT_EXPIRY=7d
Format: [number][unit] where unit is s, m, h, dExamples: 30m, 12h, 7d, 90d
SESSION_SECRET
string
required
Secret key for session management
SESSION_SECRET="your-session-secret"
Generate securely:
openssl rand -base64 32

AI Provider Configuration

Sunschool supports multiple AI providers with automatic fallback chains.

OpenRouter (Primary)

OPENROUTER_API_KEY
string
required
API key for OpenRouter
OPENROUTER_API_KEY="sk-or-v1-..."
Get your key: openrouter.ai/keysFrom server/config/env.ts:48:
export const OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY || '';
Used for:
  • Lesson content generation
  • Quiz question creation
  • SVG illustration generation
LLM_PROVIDER
string
default:"openrouter"
Primary LLM provider
LLM_PROVIDER=openrouter
Options: openrouter, bittensor, perplexityFrom server/config/env.ts:45:
export const LLM_PROVIDER = process.env.LLM_PROVIDER || 'openrouter';

Bittensor (Experimental)

Bittensor Subnet 1 integration is experimental. OpenRouter is recommended for production.
ENABLE_BITTENSOR_SUBNET_1
boolean
default:"false"
Enable Bittensor Subnet 1 provider
ENABLE_BITTENSOR_SUBNET_1=1
From server/config/flags.ts:12:
export const ENABLE_BITTENSOR_SUBNET_1 = process.env.ENABLE_BITTENSOR_SUBNET_1 === '1';
BITTENSOR_API_KEY
string
API key for Bittensor Archive
BITTENSOR_API_KEY="your-bittensor-key"
BITTENSOR_SUBNET_1_URL
string
default:"https://archive.opentensor.ai/graphql"
GraphQL endpoint for Bittensor Subnet 1
BITTENSOR_SUBNET_1_URL="https://archive.opentensor.ai/graphql"
From server/config/env.ts:52:
export const BITTENSOR_SUBNET_1_URL = process.env.BITTENSOR_SUBNET_1_URL || 'https://archive.opentensor.ai/graphql';
BITTENSOR_WALLET_NAME
string
Bittensor wallet name for authentication
BITTENSOR_WALLET_NAME="your-wallet-name"
BITTENSOR_WALLET_HOTKEY
string
Bittensor wallet hotkey
BITTENSOR_WALLET_HOTKEY="your-hotkey"
BITTENSOR_FALLBACK_ENABLED
boolean
default:"true"
Enable automatic fallback to OpenRouter when Bittensor fails
BITTENSOR_FALLBACK_ENABLED=1
From server/config/flags.ts:13:
export const BITTENSOR_FALLBACK_ENABLED = process.env.BITTENSOR_FALLBACK_ENABLED !== '0';

Perplexity

PERPLEXITY_API_KEY
string
API key for Perplexity AI
PERPLEXITY_API_KEY="pplx-..."
Get your key: perplexity.ai/settings/apiFrom server/config/env.ts:57:
export const PERPLEXITY_API_KEY = process.env.PERPLEXITY_API_KEY || '';

Image Generation

Provider Selection

IMAGE_PROVIDER
string
default:"svg-llm"
Image generation provider
IMAGE_PROVIDER=svg-llm
Options:
  • svg-llm: LLM-generated SVG graphics (recommended)
  • openrouter: Raster images via OpenRouter
  • stability: Stability AI models
From server/config/env.ts:60:
export const IMAGE_PROVIDER = process.env.IMAGE_PROVIDER || 'svg-llm';

OpenRouter Image Models

OPENROUTER_IMAGE_MODEL
string
default:"google/gemini-3.1-pro-preview"
Primary model for image generation
OPENROUTER_IMAGE_MODEL="google/gemini-3.1-pro-preview"
From server/config/env.ts:61:
export const OPENROUTER_IMAGE_MODEL = process.env.OPENROUTER_IMAGE_MODEL || 'google/gemini-3.1-pro-preview';
OPENROUTER_SVG_MODEL
string
default:"google/gemini-3.1-pro-preview"
Primary model for SVG generation
OPENROUTER_SVG_MODEL="google/gemini-3.1-pro-preview"
From server/config/env.ts:62:
export const OPENROUTER_SVG_MODEL = process.env.OPENROUTER_SVG_MODEL || 'google/gemini-3.1-pro-preview';
IMAGE_MODEL_FALLBACKS
string
Comma-separated list of fallback models for image generation
IMAGE_MODEL_FALLBACKS="google/gemini-3.1-flash-image-preview,google/gemini-3-flash-preview"
Tried in order when the primary model returns 404 or fails.From server/config/env.ts:69-70:
export const IMAGE_MODEL_FALLBACKS: string[] = (process.env.IMAGE_MODEL_FALLBACKS || '')
  .split(',').map(s => s.trim()).filter(Boolean);
SVG_MODEL_FALLBACKS
string
Comma-separated list of fallback models for SVG generation
SVG_MODEL_FALLBACKS="google/gemini-3.1-flash-lite-preview,google/gemini-3-flash-preview"
From server/config/env.ts:67-68:
export const SVG_MODEL_FALLBACKS: string[] = (process.env.SVG_MODEL_FALLBACKS || '')
  .split(',').map(s => s.trim()).filter(Boolean);
Default fallbacks from server/config/env.ts:73-76:
export const DEFAULT_SVG_MODEL_FALLBACKS = [
  'google/gemini-3.1-flash-lite-preview',
  'google/gemini-3-flash-preview',
];

Image Generation Limits

IMAGE_GENERATION_TIMEOUT
number
default:"15000"
Timeout for image generation in milliseconds
IMAGE_GENERATION_TIMEOUT=15000
From server/config/env.ts:63:
export const IMAGE_GENERATION_TIMEOUT = parseInt(process.env.IMAGE_GENERATION_TIMEOUT || '15000');
MAX_IMAGES_PER_LESSON
number
default:"4"
Maximum number of images to generate per lesson
MAX_IMAGES_PER_LESSON=4
From server/config/env.ts:64:
export const MAX_IMAGES_PER_LESSON = parseInt(process.env.MAX_IMAGES_PER_LESSON || '4');

Feature Flags

Feature flags control optional functionality. See server/config/flags.ts for implementation.

AI Controls

USE_AI
boolean
default:"true"
Enable AI-generated content
USE_AI=1
Set to 0 to disable all AI features (lessons use placeholder content).From server/config/flags.ts:9:
export const USE_AI = process.env.USE_AI !== '0';
ENABLE_OPENROUTER_IMAGES
boolean
default:"true"
Enable OpenRouter image generation
ENABLE_OPENROUTER_IMAGES=1
From server/config/flags.ts:19:
export const ENABLE_OPENROUTER_IMAGES = process.env.ENABLE_OPENROUTER_IMAGES !== '0';
ENABLE_SVG_LLM
boolean
default:"true"
Enable LLM-based SVG generation
ENABLE_SVG_LLM=1
From server/config/flags.ts:20:
export const ENABLE_SVG_LLM = process.env.ENABLE_SVG_LLM !== '0';
ENABLE_STABILITY_FALLBACK
boolean
default:"false"
Enable Stability AI as fallback for image generation
ENABLE_STABILITY_FALLBACK=1
From server/config/flags.ts:21:
export const ENABLE_STABILITY_FALLBACK = process.env.ENABLE_STABILITY_FALLBACK === '1';

Analytics

ENABLE_STATS
boolean
default:"true"
Enable statistics collection
ENABLE_STATS=1
Set to 0 to disable analytics tracking (performance data, usage metrics).From server/config/flags.ts:16:
export const ENABLE_STATS = process.env.ENABLE_STATS !== '0';

Example Configurations

Minimal (Development)

# Minimal .env for local development
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/sunschool"
JWT_SECRET="dev-secret-change-me"
SESSION_SECRET="dev-session-secret"
OPENROUTER_API_KEY="sk-or-v1-..."
PORT=5000
# Production .env with all providers
DATABASE_URL="postgresql://user:pass@prod-db.example.com:5432/sunschool?sslmode=require"
DATABASE_SSL=true
JWT_SECRET="<openssl-rand-base64-32>"
JWT_EXPIRY=7d
SESSION_SECRET="<openssl-rand-base64-32>"

# AI Providers
LLM_PROVIDER=openrouter
OPENROUTER_API_KEY="sk-or-v1-..."
PERPLEXITY_API_KEY="pplx-..."

# Image Generation
IMAGE_PROVIDER=svg-llm
OPENROUTER_SVG_MODEL="google/gemini-3.1-pro-preview"
SVG_MODEL_FALLBACKS="google/gemini-3.1-flash-lite-preview,google/gemini-3-flash-preview"
IMAGE_GENERATION_TIMEOUT=15000
MAX_IMAGES_PER_LESSON=4

# Feature Flags
USE_AI=1
ENABLE_STATS=1
ENABLE_SVG_LLM=1
ENABLE_OPENROUTER_IMAGES=1

# Server
PORT=5000
NODE_ENV=production

Bittensor Experimental

# Bittensor-first configuration with OpenRouter fallback
DATABASE_URL="postgresql://user:pass@host:5432/sunschool"
JWT_SECRET="<secure-key>"
SESSION_SECRET="<secure-key>"

# Bittensor Primary
LLM_PROVIDER=bittensor
ENABLE_BITTENSOR_SUBNET_1=1
BITTENSOR_API_KEY="your-key"
BITTENSOR_SUBNET_1_URL="https://archive.opentensor.ai/graphql"
BITTENSOR_WALLET_NAME="wallet"
BITTENSOR_WALLET_HOTKEY="hotkey"

# OpenRouter Fallback
BITTENSOR_FALLBACK_ENABLED=1
OPENROUTER_API_KEY="sk-or-v1-..."

# Feature Flags
USE_AI=1
ENABLE_STATS=1

Advanced Configuration

Database Connection Pooling

Sunschool uses pg with Neon serverless driver. Connection pooling is handled automatically. For high-traffic deployments, configure external pooling (e.g., PgBouncer):
DATABASE_URL="postgresql://user:pass@pgbouncer:6432/sunschool?sslmode=require"

CORS Configuration

CORS is enabled by default for all origins in development. For production, edit server/index.ts:
import cors from 'cors';

app.use(cors({
  origin: 'https://yourdomain.com',
  credentials: true,
}));

Rate Limiting

Sunschool does not include built-in rate limiting. Use a reverse proxy (nginx, Caddy) or middleware:
npm install express-rate-limit
import rateLimit from 'express-rate-limit';

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // limit each IP to 100 requests per windowMs
});

app.use('/api/', limiter);

Configuration Validation

Sunschool validates critical environment variables on startup. Missing required variables throw errors:
// From server/config/env.ts:20-28
export function getEnv(key: string, fallback?: string): string {
  const value = process.env[key];
  if (value === undefined) {
    if (fallback !== undefined) {
      return fallback;
    }
    throw new Error(`Environment variable ${key} is required but not set`);
  }
  return value;
}

Startup Checks

When the server starts:
  1. Database connection is tested
  2. Migrations are run automatically
  3. Missing API keys log warnings (not errors) for optional providers

Next Steps


Troubleshooting

Symptoms: Failed to generate lesson content, 401 errorsSolutions:
  • Verify the API key is correct (no extra spaces or quotes)
  • Check the provider’s dashboard for key status
  • Test the key with a curl request
  • Ensure the account has sufficient credits/quota
Symptoms: Feature still enabled/disabled after changing flagSolutions:
  • Restart the server: npm start
  • Check .env file has no typos in variable names
  • Verify the flag is read correctly: add console.log(USE_AI) in server/config/flags.ts
  • Clear any cached environment variables
Symptoms: Lessons load but images take 20+ secondsSolutions:
  • Increase IMAGE_GENERATION_TIMEOUT (default: 15000ms)
  • Use faster models in fallback chain (e.g., gemini-3-flash-preview)
  • Reduce MAX_IMAGES_PER_LESSON to 2-3
  • Check OpenRouter model availability at openrouter.ai/models
Symptoms: Error: self signed certificate, SSL SYSCALL errorSolutions:
  • For local dev, set DATABASE_SSL=false
  • For production, ensure the database supports SSL
  • Add ?sslmode=require to DATABASE_URL
  • Check the database provider’s SSL documentation