Privacy-Preserving Career Matching Connecting candidates with opportunities based on intent, not just keywords. Mutual matching with identity protection until both parties express interest.
SABER employs a modern, scalable architecture with aggressive caching strategies and privacy-first design principles.
graph TB
subgraph Client_Layer ["Client Applications"]
Candidate["Candidate App<br/>(React + Vite)"]
Dashboard["Recruiter Dashboard<br/>(React + Vite)"]
end
subgraph API_Gateway ["API Layer"]
Express["Express.js API<br/>(TypeScript)"]
Auth["OAuth 2.0 Handler"]
RateLimit["Rate Limiter"]
end
subgraph Business_Logic ["Business Logic"]
MatchEngine["Intent Matching Engine"]
SwipeLogic["Swipe & Match Logic"]
ConstraintFilter["Constraint Validator"]
end
subgraph Data_Layer ["Data Persistence"]
Postgres[("PostgreSQL<br/>(Neon)")]
Redis[("Redis Cache<br/>(Upstash)")]
Cloudinary["Cloudinary<br/>(Image Storage)"]
end
subgraph External_Services ["External Services"]
Google["Google OAuth"]
GitHub["GitHub OAuth"]
LinkedIn["LinkedIn OAuth"]
end
Candidate --> Express
Dashboard --> Express
Express --> Auth
Express --> RateLimit
RateLimit --> MatchEngine
RateLimit --> SwipeLogic
MatchEngine --> ConstraintFilter
SwipeLogic --> Postgres
ConstraintFilter --> Postgres
Express --> Redis
Express --> Cloudinary
Auth --> Google
Auth --> GitHub
Auth --> LinkedIn
sequenceDiagram
participant C as Candidate
participant API as SABER API
participant DB as PostgreSQL
participant R as Redis Cache
participant Rec as Recruiter
C->>API: GET /jobs/feed
API->>R: Check cache
alt Cache Hit
R-->>API: Return cached feed
else Cache Miss
API->>DB: Query jobs + constraints
DB-->>API: Return filtered jobs
API->>R: Store in cache (60s TTL)
end
API-->>C: Return personalized feed
C->>API: POST /jobs/swipe (right)
API->>DB: Create swipe record
API->>DB: Create application
API->>DB: Check for reciprocal swipe
alt Mutual Match
DB-->>API: Recruiter swiped right
API->>DB: Create match record
API->>DB: Update application status
API-->>C: Match created!
API-->>Rec: Notify match
else No Match Yet
API-->>C: Swipe recorded
end
API->>R: Invalidate feed cache
| Layer | Technology | Purpose |
|---|---|---|
| Backend | Express.js + TypeScript | High-performance async API with type safety |
| Frontend | React 18 + TypeScript | Component-based UI with strict typing |
| Database | PostgreSQL (Neon) | Relational data with ACID guarantees |
| Cache | Redis (Upstash) | Sub-100ms response times for hot data |
| ORM | Prisma | Type-safe database access with migrations |
| State | TanStack Query | Intelligent client-side caching |
| Auth | OAuth 2.0 | Trusted identity via Google, GitHub, LinkedIn |
| Storage | Cloudinary | CDN-backed image delivery |
| Deployment | Vercel | Edge-optimized serverless functions |
| Styling | TailwindCSS | Utility-first responsive design |
- Node.js 18 or higher
- PostgreSQL database (Neon recommended)
- Redis instance (Upstash recommended)
- OAuth credentials (Google, GitHub, LinkedIn)
- Cloudinary account
1. Clone the repository
git clone https://github.com/sreecharan-desu/SABER.git
cd SABER2. Install dependencies
npm install3. Configure environment variables
Copy .env.example to .env and configure:
DATABASE_URL="postgresql://user:password@host:5432/database"
JWT_SECRET="your-secure-jwt-secret-min-32-chars"
REDIS_URL="rediss://default:password@host:6379"
GOOGLE_CLIENT_ID="your-google-client-id"
GOOGLE_CLIENT_SECRET="your-google-client-secret"
GITHUB_CLIENT_ID="your-github-client-id"
GITHUB_CLIENT_SECRET="your-github-client-secret"
LINKEDIN_CLIENT_ID="your-linkedin-client-id"
LINKEDIN_CLIENT_SECRET="your-linkedin-client-secret"
CLOUDINARY_CLOUD_NAME="your-cloud-name"
CLOUDINARY_API_KEY="your-api-key"
CLOUDINARY_API_SECRET="your-api-secret"4. Initialize database
npx prisma db push5. Start development server
npm run devAPI available at http://localhost:3000
SABER/
├── prisma/
│ ├── schema.prisma # Database schema with optimized indexes
│ └── migrations/ # Migration history
├── src/
│ ├── config/ # Configuration modules
│ │ ├── cloudinary.ts # Image upload configuration
│ │ ├── env.ts # Environment validation
│ │ ├── prisma.ts # Database client
│ │ └── redis.ts # Cache client
│ ├── controllers/ # Request handlers
│ │ ├── admin.controller.ts # Platform metrics
│ │ ├── auth.controller.ts # OAuth & JWT
│ │ ├── candidate.controller.ts # Bookmarks & applications
│ │ ├── job.controller.ts # Job feed & swipes
│ │ ├── match.controller.ts # Match & messaging
│ │ ├── recruiter.controller.ts # Company & job management
│ │ └── user.controller.ts # Profile updates
│ ├── middleware/
│ │ ├── auth.middleware.ts # JWT validation
│ │ ├── rateLimit.middleware.ts # Request throttling
│ │ └── upload.middleware.ts # Multipart form handling
│ ├── routes/ # API route definitions
│ ├── services/ # Business logic
│ │ ├── email.service.ts # Transactional emails
│ │ ├── github.data.service.ts # Skill extraction
│ │ ├── linkedin.data.service.ts # Profile enrichment
│ │ ├── oauth.providers.ts # OAuth integrations
│ │ └── user.service.ts # User management
│ ├── utils/
│ │ ├── cache.ts # Redis wrapper
│ │ ├── jwt.ts # Token generation
│ │ └── logger.ts # Structured logging
│ ├── app.ts # Express configuration
│ └── server.ts # Application entry point
├── scripts/
│ └── benchmark.ts # Performance testing
└── vercel.json # Deployment configuration
sequenceDiagram
participant U as User
participant FE as Frontend
participant API as SABER API
participant OAuth as OAuth Provider
participant DB as Database
U->>FE: Click "Login with Google"
FE->>OAuth: Redirect to OAuth consent
OAuth->>FE: Redirect with auth code
FE->>API: POST /auth/oauth/callback
API->>OAuth: Exchange code for token
OAuth-->>API: Access token + profile
API->>DB: Find or create user
DB-->>API: User record
API->>API: Generate JWT
API-->>FE: Return JWT + user data
FE->>FE: Store JWT in localStorage
FE->>API: Subsequent requests with JWT
API->>API: Validate JWT
API-->>FE: Protected resource
User
- Identity and profile information
- Role:
candidate,recruiter, oradmin - Intent statement and constraints
- Links to OAuth accounts
Company
- Recruiter-owned organization profile
- Branding assets (logo, cover image)
- Links to job postings
Job
- Problem statement and expectations
- Required skills and constraints
- Active/inactive status
Swipe
- Directional preference (left/right)
- Links user to job or recruiter to candidate
- Timestamp for match detection
Match
- Created on mutual right-swipes
- Reveals identities to both parties
- Enables direct messaging
Application
- Auto-created on candidate right-swipe
- Status tracking (pending, reviewing, interview, accepted, rejected)
- Managed by recruiters
GET /auth/oauth/callback OAuth provider callback
POST /auth/oauth/callback Exchange code for JWT
POST /auth/link-provider Link additional OAuth account
GET /auth/me Get current user profile
POST /user/intent Update candidate intent
POST /user/constraints Update user constraints
PUT /user/role Switch user role
GET /jobs/feed Personalized job feed
POST /jobs/swipe Swipe on job
POST /recruiters/company Create company profile
GET /recruiters/company Get company details
PUT /recruiters/company/images Upload branding assets
POST /recruiters/job Create job posting
GET /recruiters/jobs List all jobs
PUT /recruiters/job/:id Update job
DELETE /recruiters/job/:id Delete job
GET /recruiters/feed Get candidate feed
POST /recruiters/swipe Swipe on candidate
GET /recruiters/signals Get signals of interest
GET /candidates/bookmarks Get bookmarked jobs
POST /candidates/bookmarks Bookmark job
DELETE /candidates/bookmarks/:job_id Remove bookmark
GET /candidates/applications Get all applications
POST /candidates/applications Submit application
DELETE /candidates/applications/:id Withdraw application
GET /matches Get all matches
POST /matches/messages Send message
GET /admin/metrics Platform analytics
POST /admin/ai/keys Rotate AI API keys
| Resource | TTL | Invalidation Trigger |
|---|---|---|
| Company profiles | 60 minutes | Profile update, image upload |
| Job listings | 5 minutes | Job creation, update, deletion |
| User profiles | 60 seconds | Profile update, role change |
| Candidate feed | 60 seconds | Swipe action |
| Recruiter feed | 60 seconds | Swipe action |
| Signals of interest | 30 seconds | Swipe action |
-- Optimized for feed generation
CREATE INDEX idx_job_active_created ON Job(active, created_at);
-- Optimized for match detection
CREATE INDEX idx_swipe_target_direction ON Swipe(target_user_id, direction);
-- Optimized for application filtering
CREATE INDEX idx_application_job_status ON Application(job_id, status);
-- Optimized for candidate pagination
CREATE INDEX idx_user_role_id ON User(role, id);| Endpoint Category | Limit | Window |
|---|---|---|
| Global | 100 requests | 15 minutes |
| Authentication | 5 requests | 15 minutes |
| Swipe actions | 50 requests | 15 minutes |
| AI endpoints | 10 requests | 1 minute |
Average response times (production environment):
| Endpoint | Cold Start | Cached | Notes |
|---|---|---|---|
GET /health |
45ms | N/A | Health check |
GET /auth/me |
250ms | 50ms | User profile with OAuth accounts |
GET /recruiters/company |
230ms | 40ms | Company profile with images |
GET /recruiters/jobs |
235ms | 45ms | Job list with company data |
GET /recruiters/feed |
960ms | 50ms | Candidate matching algorithm |
GET /jobs/feed |
700ms | 50ms | Job matching algorithm |
GET /matches |
290ms | 60ms | Match retrieval with details |
Note: Cold start times represent first request without cache. Subsequent requests leverage Redis caching for sub-100ms responses.
Authentication & Authorization
- JWT-based stateless authentication
- OAuth 2.0 with trusted providers
- Role-based access control (RBAC)
Data Protection
- Identity masking until mutual match
- Encrypted database connections
- Secure environment variable management
Request Security
- Helmet.js security headers
- CORS configuration
- Rate limiting per endpoint category
- Request ID tracking for audit trails
Operational Security
- Daily swipe limits to prevent spam
- Unique constraint enforcement
- Transaction-based match creation
- Automatic cache invalidation
1. Connect repository to Vercel
2. Configure environment variables
- Navigate to Project Settings > Environment Variables
- Add all variables from
.env.example - Update
BASE_URLandFRONTEND_URLto production URLs
3. Deploy
vercel --prodProduction URL: https://saber-api-backend.vercel.app
1. Navigate to dashboard directory
cd saber-admin-dashboard2. Configure environment variables
Create .env in dashboard directory:
VITE_API_BASE_URL="https://saber-api-backend.vercel.app"
VITE_OAUTH_REDIRECT_URI="https://saber-admin-dashboard.vercel.app/auth/callback"
VITE_GOOGLE_CLIENT_ID="your-google-client-id"
VITE_GITHUB_CLIENT_ID="your-github-client-id"
VITE_LINKEDIN_CLIENT_ID="your-linkedin-client-id"3. Deploy
vercel --prodProduction URL: https://saber-admin-dashboard.vercel.app
npx tsx scripts/benchmark.tsOutput includes:
- Response times for all major endpoints
- Status codes and error rates
- Response data previews
- Average latency across all tests
Sample Output:
[Recruiter] GET /health | Status: 200 | Time: 45ms
[Recruiter] GET /auth/me | Status: 200 | Time: 250ms
[Recruiter] GET /recruiters/company | Status: 200 | Time: 230ms
[Recruiter] GET /recruiters/feed | Status: 200 | Time: 220ms
[Candidate] GET /jobs/feed | Status: 200 | Time: 218ms
Average Response Time: 443ms
1. Fork the repository
2. Create a feature branch
git checkout -b feature/your-feature-name3. Commit your changes
git commit -m 'Add: your feature description'4. Push to the branch
git push origin feature/your-feature-name5. Open a Pull Request
This project is licensed under the MIT License.
- Neon - Serverless PostgreSQL hosting
- Upstash - Redis caching infrastructure
- Vercel - Edge-optimized deployment platform
- Cloudinary - Image CDN and transformation
- Prisma - Next-generation ORM
- TanStack Query - Powerful async state management
For issues, questions, or feature requests, please open an issue on GitHub.
Live Demo: https://saber-admin-dashboard.vercel.app
API Health: https://saber-api-backend.vercel.app/health
Built for the future of intent-driven career matching