Skip to content

Kash15if/online-exam-api

Repository files navigation

# Online Exam API Modern Node.js + Express REST API powering the Online Exam Portal. This is a clean rewrite of the original college-era project: layered (routes -> controllers -> services -> repositories), security-hardened, fully tested, and ready to swap the in-memory store for a real database. --- ## Highlights - Layered architecture: `routes -> controllers -> services -> repositories` - Pluggable storage layer: `memory`, `postgres`, `mssql` (selected via `REPOSITORY_DRIVER`) - JWT auth with **bcryptjs**-hashed passwords - Role-based access control (`student`, `admin`) - Per-route validation with `express-validator` - Tiered rate limiting (general, auth, exam) - Helmet + strict CORS allow-list - Centralised `ApiError` and JSON error envelope - Full unit + integration test suite (`jest` + `supertest`) - 60+ tests - `AUTH_BYPASS=true` switch for local dev/demos (refused in production) - Configurable via `.env`, validated at boot - Graceful shutdown on `SIGINT` / `SIGTERM` (closes DB pools) --- ## Project structure ``` online-exam-api/ |-- src/ | |-- app.js Express app factory | |-- config/ | | |-- env.js Validated env config | | `-- logger.js Tiny levelled logger | |-- middleware/ | | |-- asyncHandler.js | | |-- auth.js authenticate + requireAdmin | | |-- errorHandler.js | | |-- notFound.js | | |-- rateLimiter.js | | |-- security.js Helmet + CORS | | `-- validate.js | |-- controllers/ Thin HTTP handlers | |-- services/ Business logic | |-- repositories/ Data layer (in-memory, swappable) | |-- routes/ Express routers | |-- validators/ Request schemas | `-- utils/ | |-- ApiError.js | `-- seed.js Seeds default users + demo exam |-- tests/ | |-- unit/ | | |-- middleware/ | | `-- services/ | |-- integration/ | | `-- routes/ | |-- helpers.js | `-- setupEnv.js |-- server.js Process entry |-- jest.config.js |-- .eslintrc.json / .prettierrc.json |-- .env.example `-- package.json ``` --- ## Getting started ```bash npm install cp .env.example .env npm run dev # auto-reload with nodemon # or npm start ``` The server boots on `http://localhost:8081` by default and seeds: | Role | Email | Password | |---------|------------------------|-----------------| | admin | admin@example.com | Admin@12345 | | student | student@example.com | Student@12345 | It also seeds one demo exam with five JavaScript questions so the UI works out of the box. > Override seed credentials via `SEED_ADMIN_EMAIL`, `SEED_ADMIN_PASSWORD`, etc. --- ## Scripts | Command | Description | |-------------------------|--------------------------------------------| | `npm start` | Start the server | | `npm run dev` | Start with nodemon (auto-reload) | | `npm test` | Run the full Jest test suite | | `npm run test:coverage` | Run tests with a coverage report | | `npm run lint` | Run ESLint | | `npm run lint:fix` | ESLint with `--fix` | | `npm run format` | Prettier on the project | --- ## Endpoints All non-auth routes live under `/api`. JSON envelopes look like: ```json { "success": true, "exam": { } } ``` ```json { "success": false, "error": { "status": 400, "message": "...", "details": [] } } ``` ### Auth (`/api/auth`) | Method | Path | Auth | Body / Notes | |--------|-------------|----------|-----------------------------------------------| | POST | `/register` | public | `{ email, password (>=8), name }` | | POST | `/login` | public | `{ email, password }` -> `{ token, user }` | | POST | `/refresh` | bearer | issues a new token | | GET | `/me` | bearer | returns the current user | ### Exams (`/api/exams`) | Method | Path | Auth | |--------|----------|---------------| | GET | `/` | any user | | GET | `/:id` | any user | | POST | `/` | admin | | PUT | `/:id` | admin | | DELETE | `/:id` | admin | `correctAnswerIndex` is hidden from non-admin viewers. ### Questions (`/api/questions`) | Method | Path | Auth | |--------|---------------------|--------| | GET | `/exam/:examId` | any | | GET | `/:id` | any | | POST | `/` | admin | | PUT | `/:id` | admin | | DELETE | `/:id` | admin | ### Submissions (`/api/submissions`) | Method | Path | Auth | |--------|-----------------------|---------------| | POST | `/exam/:examId` | bearer | | GET | `/me` | bearer | | GET | `/:id` | owner / admin | | GET | `/user/:userId` | admin | ### Health `GET /api/health` -> `{ success: true, status: "ok", uptime, timestamp }` --- ## Security notes - Passwords stored as bcrypt hashes; constant-time-ish login defence vs. timing oracles. - `JWT_SECRET` must be set in production (boot fails otherwise). - `helmet` with strict CSP, HSTS, frameguard, no-sniff. - CORS uses an explicit allow-list (set via `CORS_ORIGIN`). - Tiered rate limits on `/api/auth/*` (10/15min) and `/api/submissions/exam/*` (30/min). - Body size capped at 50 KB by default. - `correctAnswerIndex` is stripped from responses for non-admin users. --- ## Storage drivers The data layer is dispatched at boot by `REPOSITORY_DRIVER`: | Driver | Backed by | Notes | |------------|------------------------------------|-------------------------------------------------| | `memory` | In-process `Map` (default) | Resets on restart. Used by all tests. | | `postgres` | `pg` connection pool | Runs migrations from `db/migrations/postgres.sql` | | `mssql` | `mssql` (tedious) connection pool | Runs migrations from `db/migrations/mssql.sql` | `pg` and `mssql` are listed under `optionalDependencies`, so they install with the project but the modules are only required at boot time when the matching driver is selected. Switching drivers is purely env-driven - no code changes: ```bash # Postgres REPOSITORY_DRIVER=postgres DATABASE_URL=postgres://user:pass@host:5432/online_exam npm start # MSSQL REPOSITORY_DRIVER=mssql MSSQL_HOST=host MSSQL_USER=sa MSSQL_PASSWORD=... MSSQL_DATABASE=online_exam npm start # Default REPOSITORY_DRIVER=memory npm start ``` ### Running migrations ```bash # Postgres psql "$DATABASE_URL" -f db/migrations/postgres.sql # or: npm run db:postgres # MSSQL (requires sqlcmd on PATH) npm run db:mssql ``` ### Disabling the seed step When pointing the API at a real production database you usually do not want it to seed demo users / a demo exam every boot. Set `SEED_ON_BOOT=false` to skip. ### Repository interface Every driver exposes the same shape from `src/repositories/index.js`: ``` users { findByEmail, findById, create, list, reset } exams { findById, list, create, update, delete, reset } questions { findById, findByExam, create, update, delete, deleteByExam, reset } submissions { findById, findByUser, findByExamAndUser, create, reset } ``` All SQL queries are parameterized; no string concatenation anywhere. ### Auth bypass (development only) `AUTH_BYPASS=true` short-circuits auth entirely - every protected route is treated as authenticated as a fixed bypass user (admin by default). The server refuses to start with `AUTH_BYPASS=true` when `NODE_ENV=production`. --- ## License MIT

About

Rest api for online exam portal backend using node.js and sql. Used jwt for authentication

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors