A full-stack job portal where job seekers can browse and apply for jobs, and recruiters can post jobs and manage applicants. It includes email OTP verification, password reset via OTP, and AI-powered job recommendations.
Built with the MERN stack (MongoDB, Express, React, Node.js).
- Browse all jobs, search by keyword, and view full job details
- See AI-recommended related jobs on every job detail page
- Clean, responsive UI (works on mobile and desktop)
- Register with email OTP verification (account is verified before login)
- Auto-login right after verifying the OTP
- Apply to jobs (login required)
- Save / bookmark jobs for later (login required)
- Profile page with bio, skills, resume link, and applied-jobs history
- Edit profile and change password (OTP based)
- Register / login as a recruiter
- Create and manage companies
- Post jobs and edit job listings
- View applicants for each job
- Email OTP verification on signup (via Nodemailer + Gmail OAuth2)
- Forgot password flow from the login page (OTP based)
- Change password from the profile (OTP based)
- Passwords hashed with bcrypt, auth via JWT stored in an httpOnly cookie
- Job recommendations generated by an LLM (GPT-OSS-120B via an OpenAI-compatible API)
- Falls back to jobType/location matching if the AI is slow or unavailable
Frontend
- React 18 + Vite
- Redux Toolkit + redux-persist (state management)
- React Router DOM (routing)
- Tailwind CSS + shadcn/ui (Radix UI) components
- Axios, Sonner (toasts), Lucide icons
Backend
- Node.js + Express
- MongoDB + Mongoose
- JWT + bcryptjs (authentication)
- Multer + Cloudinary (file/image uploads)
- Nodemailer (Gmail OAuth2) for OTP emails
- Native
fetchto call the AI API
JOB-PORTAL/
├── Backend/
│ ├── controllers/ # Route logic (user, job, company, application)
│ ├── models/ # Mongoose schemas
│ ├── routes/ # Express routes
│ ├── middleware/ # Auth (JWT) + Multer upload
│ ├── utils/ # db, cloudinary, datauri, mailer, aiClient
│ ├── index.js # App entry (Express server)
│ └── .env # Backend secrets (you create this)
│
└── Frontend/
├── src/
│ ├── components/ # UI components (auth, admin, job, shared)
│ ├── redux/ # Slices + store
│ ├── hooks/ # Custom data-fetching hooks
│ └── utils/data.js # API base URLs
└── vite.config.js
Before you start, make sure you have:
- Node.js v18 or higher (the backend uses the built-in
fetch) - npm (comes with Node.js)
- A MongoDB database (local or a free MongoDB Atlas cluster)
- A Cloudinary account (for profile photo uploads) — free tier works
- A Gmail account with OAuth2 credentials (for sending OTP emails)
- An AI API key (clod.io / any OpenAI-compatible endpoint) for recommendations
cd JOB-PORTALcd Backend
npm installCreate a file named .env inside the Backend/ folder with these values:
# Database
MONGO_URI = your_mongodb_connection_string
# Auth
JWT_SECRET = any_long_random_secret
# Cloudinary (image uploads)
CLOUD_NAME = your_cloud_name
CLOUD_API = your_cloudinary_api_key
API_SECRET = your_cloudinary_api_secret
# Server
PORT = 5011
NODE_ENV = development
# Allowed frontend origins (comma separated, no trailing slash)
CORS_ORIGIN = http://localhost:5173
# AI (for job recommendations - OpenAI-compatible endpoint)
AI_BASE_URL = https://api.clod.io/v1
AI_API_KEY = your_ai_api_key
AI_MODEL = openai/gpt-oss-120b
# Email (Gmail OAuth2 - for OTP verification)
EMAIL_USER = your_gmail_address
CLIENT_ID = your_google_oauth_client_id
CLIENT_SECRET = your_google_oauth_client_secret
REFRESH_TOKEN = your_google_oauth_refresh_tokenStart the backend:
npm run devThe server runs at http://localhost:5011
Note: Set
NODE_ENV = developmentwhile developing locally so the backend does not try to serve the production frontend build.
Open a new terminal:
cd Frontend
npm install
npm run devThe app runs at http://localhost:5173
The frontend talks to the backend using the URLs in Frontend/src/utils/data.js
(default http://localhost:5011). Change those if your backend runs on a different host/port.
| Step | Command | Folder | URL |
|---|---|---|---|
| 1. Start backend | npm run dev |
Backend/ |
http://localhost:5011 |
| 2. Start frontend | npm run dev |
Frontend/ |
http://localhost:5173 |
Open http://localhost:5173 in your browser. 🎉
Register → Verify → Auto Login
- Fill the register form → backend creates an unverified account and emails a 6-digit OTP.
- You are taken to the Verify Email page → enter the OTP.
- On success you are logged in automatically and sent to the home page.
Forgot Password (from Login)
- Click "Forgot Password?" on the login page.
- Enter your email → get an OTP → enter OTP + new password → done.
Change Password (from Profile)
- Open Profile → Edit (pencil) → Change Password.
- An OTP is sent to your email automatically → enter OTP + new password → done.
Base URL: http://localhost:5011
| Method | Endpoint | Description |
|---|---|---|
| POST | /register |
Register + send OTP |
| POST | /verify-otp |
Verify email OTP (auto-login) |
| POST | /resend-otp |
Resend signup OTP |
| POST | /forgot-password |
Send password-reset OTP |
| POST | /reset-password |
Reset password with OTP |
| POST | /login |
Login |
| POST | /logout |
Logout |
| POST | /profile/update |
Update profile (auth) |
| Method | Endpoint | Description |
|---|---|---|
| GET | /get |
List/search all jobs (public) |
| GET | /get/:id |
Get single job (public) |
| GET | /recommendations/:id |
AI recommended jobs (public) |
| POST | /post |
Post a job (recruiter, auth) |
| GET | /getadminjobs |
Recruiter's jobs (auth) |
- Company create / get / update (recruiter, auth)
- Apply to a job, view applicants (auth)
Backend
npm run dev/npm start— start with nodemon
Frontend
npm run dev— start dev servernpm run build— production buildnpm run preview— preview the buildnpm run lint— run ESLint
- CORS error — make sure your frontend URL is listed in
CORS_ORIGINin the backend.env, then restart the backend. - OTP email not sending — check the Gmail OAuth2 credentials (
CLIENT_ID,CLIENT_SECRET,REFRESH_TOKEN,EMAIL_USER). The refresh token must have Gmail send scope. - Can't log in after adding verification — accounts created before email verification existed may have
isVerifiedunset; verify them or update the field in the DB. .envchanges not applied — restart the backend (nodemon does not reload.envautomatically).- Recommendations not showing — confirm the
AI_*values in.envand that the backend was restarted.
Ankit Pathak — github.com/ankitpathak62
Made with the MERN stack. Contributions and suggestions are welcome!