An intelligent job tracking platform that fetches jobs from external sources, matches them with user resumes using AI, and provides a conversational AI assistant for natural language job search.
https://job-apex-p5a60p17c-sg172003s-projects.vercel.app/
- Email:
test@gmail.com - Password:
test@123
┌─────────────────────────────────────────────────────────────────────────────┐
│ JobApex Architecture │
└─────────────────────────────────────────────────────────────────────────────┘
┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐
│ Frontend │◄───────►│ Backend │◄───────►│ External Job API │
│ (React) │ HTTP │ (Fastify) │ HTTP │ (Adzuna/Mock) │
└──────┬───────┘ └──────┬───────┘ └──────────────────────┘
│ │
│ ▼
│ ┌─────────────────┐
│ │ In-Memory │
│ │ Storage │
│ │ (Users, Jobs, │
│ │ Applications) │
│ └─────────────────┘
│ │
│ ▼
│ ┌─────────────────┐
│ │ AI Services │
│ │ │
│ │ ┌───────────┐ │
└──────────────►│ │ LangChain │ │◄──────► OpenAI API
│ │ (Matching)│ │
│ └───────────┘ │
│ ┌───────────┐ │
│ │ LangGraph │ │◄──────► OpenAI API
│ │(Assistant)│ │
│ └───────────┘ │
└─────────────────┘
- React 19 with TypeScript
- Vite for build tooling
- Tailwind CSS for styling
- shadcn/ui components
- Context API for state management
- Node.js with Fastify
- @fastify/cors for CORS
- @fastify/multipart for file uploads
- pdf-parse for PDF text extraction
- OpenAI API for AI features
- LangChain pattern: OpenAI chat completions with structured prompts
- LangGraph pattern: State-based conversation flow with intent detection
- GPT-3.5-turbo for job matching and assistant
- Node.js 18+
- npm or yarn
- OpenAI API key
- Clone the repository:
git clone <repo-url>
cd jobapex- Set up the backend:
cd backend
cp .env.example .env
# Edit .env and add your OPENAI_API_KEY
npm install
npm start- Set up the frontend:
cd app
cp .env.example .env
# Edit .env to point to backend URL
npm install
npm run dev- Open http://localhost:5173 in your browser
Backend (.env)
OPENAI_API_KEY=your_openai_api_key_here
PORT=3001
Frontend (.env)
VITE_API_URL=http://localhost:3001/api
The job matching system uses a LangChain-inspired approach with structured prompts:
// AI Job Matching Prompt Design
const prompt = `You are an AI job matching system. Analyze how well the candidate's resume matches the job description.
Resume:
${resumeText}
Job Title: ${job.title}
Company: ${job.company}
Description: ${job.description}
Required Skills: ${job.skills.join(', ')}
Provide a match score from 0-100 and a brief explanation.
Respond in JSON format:
{
"score": number,
"explanation": "string",
"matchingSkills": ["skill1", "skill2"],
"keywordOverlap": ["keyword1", "keyword2"]
}`;Why this works:
- Structured JSON output enables programmatic use
- Temperature 0.3 ensures consistent scoring
- Fallback keyword matching handles API failures
- Batch processing (5 jobs at a time) for performance
The AI assistant implements a LangGraph-style state machine:
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Start │────►│Intent Detect │────►│ Router │
└─────────────┘ └──────────────┘ └──────┬──────┘
│
┌────────────────────────────────────┼────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌──────────┐ ┌──────────┐
│ Filter Update │ │Job Search│ │ Help │
└───────┬───────┘ └──────────┘ └──────────┘
│
▼
┌───────────────┐
│ Update UI │
│ State │
└───────────────┘
Intent Detection:
filter_update: User wants to change filters ("Show remote jobs")job_search: User is searching for specific jobs ("Find React jobs")help: User needs product assistance ("How do I upload my resume?")general: General conversation
State Management:
- Conversation history maintained per session
- Current filter state passed to AI for context
- Tool calling updates frontend filters directly
-
Primary Method: OpenAI GPT-3.5-turbo analysis
- Resume text analyzed against job description
- Skills, experience, and keywords compared
- Score 0-100 with explanation
-
Fallback Method: Keyword matching
- Extract skills from resume
- Count matching job skills
- Calculate percentage match
- Batch Processing: 5 jobs processed in parallel
- Caching: Match scores stored with jobs
- Lazy Loading: Scores calculated on demand
- Timeout Handling: 30-second timeout per batch
- Green (>70%): Strong match - apply with confidence
- Yellow (40-70%): Moderate match - review details
- Gray (<40%): Weak match - consider other options
User clicks Apply
│
▼
External link opens (new tab)
│
▼
User applies on external site
│
▼
User returns to JobApex
│
▼
Popup appears: "Did you apply?"
│
├──► Yes, Applied ──► Save to tracking
│
├──► No, just browsing ──► Dismiss
│
└──► Applied earlier ──► Save with earlier date
- User never returns: Popup shown on next focus event
- User closes tab: Pending application stored in sessionStorage
- Multiple applications: Each tracked separately
- Duplicate applications: Checked before saving
- Browser extension: More invasive, harder to deploy
- Email confirmation: Slower, requires email integration
- Manual tracking only: Less user-friendly
Chosen approach: Focus-based detection balances automation with user control.
Selected: Floating chat bubble (bottom-right)
Reasoning:
- Always accessible without leaving current view
- Non-intrusive to job browsing
- Familiar pattern (similar to Intercom, Drift)
- Easy to minimize/close
Alternative considered: Collapsible sidebar
- Takes up more screen space
- Better for complex workflows
- Overkill for this use case
- Current: Batch processing (5 jobs at a time)
- Optimization: Add Redis caching for match scores
- Further: Pre-compute scores when resume uploaded
- Current: In-memory storage (not scalable)
- Required: Database migration (PostgreSQL/MongoDB)
- Session: Redis for session storage
- AI: Rate limiting and request queuing
- In-Memory Storage: Data lost on server restart
- No Real Job API: Using mock data (Adzuna integration ready)
- Single User Session: No multi-device sync
- PDF Parsing: Basic text extraction only
- No Email Notifications: Application reminders not implemented
- Database Integration: PostgreSQL with Prisma ORM
- Real Job APIs: Adzuna, Indeed, LinkedIn integration
- Advanced Resume Parsing: Skills extraction, experience calculation
- Email Notifications: Application deadlines, interview reminders
- Mobile App: React Native companion app
- Collaboration: Share applications with mentors/friends
- Analytics: Application success rates, time-to-offer metrics
jobapex/
├── app/ # Frontend (React)
│ ├── src/
│ │ ├── components/ # React components
│ │ ├── contexts/ # Auth & Job contexts
│ │ ├── services/ # API service
│ │ ├── types/ # TypeScript types
│ │ ├── App.tsx # Main app
│ │ └── main.tsx # Entry point
│ ├── public/
│ └── package.json
│
├── backend/ # Backend (Fastify)
│ ├── server.js # Main server
│ ├── package.json
│ └── .env.example
│
└── README.md # This file
POST /api/auth/login- User loginPOST /api/auth/logout- User logout
POST /api/resume/upload- Upload resume (PDF/TXT)GET /api/resume- Get resume info
GET /api/jobs- Get all jobs with match scores
GET /api/applications- Get user applicationsPOST /api/applications- Create applicationPATCH /api/applications/:id- Update application status
POST /api/ai/assistant- Send message to AI
MIT License - feel free to use this project for learning or as a starting point for your own job tracking application.
Built as an assignment submission demonstrating:
- Full-stack development (React + Node.js)
- AI integration (LangChain/LangGraph patterns)
- Product thinking (UX decisions, edge cases)
- System design (architecture, scalability considerations)