A multi-role online Quranic education platform: students, teachers, parents, and admins each get their own portal, dashboard, and workflows. Built with React + TypeScript on the frontend and Node.js + Express + MySQL on the backend.
| Layer | Stack |
|---|---|
| Frontend | React 19, TypeScript, Vite, Redux Toolkit + redux-persist, Tailwind CSS, Recharts, Lucide Icons |
| Backend | Node.js, Express 4, MySQL 8 (mysql2/promise pool), Socket.IO |
| Auth | JWT (15m access / 7d refresh, with rotation), bcrypt, Google OAuth 2.0, email OTP |
| Tooling | Nodemailer / SendGrid, Stripe, Helmet, express-rate-limit, Winston, Morgan, Swagger UI |
.
├── Backend/ # Express API, MySQL data layer, auth, sockets
│ ├── config/ # db pool, passport, swagger
│ ├── controllers/ # request handlers, organised by domain
│ ├── middleware/ # auth, error handler, file upload, strict-auth
│ ├── migrations/ # versioned SQL files (000_..., 001_..., …)
│ ├── routes/ # Express route registrations
│ ├── services/ # email, otp, notifications
│ ├── scripts/migrate.js # SQL migration runner
│ ├── utils/ # logger, response helpers, pagination
│ └── server.js
├── Frontend/ # React + Vite app
│ ├── src/api/ # typed API client (single source of truth)
│ ├── src/components/ # shared components (Layout, Toast, Charts, …)
│ ├── src/contexts/ # Toast provider
│ ├── src/hooks/ # useSocket, etc.
│ ├── src/pages/ # route components (one file per page)
│ └── src/store/ # Redux Toolkit slices + persist config
├── package.json # monorepo root scripts (dev/install:all/build)
└── .env.example # canonical env template
- Node.js 20+ and npm 10+
- MySQL 8 (or XAMPP / MariaDB 10.6+)
cp .env.example .env
cp Backend/.env.example Backend/.env
cp Frontend/.env.example Frontend/.envThen edit each .env to match your local setup. At minimum the backend needs:
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=
DB_NAME=equran_academy
JWT_SECRET=... # `node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"`
JWT_REFRESH_SECRET=...
Google OAuth, Stripe and email transports are optional — leave their values blank to disable those features cleanly.
npm run install:allThis installs root, Backend, and Frontend packages in one shot.
cd Backend
npm run db:migrate # apply schema (idempotent, safe to re-run)
npm run db:seed # optional: load sample teachers, classes, etc.
# npm run db:reset # DANGER: drops the database and re-applies everything with seedsThe migration runner reads every *.sql in Backend/migrations/ in lexical order and tracks applied files in a _migrations table.
From the repo root, with both servers running concurrently:
npm run devOr run them in separate shells:
npm run backend:dev # http://localhost:5000 (API + Swagger at /api-docs)
npm run frontend:dev # http://localhost:3000 (proxies /api -> backend)- Student / Parent: register → email-OTP verification → can log in.
- Teacher: register + upload required documents → admin reviews → approval grants login.
- Admin: only the addresses listed in
PREDEFINED_ADMIN_EMAILSare auto-approved. Others get queued inadmin_approval_requestsand require an existing admin's approval.
- Access token: short-lived JWT (
JWT_ACCESS_EXPIRES_IN, default15m), sent asAuthorization: Bearer …. - Refresh token: longer-lived JWT (
JWT_REFRESH_EXPIRES_IN, default7d), stored in therefresh_tokenstable and rotated on every/api/auth/refreshcall. - Reuse detection: if a revoked refresh token is presented again, every active session for that user is revoked.
- Logout marks the token
revoked_at = NOW()rather than hard-deleting (audit trail). npm run db:migrateadds therevoked_atcolumn on existing installs.
- The API client (
Frontend/src/api/index.ts) silently refreshes the access token onTOKEN_EXPIREDand queues concurrent requests during the refresh. - On unrecoverable session loss, it dispatches the Redux
logoutaction and callspersistor.purge()so no stale user state survives.
With the backend running, browse Swagger UI at:
http://localhost:5000/api-docs
The /api/health endpoint returns { status: 'ok', version } and is safe for liveness probes.
| From | Command | What it does |
|---|---|---|
| repo root | npm run dev |
Run backend + frontend concurrently |
| repo root | npm run install:all |
Install everything |
| repo root | npm run build |
Production build of the frontend |
| Backend | npm run dev |
nodemon-watched API server |
| Backend | npm run start |
Production-mode API server |
| Backend | npm run db:migrate |
Apply pending SQL migrations |
| Backend | npm run db:seed |
Apply migrations + seed data |
| Backend | npm run db:reset |
Drop database and reapply everything (DANGER) |
| Frontend | npm run dev |
Vite dev server on port 3000 |
| Frontend | npm run build |
Production build (dist/) |
| Frontend | npm run lint |
TypeScript-only check (tsc --noEmit) |
-
JWT_SECRETandJWT_REFRESH_SECRETset to long random strings (the server refuses to boot otherwise inNODE_ENV=production). -
FRONTEND_URLset to the deployed origin (used for CORS + OAuth redirects). Add extras toCORS_ORIGINS(comma-separated) if you have staging hosts. - MySQL user has only the privileges it needs on
equran_academy. - HTTPS terminator in front of the API; Helmet is already enabled.
- Logs are persisted (Winston already writes to
Backend/logs/). - If you use Google OAuth, add
${BACKEND_URL}/api/auth/google/callbackto the Google Cloud OAuth client's redirect URIs.