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.
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.
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
| Script | What It Does |
|---|---|
npm run dev | Start dev server on default port (3000) |
npm run dev -- -p 3001 | Start dev server on port 3001 |
npm run worker | Run the BullMQ worker (notifications + crons) |
npm run seed:notifications | Seed NotificationTemplate system defaults |
npm run build | Production build |
npm start | Run production build |
npx prisma studio | Open Prisma Studio database browser |
npx prisma migrate dev --name <name> | Create + apply a migration |
npx prisma generate | Regenerate Prisma client |
npx prisma db push | Push schema without a migration (dev only) |
npx prisma db push --accept-data-loss | Same, 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.
| Endpoint | What it does | Invoke manually |
|---|---|---|
GET /api/cron/assignment-reminders | Creates in-app notifications for assignments due in the next 24 hours | curl -H "Authorization: Bearer $CRON_SECRET" http://localhost:3001/api/cron/assignment-reminders |
GET /api/cron/report-schedules | Fires due report schedules + evaluates alerts | curl -H "Authorization: Bearer $CRON_SECRET" http://localhost:3001/api/cron/report-schedules |
GET /api/cron/analytics-snapshot | Populates DailyMetricSnapshot + invalidates analytics cache per institution | curl -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:
- The worker isn't running. Start
npm run workerin a second terminal. - Templates not seeded. Run
npm run seed:notifications. - 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.