AI-Powered Internship & Job Placement Platform
Connecting the right students to the right opportunities β intelligently.
CareerNest is a backend platform that uses vector similarity search to intelligently match students with job and internship opportunities. When a recruiter posts a job, the system automatically generates an embedding from the job description, compares it against candidate resume embeddings using pgvector cosine similarity, and notifies the top matches via WhatsApp β all asynchronously through a robust background worker pipeline.
Key idea: Instead of keyword-based filtering, CareerNest understands the semantic meaning of resumes and job descriptions to surface truly relevant matches.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Client (REST API) β
ββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββ
β
βββββββββββΌβββββββββββ
β Express 5 Server β
β (JWT Auth Layer) β
βββββββββββ¬βββββββββββ
β
βββββββββββββββββββββΌββββββββββββββββββββ
β β β
βββββββββΌββββββββ βββββββββΌββββββββ βββββββββΌββββββββ
β Auth Routes β β Job Routes β β Match Routes β
β /login β β /jobs β β /jobs/:id/ β
β /register β β /jobs/my β β matches β
β /logout β β /jobs/:id β β β
βββββββββ¬ββββββββ βββββββββ¬ββββββββ βββββββββ¬ββββββββ
β β β
βββββββββββββββββββββΌββββββββββββββββββββ
β
βββββββββββββββββββββΌββββββββββββββββββββ
β β β
βββββββββΌββββββββ βββββββββΌββββββββ βββββββββΌββββββββ
β Prisma ORM β β BullMQ Queue β β Groq & JinaAI β
β (PostgreSQL) β β (Upstash Redis)β β (AI & Vectors)β
βββββββββ¬ββββββββ βββββββββ¬ββββββββ βββββββββββββββββ
β β
βββββββββΌββββββββ βββββββββΌβββββββββββββββββββ
β pgvector β β Background Workers β
β (Vector Search)β β ββ Job Matching Worker β
βββββββββββββββββ β ββ Notification Worker β
ββββββββββββββββββββββββββββ
- JWT-based cookie authentication with secure login/register/logout
- Role-based access control β
StudentandRecruiterroles - Password hashing using bcrypt
- PDF upload support via Multer
- AI-powered resume parsing β extracts skills, projects, work experience, and achievements from uploaded PDFs using Groq LLM (
llama-3.3-70b-versatile) - Automatically parses unstructured date formats from LLM output
- Recruiters can create and manage job postings
- Students can browse all jobs or view specific job details
- Each job stores a 768-dimensional vector embedding of its description for semantic matching
- Vector embeddings generated via the Jina AI Embeddings API (
jina-embeddings-v2-base-en) - Cosine similarity search powered by
pgvectoron PostgreSQL (calculated as1 - (resume.embedding <=> job.embedding) AS similarity) - Hard constraint filtering (location, experience, role) applied before ranking β ensuring efficient index usage
- Returns the top N most relevant candidates per job posting
- BullMQ background workers process matches asynchronously
- WhatsApp notifications sent automatically via
whatsapp-web.jsto matched candidates, complete with local QR code session management (.wwebjs_auth) - Rate-limited worker with exponential backoff for production readiness
- Workers run via Upstash Redis for serverless-friendly queue management
- Unit tests for services, middleware, and utilities
- Integration tests for auth flows and API endpoints
- Test coverage reporting via
@vitest/coverage-v8
- Dockerized architecture with multi-stage builds (
Dockerfile) - Railway deployment config out-of-the-box (
railway.toml) supporting multiple services natively
internship_placement/
βββ prisma/
β βββ schema.prisma # Database schema (User, Resume, Job, Application)
β βββ seed-test-data.ts # Database seeding script
βββ src/
β βββ index.ts # Express app entry point & route definitions
β βββ controllers/
β β βββ register.controller.ts # User registration + resume upload
β β βββ login.controller.ts # JWT login
β β βββ logout.controller.ts # Session logout
β β βββ jobs.controller.ts # CRUD for job postings
β β βββ matches.controller.ts # Trigger & retrieve top job matches
β βββ middleware/
β β βββ auth.ts # JWT verification middleware
β β βββ role.ts # Role-based access (isRecruiter, isStudent)
β β βββ upload.ts # Multer file upload configuration
β βββ services/
β β βββ embedding.service.ts # xAI/Grok vector embedding generation
β β βββ job.service.ts # Job business logic
β β βββ whatsapp.service.ts # WhatsApp message sender (mock)
β βββ workers/
β β βββ start.ts # Worker process entry point
β β βββ jobMatching.worker.ts # pgvector similarity search worker
β β βββ notification.worker.ts # WhatsApp notification dispatch worker
β βββ lib/
β β βββ db.ts # Prisma client singleton
β β βββ queue.ts # BullMQ queue definitions
β β βββ redis.ts # Upstash Redis connection
β βββ utils/
β βββ resumeParser.ts # AI-powered PDF resume parser
β βββ utils.ts # bcrypt helpers
βββ tests/
β βββ unit/ # Unit tests (services, middleware, utils)
β βββ integration/ # Integration tests (auth, API flows)
β βββ helpers/ # Test utilities and mocks
βββ vitest.config.ts # Vitest test runner configuration
βββ tsconfig.json # TypeScript compiler options
βββ package.json
| Layer | Technology |
|---|---|
| Runtime | Node.js + TypeScript 5.9 |
| Framework | Express 5 |
| ORM | Prisma 6 (with preview postgresqlExtensions) |
| Database | PostgreSQL + pgvector extension |
| Queue | BullMQ + Upstash Redis (rediss://) |
| AI/ML | Groq LLM (Parse) & Jina AI (Embeddings) |
| Auth | JWT (jsonwebtoken) + bcrypt |
| File Upload | Multer (PDF resume parsing) |
| PDF Parsing | pdf-parse |
| Testing | Vitest + Supertest + Coverage (V8) |
- Node.js β₯ 18
- PostgreSQL with the
pgvectorextension enabled (recommended: Neon) - Redis instance (recommended: Upstash for serverless)
- Groq API key for LLM-based resume parsing
- HuggingFace API key for embedding generation
git clone https://github.com/Nikhil/internship_placement.git
cd internship_placement
npm installCreate a .env file in the project root:
# Database (Neon PostgreSQL with pgvector)
DATABASE_URL="postgresql://user:pass@host-pooler.region.aws.neon.tech/dbname?sslmode=require"
DIRECT_URL="postgresql://user:pass@host.region.aws.neon.tech/dbname?sslmode=require"
# Redis (Upstash β rediss:// for TLS)
REDIS_URL="rediss://default:your-token@your-endpoint.upstash.io:6379"
# AI / LLM
GROQ_API="your-groq-api-key"
HF_API_KEY="your-huggingface-api-key"
JINA_API_KEY="your-jina-api-key"
# Auth
SECRET="your-jwt-secret"
# Server (optional)
PORT=3000# Generate Prisma client
npx prisma generate
# Run migrations
npx prisma migrate dev
# (Optional) Seed test data
npm run seedYou need two terminals running:
# Terminal 1: Start the API server (hot-reload)
npm run dev# Terminal 2: Start background workers (job matching + WhatsApp notifications)
npm run workersThe API will be available at http://localhost:3000.
When the workers start, a QR code will appear in the terminal. Scan it with WhatsApp:
- Open WhatsApp on your phone
- Go to Settings β Linked Devices β Link a Device
- Scan the QR code from the terminal
- The session is saved locally in
.ww_auth/β you won't need to scan again unless the session expires
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /register |
β | Register a new user (with resume for students) |
| POST | /login |
β | Login and receive JWT cookie |
| POST | /logout |
β | Invalidate session |
| Method | Endpoint | Auth | Role | Description |
|---|---|---|---|---|
| GET | /jobs |
β | Any | List all job postings |
| GET | /jobs/:id |
β | Any | Get a specific job by ID |
| GET | /jobs/my |
β | Any | List jobs created by the user |
| POST | /jobs |
β | Recruiter | Create a new job posting |
| GET | /jobs/:id/matches |
β | Any | Get top matching candidates |
| Method | Endpoint | Auth | Role | Description |
|---|---|---|---|---|
| GET | /analytics/me |
β | Stud. / Recr. | Fetch user-specific analytics directly from DB |
# Run all tests
npm test
# Run tests in watch mode
npm run test:watch
# Run tests with coverage report
npm run test:coverageThe test suite includes:
- Unit tests β Embedding service, WhatsApp service, middleware (auth & role), resume parser
- Integration tests β Full auth flow, job CRUD via Supertest
sequenceDiagram
participant R as Recruiter
participant API as Express API
participant Q as BullMQ Queue
participant JW as Job Matching Worker
participant DB as PostgreSQL + pgvector
participant NW as Notification Worker
participant S as Student (WhatsApp)
R->>API: POST /jobs (create job)
API->>DB: Store job + generate embedding
API->>Q: Enqueue job-matching task
Q->>JW: Process job matching
JW->>DB: Cosine similarity search (with hard filters)
DB-->>JW: Top N candidates
JW->>Q: Enqueue notifications for each match
Q->>NW: Process notification
NW->>S: Send WhatsApp message
- Recruiter creates a job β A 768-dim embedding is generated from the job description via Jina AI.
- Job Matching Worker picks up the task, runs a pgvector cosine similarity query (using the
<=>operator for distance) against all student resume embeddings, applying hard constraints (location, experience level) first. - Top N matches are enqueued as notification tasks.
- Notification Worker sends WhatsApp messages to matched students with job details and a similarity score.
The database is structured around five core models:
| Model | Purpose |
|---|---|
| User | Students and Recruiters with auth credentials |
| Resume | Parsed resume data with vector embedding |
| Job | Job postings with vector embedding |
| Project | Student projects (linked to Resume) |
| WorkExperience | Work history entries (linked to Resume) |
| Application | Tracks student applications to jobs |
- A Railway account
- Your project pushed to a GitHub repository
- WhatsApp authenticated locally (
.ww_auth/session folder exists)
You need two services from the same repo:
| Service | Start Command | Purpose |
|---|---|---|
| api | npm start |
Express REST API server |
| workers | npm run start:workers |
BullMQ job matching + WhatsApp notifications |
npm run buildThis runs prisma generate + tsc, compiling TypeScript to dist/.
- Go to railway.app β New Project
- Select Deploy from GitHub Repo β choose your repository
- Railway will auto-detect the
Dockerfileand build the image
In Railway dashboard β Variables, add all your .env variables:
REDIS_URL=rediss://default:token@endpoint.upstash.io:6379
DATABASE_URL=postgresql://...
DIRECT_URL=postgresql://...
GROQ_API=your-key
HF_API_KEY=your-key
JINA_API_KEY=your-key
SECRET=your-jwt-secret
PORT=3000
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
- In the same Railway project, click + New β GitHub Repo β same repository
- Rename this service to
workers - In Settings β Deploy, set the Start Command to:
npm run start:workers - Copy the same environment variables to this service
- Attach a Volume at mount path
/app/.ww_authfor WhatsApp session persistence
Since you can't scan a QR code on Railway:
- Authenticate locally first by running
npm run workersand scanning the QR code - A
.ww_auth/folder will be created in your project root - Upload the contents to the Railway volume attached to the workers service
Push to your GitHub main branch β Railway will automatically build and deploy both services.
git add .
git commit -m "Add Railway deployment config"
git push origin main- Health check:
curl https://<your-app>.railway.app/β should returnWELCOME TO Careernest! - Railway logs: Check for
[Redis] Connectedand[WhatsApp] Client connected and ready - Test API: Run curl commands against your Railway URL instead of
localhost:3000
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the ISC License.
Built with β€οΈ by Nikhil for smarter placements.