βββ βββββββ ββββββββββ ββββββββββββββββββββββββββββ
βββ ββββββββ βββββββββββ ββββββββββββββββββββββββββββ
βββ βββββββββ ββββββββββββ βββββββββ ββββββββ βββ
βββ ββββββββββββββββββββββββββββββββ ββββββββ βββ
ββββββββββββ ββββββββββββ ββββββββββββββββββββββ βββ
βββββββ βββ βββββββββββ βββββββββββββββββββββ βββ
Campus Recruitment Operating System
From application to offer β fully orchestrated.
Overview Β· Architecture Β· Features Β· Getting Started Β· API Reference Β· Database Schema Β· AI & ATS Β· Environment Variables Β· Contributing
UniNest is a full-stack campus recruitment operating system that connects students and companies through a structured, end-to-end placement workflow. It handles everything from drive discovery and resume verification to interview scheduling, ATS scoring, and offer management β in a single cohesive platform.
| Role | Core Capabilities |
|---|---|
| Student | Browse drives, apply with verified resumes, track applications, manage interviews, accept/counter offers |
| Company | Post drives, review applications with AI-powered ATS analysis, schedule interviews, extend and manage offers |
UniNest is a monorepo. The
frontend/folder is a Next.js 15 App Router application. Thebackend/folder is an Express + Prisma API server. They communicate over a versioned REST API (/api/v1).
uninest/
βββ frontend/ # Next.js 15 App Router
β βββ app/
β β βββ (auth)/ # login Β· register
β β βββ student/ # dashboard Β· drives Β· applications Β· offers Β· resumes Β· profile
β β βββ company/ # dashboard Β· drives Β· applications Β· offers Β· profile Β· statistics
β β βββ unauthorized/
β β βββ page.tsx # landing / root redirect
β βββ components/
β β βββ auth/ # LoginForm Β· RegisterForm Β· ProtectedRoute
β β βββ common/ # Alerts
β β βββ layout/ # Navbar
β β βββ ui/ # Button Β· Badge Β· Card Β· FormField Β· ResumeUploader
β βββ context/
β β βββ AuthContext.tsx # global auth state Β· localStorage Β· role-based redirect
β βββ lib/
β βββ api.ts # Axios client Β· auto-attach Bearer Β· 401 redirect
β
βββ backend/
β βββ prisma/
β β βββ schema.prisma # canonical data model
β βββ src/
β βββ server.ts # entrypoint
β βββ controllers/ # request/response handlers
β βββ services/ # business logic Β· ATS Β· match
β βββ repositories/ # DB access via Prisma
β βββ routes/ # Express route definitions
β βββ middleware/ # authMiddleware Β· resumeUpload Β· errorHandler
β βββ config/
β βββ utils/
β βββ scripts/ # seed and setup scripts
β
βββ MD Files/ # Postman guides Β· testing docs
βββ package.json # root scripts
βββ LICENSE
π Student Portal
- Dashboard β placement stats, upcoming drives, recent activity
- Drive Discovery β browse and filter active placement drives
- Applications β view status, stage history, interview timeline
- Resume Management β upload PDFs (β€ 5 MB), request verification, track verification status
- Offers β view, accept, reject, or counter salary offers
- Profile β manage academic info and personal details
π’ Company Portal
- Dashboard β overview of posted drives, application volume, hire stats
- Drive Management β create, update, and close placement drives with eligibility criteria
- Application Review β view applicants, shortlist, update status, advance pipeline stages
- ATS Analysis β AI-powered resume scoring per drive using Gemini 1.5 Flash
- Interview Scheduling β create slots, confirm/reschedule with applicants
- Offer Management β extend offers, respond to counter-offers, audit trail
- Statistics β company-wide and per-drive placement analytics
π€ AI / ATS Engine
- Resume vs. drive JD matching via Gemini 1.5 Flash
- Returns: score, verdict, summary, strengths, gaps, matched/missing keywords, score breakdown, confidence, rejection reason, recommendation
- Graceful fallback to heuristic scoring (keyword overlap + resume length + education signals) when
GEMINI_API_KEYis absent or the API call fails - Local keyword matching service (
matchService) for lightweight analysis - Resume preview generation with Gemini fallback to text truncation
π Auth & Security
- JWT-based authentication,
bcryptjspassword hashing authMiddlewareverifies Bearer tokens and setsreq.userIdon all protected routes- Role enum:
STUDENT,COMPANY,ADMIN - CORS restricted to
FRONTEND_URL(default:http://localhost:3000) - PDF-only resume upload enforced in middleware, 5 MB file size cap
- Frontend 401 auto-logout: clears token + user from
localStorage, redirects to/login window.__TEST_INJECT_AUTHhook inAuthContextfor browser-based integration testing
| Tool | Version |
|---|---|
| Node.js | β₯ 18.x |
| PostgreSQL | β₯ 14 |
| npm | β₯ 9 |
git clone https://github.com/ChiragVasava/uninest.git
cd uninestBackend β create backend/.env:
DATABASE_URL="postgresql://USER:PASSWORD@localhost:5432/uninest"
PORT=8000
NODE_ENV=development
JWT_SECRET=your_jwt_secret
JWT_EXPIRE=7d
FRONTEND_URL=http://localhost:3000
GEMINI_API_KEY=your_gemini_api_key # optional β falls back to heuristic scoring
UPLOAD_DIR=uploads/resumes # see backend/.env.example
MAX_FILE_SIZE=5242880 # see backend/.env.exampleFrontend β create frontend/.env.local:
NEXT_PUBLIC_API_URL=http://localhost:8000/api/v1# root (runs both workspaces if configured)
npm install
# or independently:
cd backend && npm install
cd ../frontend && npm installcd backend
npm run prisma:generate # generate Prisma client
npm run prisma:migrate # run all migrations
npm run prisma:seed # seed initial data (optional)# From repo root
npm run dev:frontend # starts Next.js on http://localhost:3000
npm run dev:backend # starts Express on http://localhost:8000Or from each folder individually:
cd backend && npm run dev
cd frontend && npm run devnpm run build:backend # compiles TypeScript β dist/
npm run build:frontend # Next.js production build
# Start
cd backend && npm run start
cd frontend && npm run startDeployment platform: Not Found in Codebase β no Docker, Vercel, or CI configuration files exist in the repository.
All routes are prefixed with /api/v1. Protected routes require Authorization: Bearer <token>.
Auth /api/v1/auth
| Method | Path | Description |
|---|---|---|
POST |
/register |
Register new user (student or company) |
POST |
/login |
Authenticate and receive JWT |
GET |
/me |
Get current authenticated user |
Students /api/v1/students
| Method | Path | Description |
|---|---|---|
GET |
/ |
List all students |
GET |
/statistics |
Placement statistics |
GET |
/eligible/:department |
Students eligible by department |
POST |
/ |
Create student profile |
GET |
/me/profile |
Get own profile |
GET |
/:id |
Get student by ID |
PUT |
/:id |
Update student |
DELETE |
/:id |
Delete student |
Companies /api/v1/companies
| Method | Path | Description |
|---|---|---|
GET |
/ |
List all companies |
GET |
/statistics |
Global company statistics |
GET |
/by-sector/:sector |
Filter companies by sector |
GET |
/me/profile |
Get own company profile |
GET |
/me/statistics |
Own company's placement statistics |
GET |
/:id |
Get company by ID |
POST |
/ |
Create company profile |
PUT |
/:id |
Update company |
DELETE |
/:id |
Delete company |
Drives /api/v1/drives
| Method | Path | Description |
|---|---|---|
GET |
/ |
List all drives |
GET |
/statistics |
Drive statistics |
GET |
/eligible/list |
Drives the authenticated student is eligible for |
GET |
/me/company |
Drives posted by own company |
GET |
/:id |
Get drive by ID |
POST |
/ |
Create drive |
PUT |
/:id |
Update drive |
DELETE |
/:id |
Delete drive |
Applications /api/v1/applications
| Method | Path | Description |
|---|---|---|
GET |
/statistics |
Application statistics |
GET |
/me/list |
Own applications list |
GET |
/drive/:driveId |
All applications for a drive |
GET |
/drive/:driveId/ats |
ATS scores for all drive applicants |
GET |
/drive/:driveId/shortlisted |
Shortlisted applicants |
GET |
/:id |
Get application by ID |
GET |
/:id/timeline |
Full stage history |
POST |
/ |
Submit application |
PUT |
/:id/status |
Update application status |
POST |
/:id/stage |
Advance pipeline stage |
POST |
/:id/interviews |
Create interview slot |
PUT |
/interviews/:interviewId |
Update interview |
POST |
/interviews/:interviewId/confirm |
Confirm interview |
POST |
/interviews/:interviewId/reschedule |
Reschedule interview |
DELETE |
/:id |
Delete application |
Resumes /api/v1/resumes
| Method | Path | Description |
|---|---|---|
GET |
/statistics |
Resume statistics |
GET |
/pending |
Resumes pending verification |
GET |
/me/list |
Own resumes |
GET |
/me/verified |
Own verified resumes |
GET |
/ |
All resumes |
GET |
/:id |
Get resume by ID |
POST |
/ |
Upload resume (PDF, β€ 5 MB) |
PUT |
/:id |
Update resume metadata |
POST |
/:id/match |
Run ATS match against a drive |
POST |
/:id/verify |
Mark resume as verified |
POST |
/:id/reject |
Reject resume |
DELETE |
/:id |
Delete resume |
Offers /api/v1/offers
| Method | Path | Description |
|---|---|---|
GET |
/statistics |
Offer statistics |
GET |
/accepted |
All accepted offers |
GET |
/me/list |
Own offers |
GET |
/me/accepted |
Own accepted offers |
GET |
/drive/:driveId |
Offers for a specific drive |
GET |
/ |
All offers |
GET |
/:id |
Get offer by ID |
GET |
/:id/audit |
Full offer audit log |
POST |
/ |
Extend offer |
POST |
/:id/accept |
Accept offer |
POST |
/:id/reject |
Reject offer |
POST |
/:id/counter |
Submit counter-offer |
POST |
/:id/counter/respond |
Respond to counter-offer |
Health
| Method | Path | Description |
|---|---|---|
GET |
/api/v1/health |
Server health check |
The canonical schema lives at backend/prisma/schema.prisma.
erDiagram
User {
string id PK
string email
string password
UserRole role
datetime createdAt
datetime updatedAt
}
Student {
string id PK
string userId FK
string profile
string academicInfo
bool verified
}
Company {
string id PK
string userId FK
string profile
bool approved
}
Resume {
string id PK
string studentId FK
string fileMetadata
string extractedText
VerificationStatus verificationStatus
}
Drive {
string id PK
string companyId FK
string eligibilityFields
string interviewFormat
}
DriveApplication {
string id PK
string studentId FK
string driveId FK
DriveApplicationStatus status
string rejectionReason
}
Offer {
string id PK
string studentId FK
string driveId FK
float salary
datetime joinDate
json offerDetails
OfferStatus status
string counterOfferText
datetime expiresAt
}
InterviewSchedule {
string id PK
string applicationId FK
string driveId FK
string companyId FK
string studentId FK
datetime scheduledTime
InterviewMode mode
string meetingLink
InterviewConfirmationStatus confirmationStatus
}
ApplicationTimeline {
string id PK
string applicationId FK
ApplicationStage stage
string note
json metadata
string createdByUserId
}
OfferAudit {
string id PK
string offerId FK
OfferAuditAction action
}
User ||--o| Student : "has"
User ||--o| Company : "has"
Student ||--o{ Resume : "uploads"
Student ||--o{ DriveApplication : "submits"
Student ||--o{ Offer : "receives"
Student ||--o{ InterviewSchedule : "has"
Company ||--o{ Drive : "posts"
Company ||--o{ InterviewSchedule : "conducts"
Drive ||--o{ DriveApplication : "receives"
Drive ||--o{ Offer : "generates"
Drive ||--o{ InterviewSchedule : "has"
DriveApplication ||--o{ ApplicationTimeline : "tracks"
DriveApplication ||--o{ InterviewSchedule : "linked"
Offer ||--o{ OfferAudit : "logs"
| Enum | Values |
|---|---|
UserRole |
STUDENT, COMPANY, ADMIN |
DriveApplicationStatus |
Application pipeline statuses |
OfferStatus |
Offer lifecycle statuses |
ApplicationStage |
Interview pipeline stages |
InterviewMode |
ONLINE, OFFLINE (or equivalent) |
InterviewConfirmationStatus |
Confirmation states |
OfferAuditAction |
Offer action log types |
VerificationStatus |
Resume verification states |
UniNest integrates Google's Gemini 1.5 Flash model for resume-to-JD matching via backend/src/services/atsService.ts.
{
score: number; // 0β100
verdict: string; // e.g. "Strong Match"
summary: string;
strengths: string[];
gaps: string[];
matchedKeywords: string[];
missingKeywords: string[];
scoreBreakdown: object;
confidence: number;
whyRejected: string | null;
recommendation: string;
source: "gemini" | "heuristic";
}GEMINI_API_KEY present?
βββ YES β call Gemini 1.5 Flash
β βββ Success β return AI analysis (source: "gemini")
β βββ Failure β fall through to heuristic
βββ NO β heuristic scoring
(keyword overlap + resume length + education signals)
returns source: "heuristic"
A separate matchService.ts provides local, synchronous keyword matching without any external API dependency.
| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
β | PostgreSQL connection string |
PORT |
β | Server port (default: 8000) |
NODE_ENV |
β | development or production |
JWT_SECRET |
β | Secret key for JWT signing |
JWT_EXPIRE |
β | Token expiry (e.g. 7d) |
FRONTEND_URL |
β | CORS allowed origin |
GEMINI_API_KEY |
Gemini API key β falls back to heuristic ATS if absent | |
UPLOAD_DIR |
Upload directory path (see backend/.env.example) |
|
MAX_FILE_SIZE |
Max file size in bytes (see backend/.env.example) |
| Variable | Required | Description |
|---|---|---|
NEXT_PUBLIC_API_URL |
β | Backend API base URL (default: http://localhost:8000/api/v1) |
Automated test runner: Not Found in Codebase β no test scripts were found in
package.json.
Manual testing documentation is available in:
backend/README.mdbackend/STUDENT_MODULE_API.mdMD Files/POSTMAN_TESTING_GUIDE.mdMD Files/START_TESTING_NOW.md
The frontend AuthContext exposes window.__TEST_INJECT_AUTH for browser-based integration testing without going through the login flow.
| Script | Command |
|---|---|
dev:frontend |
Start Next.js dev server |
dev:backend |
Start Express dev server |
build:frontend |
Build frontend for production |
build:backend |
Compile backend TypeScript |
| Script | Command |
|---|---|
dev |
Nodemon + ts-node dev server |
build |
TypeScript compile |
start |
Run compiled production server |
prisma:generate |
Generate Prisma client |
prisma:migrate |
Run DB migrations |
prisma:seed |
Seed database |
lint |
ESLint |
| Script | Command |
|---|---|
dev |
Next.js dev server |
build |
Next.js production build |
start |
Start production server |
lint |
ESLint |
- Fork the repository
- Create a feature branch:
git checkout -b feat/your-feature - Commit your changes:
git commit -m "feat: add your feature" - Push to your fork:
git push origin feat/your-feature - Open a Pull Request
Please keep PRs focused and reference any related issues.
MIT License
Copyright (c) 2026 Chirag Vasava
See LICENSE for the full text.
Built by Chirag Vasava