DocsDeveloper

Local Development Setup

Get OptiLearn running on your machine

This guide walks you through running OptiLearn locally for development.

Prerequisites

  • Node.js 20+ and npm
  • Docker Desktop (for PostgreSQL + Redis)
  • Git

Step 1: Clone the Repo

git clone git@github.com:hemanthopti/optilearn.git
cd optilearn

Step 2: Install Dependencies

npm install

Step 3: Start Services

OptiLearn needs PostgreSQL (and optionally Redis + MinIO). The easiest setup reuses OptiCRM's docker-compose:

cd ~/educrm
docker-compose up -d

This starts:

  • PostgreSQL on port 5432
  • Redis on port 6379
  • MinIO on ports 9000 / 9001

Or start just a new PostgreSQL for OptiLearn on a different port:

docker run -d --name optilearn-db \
  -e POSTGRES_USER=optilearn \
  -e POSTGRES_PASSWORD=optilearn \
  -e POSTGRES_DB=optilearn \
  -p 5433:5432 \
  postgres:16-alpine

Step 4: Configure Environment

Copy the example env file:

cp .env.example .env

Minimum required variables:

# Core
NEXTAUTH_URL=http://localhost:3001
NEXTAUTH_SECRET=any-long-random-string-here
AUTH_SECRET=any-long-random-string-here
NODE_ENV=development

# Database
DATABASE_URL=postgresql://optilearn:optilearn@localhost:5433/optilearn

For file uploads (optional — local FS works without it):

R2_ACCOUNT_ID=<your cloudflare account id>
R2_ACCESS_KEY_ID=<from r2 api token>
R2_SECRET_ACCESS_KEY=<from r2 api token>
R2_BUCKET=opticloud-optilearn
R2_PUBLIC_URL=https://pub-xxx.r2.dev

Step 5: Push the Schema

npx prisma db push

This creates the LMS tables. After pulling new commits that change the schema you usually want:

npx prisma db push --accept-data-loss

The --accept-data-loss flag is necessary in dev when columns are renamed/dropped. Never use it on a real database.

You should see:

🚀  Your database is now in sync with your Prisma schema.

Step 6: Seed Notification Templates

The notification engine refuses to deliver any message that doesn't have a row in NotificationTemplate. Seed the system defaults:

npm run seed:notifications

This upserts one NotificationTemplate row per key in NOTIFICATION_DEFINITIONS with institutionId = null. Re-run after pulling commits that add new keys — it's idempotent.

Warning

Skip this and every notification will land in LMSNotification with status=FAILED, failureReason=missing_template. Easy to forget and easy to fix.

Step 7: Run the Dev Server

npm run dev -- -p 3001

OptiCRM usually runs on port 3000, so OptiLearn uses 3001 to avoid conflicts.

Open http://localhost:3001. You'll see the login page with a Dev Quick Login panel. Click Instructor or Student to log in without credentials.

Step 8: Run the Worker (optional in dev)

The notification queues + cron schedules live in a long-running worker process, separate from the Next.js server:

npm run worker

You'll see [worker] starting notification workers... followed by [worker] ready. Leave it running in a second terminal whenever you're testing emails, push notifications, digests, or re-engagement nudges.

The Next.js server enqueues but never processes — without the worker, jobs pile up in Redis and nothing sends. See Worker Deployment for details and prod setup.

Tip

Set WORKER_VERBOSE=true to log every completed job. Useful when chasing why an email didn't send.

Step 9: Seed Test Data

After logging in, open DevTools console (F12) and run:

fetch("/api/dev/seed", { method: "POST" })
  .then(r => r.json())
  .then(console.log);

This creates sample courses, badges, and a certificate so you can explore with real data.

Scripts

ScriptWhat It Does
npm run devStart dev server on default port (3000)
npm run dev -- -p 3001Start dev server on port 3001
npm run workerRun the BullMQ worker (notifications + crons)
npm run seed:notificationsSeed NotificationTemplate system defaults
npm run buildProduction build
npm startRun production build
npx prisma studioOpen Prisma Studio database browser
npx prisma migrate dev --name <name>Create + apply a migration
npx prisma generateRegenerate Prisma client
npx prisma db pushPush schema without a migration (dev only)
npx prisma db push --accept-data-lossSame, but allow destructive column changes

Cron endpoints (optional in local dev)

OptiLearn runs three hourly cron endpoints in production. You don't need to schedule them locally, but it's worth knowing what they do so you can invoke them manually when testing a cron-dependent feature.

EndpointWhat it doesInvoke manually
GET /api/cron/assignment-remindersCreates in-app notifications for assignments due in the next 24 hourscurl -H "Authorization: Bearer $CRON_SECRET" http://localhost:3001/api/cron/assignment-reminders
GET /api/cron/report-schedulesFires due report schedules + evaluates alertscurl -H "Authorization: Bearer $CRON_SECRET" http://localhost:3001/api/cron/report-schedules
GET /api/cron/analytics-snapshotPopulates DailyMetricSnapshot + invalidates analytics cache per institutioncurl -H "Authorization: Bearer $CRON_SECRET" http://localhost:3001/api/cron/analytics-snapshot

All three use the same CRON_SECRET env var for auth. Set it in .env to any long random string; the same value goes into the curl header when invoking them. If CRON_SECRET is unset, the endpoints skip auth entirely (dev convenience) — don't rely on that for any environment that isn't your laptop.

In production, schedule them on the Coolify panel alongside the existing assignment-reminders entry:

0  * * * * curl -fsS -H "Authorization: Bearer $CRON_SECRET" https://learn.opticrm.app/api/cron/assignment-reminders
0  * * * * curl -fsS -H "Authorization: Bearer $CRON_SECRET" https://learn.opticrm.app/api/cron/report-schedules
30 * * * * curl -fsS -H "Authorization: Bearer $CRON_SECRET" https://learn.opticrm.app/api/cron/analytics-snapshot

The analytics-snapshot is offset by 30 minutes so it doesn't contend for DB connections with the report-schedules cron.

Common Issues

"Invalid URL" errors on startup

You have S3_* env vars from an old config. Remove them — OptiLearn now uses R2_*.

Database connection refused

Docker Desktop may not be running. Start it, then check:

docker ps | grep postgres

Port 3001 already in use

Another process is using it. Kill it or pick a different port:

lsof -i :3001 -t | xargs kill

Prisma client out of date

After pulling new commits that changed the schema:

npx prisma generate
npx prisma db push --accept-data-loss

Notifications never arrive in dev

Three usual suspects:

  1. The worker isn't running. Start npm run worker in a second terminal.
  2. Templates not seeded. Run npm run seed:notifications.
  3. Redis not running. Verify with docker ps | grep redis.

"OptiCRM contact lookup failed" warnings

You're missing OPTICRM env vars. For pure UI work you can ignore — the affected lookups gracefully degrade. For real integration testing, set:

OPTICRM_API_KEY=<shared secret matching OptiCRM>
OPTICRM_BASE_DOMAIN=opticrm.local       # whatever your dev domain is
OPTICRM_PROTOCOL=http                    # default https

See OptiCRM Integration.