A full-stack mock interview platform with a Spring Boot API, MongoDB persistence, and a static HTML/CSS/JavaScript frontend.
| Layer | Technology |
|---|---|
| Frontend | HTML, CSS, vanilla JavaScript |
| Backend | Spring Boot 3, Java 17, Maven |
| Database | MongoDB |
| Deployment | Render Web Service + Render Static Site |
Interview/
├── backend/
│ ├── Dockerfile
│ ├── pom.xml
│ └── src/main/
│ ├── java/com/interview/platform/
│ └── resources/application.properties
├── frontend/
│ ├── css/
│ ├── js/
│ │ ├── app.js
│ │ ├── dashboard.js
│ │ └── env.js
│ ├── pages/dashboard.html
│ └── index.html
└── render.yaml
- Java 17
- Maven 3.9+
- MongoDB connection string
- Optional: Node.js, only if you want to serve the static frontend locally
From the repository root:
cd backend
$env:MONGO_URI="mongodb+srv://USER:PASSWORD@HOST/interview_platform"
mvn spring-boot:runOn macOS/Linux, use:
export MONGO_URI="mongodb+srv://USER:PASSWORD@HOST/interview_platform"
mvn spring-boot:runThe API listens on PORT when it is provided by the host, otherwise it defaults to 8080.
For local development, frontend/js/env.js provides the backend base URL used by all frontend API helpers:
window.INTERVIEW_API_BASE = "https://interview-platform-api-iv6x.onrender.com";You can open frontend/index.html directly in a browser, or serve the folder:
npx serve frontendIf your backend runs somewhere else, update frontend/js/env.js locally or override window.INTERVIEW_API_BASE before loading app.js and dashboard.js. Application code should continue to call APIs through window.INTERVIEW_API_BASE, not a hardcoded backend host.
| Variable | Required | Default | Description |
|---|---|---|---|
PORT |
No | 8080 |
HTTP port. Render sets this automatically. |
MONGO_URI |
Yes | none | MongoDB connection URI. |
MONGO_DATABASE |
No | interview_platform |
MongoDB database name. |
CORS_ALLOWED_ORIGIN_PATTERNS |
No | local dev origins + https://*.onrender.com |
Comma-separated allowed frontend origins. |
JWT_SECRET |
Yes in production | development fallback | HMAC signing secret, at least 32 characters. |
JWT_ACCESS_TOKEN_MINUTES |
No | 45 |
Access token lifetime. |
JWT_REFRESH_TOKEN_DAYS |
No | 14 |
Refresh token lifetime. |
SENDGRID_API_KEY |
Yes for email | none | SendGrid API key used for verification and password reset OTP emails. |
SENDGRID_FROM_EMAIL |
Yes for email | [email protected] |
Verified SendGrid sender address. |
CLOUDINARY_CLOUD_NAME |
Yes for avatar uploads | none | Cloudinary cloud name used by the backend upload endpoint. |
CLOUDINARY_API_KEY |
Yes for avatar uploads | none | Cloudinary API key used only on the backend. |
CLOUDINARY_API_SECRET |
Yes for avatar uploads | none | Cloudinary API secret used to sign backend uploads. Never expose it in the frontend. |
CLOUDINARY_FOLDER |
No | interviewprep/avatars |
Folder where profile images are stored in Cloudinary. |
FRONTEND_URL |
No | http://localhost:5500 |
Public frontend URL retained for branded email context. |
JAVA_OPTS |
No | -XX:MaxRAMPercentage=75.0 |
JVM options used by the Docker container. |
| Variable | Required | Description |
|---|---|---|
INTERVIEW_API_BASE |
Render-managed | Render writes this complete backend base URL into frontend/js/env.js. |
During Render deployment, the frontend build command writes frontend/js/env.js with the deployed backend URL.
This repository includes a Render Blueprint in render.yaml.
It creates:
interview-platform-api: Dockerized Spring Boot Web Service frombackend/interview-platform-frontend: Static Site fromfrontend/
- Push this repository to GitHub.
- In Render, choose New + then Blueprint.
- Connect the GitHub repository.
- Render will detect
render.yamland create both services. - Set
MONGO_URI,JWT_SECRET, the SendGrid variables, and the Cloudinary variables oninterview-platform-api. - Deploy the blueprint.
- After the first deployment, confirm:
- Backend health:
https://<api-service>.onrender.com/api/health - Frontend:
https://<frontend-service>.onrender.com
- Backend health:
- If you rename services or use custom domains, update
CORS_ALLOWED_ORIGIN_PATTERNSon the backend to include the frontend origin.
- Render builds the backend with
backend/Dockerfile. - The Docker build caches Maven dependencies, packages the Spring Boot jar, and runs it on a slim Java 17 runtime image as a non-root user.
- Render provides
PORT; Spring Boot binds throughserver.port=${PORT:8080}. - Render builds the static frontend from
frontend/. - The frontend build writes
js/env.jswith the complete backend base URL fromINTERVIEW_API_BASE. - Browser requests go from the static frontend to the deployed backend through
window.INTERVIEW_API_BASE. - Spring CORS allows configured frontend origins through
CORS_ALLOWED_ORIGIN_PATTERNS.
Build the backend:
cd backend
mvn clean packageBuild the backend Docker image:
cd backend
docker build -t interview-platform-api .Run the Docker image locally:
docker run --rm -p 8080:8080 `
-e MONGO_URI="mongodb+srv://USER:PASSWORD@HOST/interview_platform" `
interview-platform-apiUse backslashes instead of backticks on macOS/Linux.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/health |
Health check |
POST |
/api/auth/register |
Register, hash password, create JWT pair, send OTP |
POST |
/api/auth/login |
Login with JWT + refresh token response |
POST |
/api/auth/refresh |
Exchange refresh token for a new token pair |
POST |
/api/auth/logout |
Revoke refresh token |
POST |
/api/auth/send-otp |
Send verification OTP |
POST |
/api/auth/resend-otp |
Rate-limited OTP resend |
POST |
/api/auth/verify-otp |
Verify OTP and activate account |
POST |
/api/auth/forgot-password |
Send password reset OTP if account exists |
POST |
/api/auth/verify-reset-otp |
Verify password reset OTP and issue a short-lived reset token |
POST |
/api/auth/reset-password |
Validate verified reset token and update password |
POST |
/api/users/me/avatar |
Authenticated multipart upload that stores a Cloudinary-backed profile image URL |
POST |
/api/users/register |
Register a user |
POST |
/api/users/login |
Login |
GET |
/api/users |
List users |
GET |
/api/users/interviewers?skill= |
Browse interviewers |
GET |
/api/interviewers/search |
Search/filter/sort/paginate interviewers |
GET |
/api/interviewers/{id} |
Interviewer profile |
GET |
/api/interviewers/{id}/availability |
Available slots with booked slots removed |
GET |
/api/interviewers/top-rated |
Top-rated interviewers |
GET |
/api/interviewers/recommended |
Recommended interviewers |
POST |
/api/interviewers/{id}/favorite |
Toggle saved interviewer |
POST |
/api/bookings |
Multi-step booking endpoint that creates a session and meeting link |
POST |
/api/sessions |
Create a session |
GET |
/api/sessions/interviewer/{id} |
Interviewer sessions |
GET |
/api/sessions/interviewee/{id} |
Interviewee sessions |
PATCH |
/api/sessions/{id}/confirm |
Confirm a session |
PATCH |
/api/sessions/{id}/complete |
Complete a session |
PATCH |
/api/sessions/{id}/cancel |
Cancel a session |
POST |
/api/feedback |
Submit feedback |
GET |
/api/feedback |
List feedback |
GET |
/api/notifications?userId= |
Notification dropdown data |
PATCH |
/api/notifications/{id}/read |
Mark notification as read |
users now stores profile/discovery fields (company, currentRole, bio, avatarUrl, language, yearsExperience, averageRating, reviewCount, completedInterviews, priceCents, availability, favoriteInterviewerIds, isVerified, createdAt, lastLogin) and passwordHash instead of plain passwords.
sessions now includes booking fields (interviewType, durationMinutes, meetingLink, meetingStatus, status, createdAt, updatedAt) and indexed interviewer/time/status lookups to prevent double booking.
New collections: refresh_tokens, verification_otps, password_reset_tokens, and notifications. OTP/reset/token collections use TTL indexes through expiresAt.
The frontend remains vanilla HTML/CSS/JavaScript. index.html is the public SaaS landing page plus auth, OTP, and reset flows. pages/dashboard.html is a role-aware workspace. js/dashboard.js owns API access, token refresh, discovery search, booking state, session actions, feedback submission, notifications, and mobile navigation. css/style.css contains shared design tokens and responsive layouts.
Create a SendGrid API key with Mail Send permission, verify the sender used by SENDGRID_FROM_EMAIL, then set SENDGRID_API_KEY, SENDGRID_FROM_EMAIL, and FRONTEND_URL in Render. When SendGrid is not configured, the backend logs skipped emails instead of failing local development.