HireHeaven is a modern job portal built with a microservices architecture and a polished Next.js frontend. It connects job seekers, recruiters, and AI-driven career tools into one production-style platform for discovering jobs, managing applications, posting opportunities, analyzing resumes, and generating personalized career guidance.
The project was designed to demonstrate real-world engineering depth: authenticated user flows, role-based experiences, external service integrations, AI-assisted features, service-oriented backend boundaries, and the operational scaffolding (gateway, observability, tests, CI) that separates a demo from a production-style system.
- Role-based experience for job seekers, recruiters, and admins.
- JWT access + refresh token authentication with Redis-backed revocation (logout invalidates both tokens server-side).
- Job browsing, searching, application tracking, and recruiter job posting, with paginated listings.
- Rich Job Detail page: recruiter-defined hiring pipeline (an ordered, expandable stepper), live applicant/CTC/apply-by stat cards, eligibility criteria and custom application questions, tags, required skills, and downloadable JD attachments.
- Recruiter job-posting form with required-field validation (client and server), a round-by-round hiring pipeline builder, and chip inputs for skills/tags/questions.
- Application Tracker (
/tracker): applicants watch their application move through the recruiter's defined rounds on a visual timeline (filterable by company, status, and job type); recruiters bulk-advance applicants through rounds from a "Manage Applicants" panel, which automatically resolves the application to Hired/Rejected once a terminal round is reached. - Company management for recruiter workflows.
- Admin dashboard (
/admin, role-gated) for moderation: list/deactivate any job, list/remove any company, list all users. - AI-powered resume ATS analysis and AI-powered career path recommendations.
- Subscription and payment flow powered by Razorpay.
- File upload support for resumes, profile photos, and company logos.
- Event-driven backend components using Kafka and Redis.
- Redis-backed rate limiting on every service (stricter limits on auth, payment, and AI endpoints) to protect against brute force and abuse.
- Redis cache-aside layer for job listings, job details, company details, and user profiles, with explicit invalidation on writes.
- Zod request validation at every mutating endpoint across all services.
- A single API gateway in front of all five services, so the frontend (and any other client) talks to one origin.
- Structured logging (pino), full Prometheus metrics scraping (
/metrics),/healthchecks on every service, and pre-provisioned Grafana monitoring dashboards. - Shared
@hireheaven/commonpackage (npm workspaces) for error handling, Redis clients, rate limiting, caching, validation, logging, metrics, and tokens β no copy-pasted utilities. - Automated tests (Vitest) and a GitHub Actions CI pipeline that typechecks, builds, and tests every workspace.
- Cloudinary-based media storage and Gemini-powered AI responses.
- One-command local startup via Docker Compose (Redis, Kafka/Zookeeper, Prometheus, Grafana, the gateway, all five services, and the frontend).
The system is a Next.js frontend, an API gateway, and five backend services, all managed as an npm workspaces monorepo. The frontend and every other client talk only to the gateway; the gateway proxies to the right service by path prefix. Each service owns its own responsibility and communicates through HTTP, a shared Postgres database, and asynchronous infrastructure where needed.
flowchart LR
U[User / Recruiter] --> F[Next.js Frontend]
F --> GW[API Gateway :8080]
GW --> A[Auth Service]
GW --> US[User Service]
GW --> J[Job Service]
GW --> P[Payment Service]
GW --> X[Utils Service]
A --> DB[(Neon PostgreSQL)]
J --> DB
US --> DB
P --> DB
GW --> R[(Redis)]
A --> R
US --> R
J --> R
P --> R
X --> R
J --> K[(Kafka)]
A --> K
X --> K
X --> C[(Cloudinary)]
X --> G[(Gemini)]
P --> RZ[(Razorpay)]
Redis serves three purposes here: rate limiting (every service, including the gateway, throttles requests per client IP, with tighter windows on login/register/forgot, payment checkout/verify, and the AI/upload endpoints), cache-aside reads (job listings, job details, company details, and user profiles, invalidated on writes), and auth state (refresh token storage and access/refresh token revocation on logout).
sequenceDiagram
participant User as User / Recruiter
participant Frontend as Next.js Frontend
participant Gateway as API Gateway
participant Auth as Auth Service
participant UserSvc as User Service
participant JobSvc as Job Service
participant UtilSvc as Utils Service
participant PaySvc as Payment Service
User->>Frontend: Sign in / browse jobs / manage profile
Frontend->>Gateway: POST /api/auth/login or /register
Gateway->>Auth: proxied request
Auth-->>Frontend: access token + refresh token + account response
Frontend->>Gateway: GET /api/user/me with access token
Gateway->>UserSvc: proxied request
Frontend->>Gateway: Search, view, apply, or manage jobs
Gateway->>JobSvc: proxied request
Frontend->>Gateway: Upload files or request AI career / resume analysis
Gateway->>UtilSvc: proxied request
Frontend->>Gateway: Create checkout and verify payment
Gateway->>PaySvc: proxied request
Note over Frontend,Gateway: On a 401, the frontend calls /api/auth/refresh once and retries automatically
| Layer | Technologies |
|---|---|
| Frontend | Next.js 16, React 19, TypeScript, Tailwind CSS 4 |
| UI Primitives | Radix UI, Lucide React, next-themes, react-hot-toast |
| HTTP Client | Axios (with an automatic refresh-token interceptor) |
| Auth | JWT access + refresh tokens, Redis-backed revocation, cookies |
| Gateway | Express 5 + http-proxy-middleware |
| Backend | Node.js, Express 5, TypeScript, npm workspaces |
| Validation | Zod |
| Database | Neon PostgreSQL |
| Cache / Rate Limiting | Redis (cache-aside reads + sliding-window rate limiting) |
| Messaging | Kafka / KafkaJS |
| Observability | pino (structured logs), Prometheus (metrics scraper on :9090), Grafana (dashboards on :3001), /health & /metrics |
| Testing / CI | Vitest, GitHub Actions |
| File Storage | Cloudinary |
| Payments | Razorpay |
| AI Services | Gemini |
| Mail / Notifications | Nodemailer |
job-portal/
βββ frontend/ # Next.js application
β βββ src/
β β βββ app/ # App Router pages and routes
β β βββ components/ # Shared UI and feature components
β β βββ context/ # Global app state, service URLs, refresh-token interceptor
β β βββ lib/ # Shared utilities
β β βββ type.ts # Shared TypeScript types
β βββ public/ # Static assets
β βββ package.json
βββ packages/
β βββ common/ # @hireheaven/common β shared across every service
β βββ src/ # ErrorHandler, TryCatch, redis client, rate limiter,
β # cache, zod validate middleware, logger, metrics,
β # health check, role guard, access/refresh tokens,
β # Kafka client (SASL_SSL-aware)
βββ services/
β βββ gateway/ # API gateway β single origin for the frontend
β βββ auth/ # Authentication microservice
β βββ job/ # Job and company microservice
β βββ payment/ # Razorpay payment microservice
β βββ user/ # Profile, skills, and application microservice
β βββ utils/ # AI, uploads, and notification utilities
βββ monitoring/
β βββ prometheus/ # Prometheus configuration & scrape targets
β βββ grafana/ # Pre-provisioned datasources & microservice dashboards
βββ .github/workflows/ci.yml # Typecheck + build + test every workspace
βββ Dockerfile.service # Shared Dockerfile for every backend service + gateway
βββ docker-compose.yml # One-command local stack
βββ package.json # npm workspaces root
- Node.js 20+ recommended
- npm 10+ recommended
- Docker + Docker Compose (only needed if you want Redis/Kafka/Prometheus/Grafana running locally via the provided
docker-compose.yml) - Neon PostgreSQL database (free tier works β the services use
@neondatabase/serverless, which speaks Neon's HTTP protocol, so a generic local Postgres container will not work as a drop-in replacement) - Redis instance (local via Docker Compose, or a hosted instance such as Upstash/Redis Cloud)
- Kafka broker (local via Docker Compose, or a Kafka-protocol-compatible hosted instance such as Redpanda Serverless/Confluent Cloud)
- Cloudinary account
- Razorpay account
- Gemini API key
First, copy each .env.example to .env and fill in your own values (see step 4 of Manual Setup below for the exact commands and which values are required). Then:
docker compose up --buildThis starts Redis, Kafka/Zookeeper, Prometheus, and Grafana first, then builds and runs auth (5000), utils (5001), user (5002), job (5003), payment (5004), the gateway (8080), and the frontend (3000).
- Frontend: http://localhost:3000
- API Gateway: http://localhost:8080
- Prometheus UI: http://localhost:9090
- Grafana Dashboards: http://localhost:3001 (login:
admin/admin)
- Clone the repository.
- Install all workspace dependencies from the repo root (this installs the frontend, the gateway, all five services, and
@hireheaven/commonin one pass, and symlinks the shared package):
npm install- Build the shared package once before running any service in dev mode (their
npm run devscripts assume@hireheaven/common'sdist/already exists):
npm run build --workspace=@hireheaven/common- Each service ships a
.env.example(committed) β copy it to.env(gitignored, never committed) and fill in your own values:
cp services/auth/.env.example services/auth/.env
cp services/job/.env.example services/job/.env
cp services/user/.env.example services/user/.env
cp services/payment/.env.example services/payment/.env
cp services/utils/.env.example services/utils/.env
cp frontend/.env.example frontend/.envPORT, Redis_url (redis://localhost:6379), and Kafka_Broker (localhost:9092) already have working local defaults. You need to fill in: a JWT_SEC (any random string, but the same value in auth/job/payment/user since tokens issued by auth must verify in the others β generate one with node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"), DB_URL (Neon), Cloudinary keys, Razorpay_Key/Razorpay_Secret, and GOOGLE_API_KEY. Never commit a .env file with real values β only .env.example (placeholders) is tracked in git.
5. Start Redis and Kafka yourself (or via docker compose up redis zookeeper kafka) if you're running services with npm run dev instead of Docker Compose.
PORT=5000
DB_URL=your_neon_postgres_connection_string
UPLOAD_SERVICE=http://localhost:5001
JWT_SEC=your_jwt_secret
Kafka_Broker=localhost:9092
KAFKA_USERNAME=
KAFKA_PASSWORD=
KAFKA_SASL_MECHANISM=scram-sha-256
Frontend_Url=http://localhost:3000
Redis_url=redis://localhost:6379PORT=5002
DB_URL=your_neon_postgres_connection_string
UPLOAD_SERVICE=http://localhost:5001
JWT_SEC=your_jwt_secret
Redis_url=redis://localhost:6379PORT=5003
DB_URL=your_neon_postgres_connection_string
UPLOAD_SERVICE=http://localhost:5001
JWT_SEC=your_jwt_secret
Kafka_Broker=localhost:9092
KAFKA_USERNAME=
KAFKA_PASSWORD=
KAFKA_SASL_MECHANISM=scram-sha-256
Redis_url=redis://localhost:6379PORT=5004
Razorpay_Key=your_razorpay_key_id
Razorpay_Secret=your_razorpay_secret
DB_URL=your_neon_postgres_connection_string
JWT_SEC=your_jwt_secret
Redis_url=redis://localhost:6379PORT=5001
CLOUD_NAME=your_cloudinary_cloud_name
API_KEY=your_cloudinary_api_key
API_SECRET=your_cloudinary_api_secret
Kafka_Broker=localhost:9092
KAFKA_USERNAME=
KAFKA_PASSWORD=
KAFKA_SASL_MECHANISM=scram-sha-256
SMTP_USER=your_smtp_email@gmail.com
SMTP_PASS=your_smtp_app_password
GOOGLE_API_KEY=your_gemini_api_key
Redis_url=redis://localhost:6379KAFKA_USERNAME/KAFKA_PASSWORD are only needed for a hosted broker that requires SASL_SSL auth (e.g. Redpanda Serverless, Confluent Cloud) β leave both blank for a local/plaintext broker (e.g. via docker-compose). KAFKA_SASL_MECHANISM defaults to scram-sha-256 but can be overridden (e.g. scram-sha-512, plain) to match whatever your broker requires. auth and job publish to the send-mail topic (password resets, application status emails); utils consumes it and actually sends the mail via Nodemailer. If Kafka is unreachable or unconfigured, these two email notifications silently don't fire β everything else in the app is unaffected.
The gateway has no .env file committed (it holds no secrets) β configure it via environment variables, all optional with sane local defaults:
PORT=8080
Redis_url=redis://localhost:6379
AUTH_SERVICE_URL=http://localhost:5000
UTILS_SERVICE_URL=http://localhost:5001
USER_SERVICE_URL=http://localhost:5002
JOB_SERVICE_URL=http://localhost:5003
PAYMENT_SERVICE_URL=http://localhost:5004The frontend talks to the gateway by default. Set an individual NEXT_PUBLIC_*_SERVICE_URL only if you want to bypass the gateway and hit one service directly (e.g. while debugging):
NEXT_PUBLIC_API_GATEWAY_URL=http://localhost:8080
# NEXT_PUBLIC_AUTH_SERVICE_URL=http://localhost:5000
# NEXT_PUBLIC_UTILS_SERVICE_URL=http://localhost:5001
# NEXT_PUBLIC_USER_SERVICE_URL=http://localhost:5002
# NEXT_PUBLIC_JOB_SERVICE_URL=http://localhost:5003
# NEXT_PUBLIC_PAYMENT_SERVICE_URL=http://localhost:5004For a deployed environment, point NEXT_PUBLIC_API_GATEWAY_URL at your deployed gateway URL.
Each service can be started independently. Build @hireheaven/common first (step 3 in Manual Setup above) β every service imports it.
cd frontend
npm run devcd services/gateway && npm run dev
cd services/auth && npm run dev
cd services/job && npm run dev
cd services/payment && npm run dev
cd services/user && npm run dev
cd services/utils && npm run devFrom the repo root, this builds @hireheaven/common and every workspace that has a build script (all five services, the gateway, and the frontend):
npm run build --workspaces --if-presentThen start each with npm start from its own directory (or node dist/index.js for the backend services/gateway, npm start for the frontend).
Every workspace (the shared package, all five services, and the gateway) has a Vitest suite covering validation schemas, rate limiting, caching, token issuance/revocation, and role guards β using an in-memory fake Redis client, so tests run without any real infrastructure.
npm run typecheck --workspaces --if-present # tsc --noEmit everywhere
npm run build --workspaces --if-present # compile everything, including the frontend
npm run test --workspaces --if-present # run every Vitest suite.github/workflows/ci.yml runs the same three commands (in that order, since services depend on @hireheaven/common's build output) on every push and pull request to main.
Every backend service and the gateway share Dockerfile.service at the repo root (the build context is the repo root, not the individual service folder, since they all depend on the @hireheaven/common workspace package); the frontend keeps its own self-contained Dockerfile.
- Frontend: deploy to Vercel (native Next.js support) or as the
frontendcontainer to any Docker host; setNEXT_PUBLIC_API_GATEWAY_URLto your deployed gateway's URL. - Gateway: deploy as its own container (
docker build -f Dockerfile.service --build-arg SERVICE=gateway .) to Render, Railway, Fly.io, or any container platform. SetAUTH_SERVICE_URL,UTILS_SERVICE_URL,USER_SERVICE_URL,JOB_SERVICE_URL, andPAYMENT_SERVICE_URLto each service's deployed URL, andRedis_urlto your managed Redis instance. - Backend services: deploy each (
auth,user,job,payment,utils) as its own container the same way (--build-arg SERVICE=<name>). Set each service's environment variables (from the tables above) in that platform's dashboard/secrets manager rather than committing real values. - Redis: use a managed instance (Upstash, Redis Cloud, or your platform's managed Redis add-on) and point every service's
Redis_urlat it β it's shared state for rate limiting, caching, and auth token revocation, so all services must point at the same instance. - Kafka: use a Kafka-protocol-compatible managed instance (Redpanda Serverless, Confluent Cloud) on
auth,job, andutils, settingKafka_BrokerplusKAFKA_USERNAME/KAFKA_PASSWORD(andKAFKA_SASL_MECHANISMif your provider doesn't use thescram-sha-256default). All Kafka calls are wrapped in try/catch and log-and-continue on failure, so the app still runs (without password-reset/application-status emails) if Kafka is unreachable or left unconfigured. - Database: Neon PostgreSQL is already serverless and requires no separate hosting β just use your project's connection string as
DB_URLin each deployed service. docker-compose.ymlat the repo root is intended for local development; for production, run each service as its own deployment so they can scale and fail independently, which is the point of a microservices architecture.
Deployed service URLs (Render for the backend services/gateway, Vercel for the frontend):
| Service | URL |
|---|---|
| Gateway | https://ai-microservices-job-portal-gateway.onrender.com |
| Auth | https://ai-microservices-job-portal-nl5v.onrender.com |
| Utils | https://ai-microservices-job-portal.onrender.com |
| User | https://ai-microservices-job-portal-user.onrender.com |
| Job | https://ai-microservices-job-portal-job.onrender.com |
| Payment | https://ai-microservices-job-portal-payment.onrender.com |
| Frontend | https://ai-microservices-job-portal-fronten.vercel.app |
These are set in each platform's dashboard (never committed to .env). On top of the secrets already configured (DB, Cloudinary, Kafka, etc. β see the tables above), set the following cross-service URLs so the deployed services can actually reach each other:
| Service (dashboard) | Env var | Value |
|---|---|---|
| Gateway | AUTH_SERVICE_URL |
https://ai-microservices-job-portal-nl5v.onrender.com |
| Gateway | UTILS_SERVICE_URL |
https://ai-microservices-job-portal.onrender.com |
| Gateway | USER_SERVICE_URL |
https://ai-microservices-job-portal-user.onrender.com |
| Gateway | JOB_SERVICE_URL |
https://ai-microservices-job-portal-job.onrender.com |
| Gateway | PAYMENT_SERVICE_URL |
https://ai-microservices-job-portal-payment.onrender.com |
| Auth | UPLOAD_SERVICE |
https://ai-microservices-job-portal.onrender.com (utils) |
| Auth | Frontend_Url |
https://ai-microservices-job-portal-fronten.vercel.app |
| Job | UPLOAD_SERVICE |
https://ai-microservices-job-portal.onrender.com (utils) |
| User | UPLOAD_SERVICE |
https://ai-microservices-job-portal.onrender.com (utils) |
| Frontend (Vercel) | NEXT_PUBLIC_API_GATEWAY_URL |
https://ai-microservices-job-portal-gateway.onrender.com |
Payment and Utils don't call any other service, so they need no cross-service URL beyond what's already in their tables above. .github/workflows/keep-alive.yml pings the gateway and all five backend services' /health endpoints every 10 minutes to keep Render's free tier from spinning them down after ~15 minutes idle β this reduces cold starts significantly but isn't a hard guarantee (Render still sleeps services during redeploys/maintenance, and a gateway request still fails if a specific downstream service happens to be asleep between pings). Vercel's frontend doesn't need this β it isn't on a sleeping free-tier container.
All endpoints below are reachable through the gateway at its own origin (default http://localhost:8080) using the same paths, e.g. http://localhost:8080/api/auth/login.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/auth/register |
Register a user with profile upload support |
| POST | /api/auth/login |
Authenticate and issue an access + refresh token pair |
| POST | /api/auth/refresh |
Exchange a refresh token for a new access + refresh token pair (rotates the refresh token) |
| POST | /api/auth/logout |
Revoke the current access token and refresh token |
| POST | /api/auth/forgot |
Start password reset flow |
| POST | /api/auth/reset/:token |
Complete password reset |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/user/me |
Get the authenticated user profile |
| GET | /api/user/admin/users |
Admin only. List all users, paginated |
| GET | /api/user/:userId |
Get public user details (cached) |
| PUT | /api/user/update/profile |
Update basic profile fields |
| PUT | /api/user/update/pic |
Update profile image |
| PUT | /api/user/update/resume |
Update resume file |
| POST | /api/user/skill/add |
Add a skill |
| PUT | /api/user/skill/delete |
Remove a skill |
| POST | /api/user/apply/job |
Apply for a job β seeds the application's first hiring-round stage automatically |
| GET | /api/user/application/all |
List the current user's applications, paginated (includes company name/logo and job type, for the Tracker) |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/job/admin/jobs |
Admin only. List every job (active and inactive), paginated |
| PUT | /api/job/admin/jobs/:jobId/active |
Admin only. Activate or deactivate any job |
| GET | /api/job/admin/companies |
Admin only. List every company with its job count, paginated |
| POST | /api/job/company/new |
Create a new company |
| DELETE | /api/job/company/:companyId |
Delete a company (owner or admin) |
| GET | /api/job/company/all |
List recruiter companies |
| GET | /api/job/company/:id |
Get company details (cached) |
| POST | /api/job/new |
Create a job posting, including its hiring rounds, tags, skills, and application questions (owner or admin) |
| PUT | /api/job/:jobId |
Update a job posting; hiring rounds are only replaceable while no applications exist yet, to keep applicant progress from pointing at a round that got reshuffled out from under it (owner or admin) |
| POST | /api/job/:jobId/attachments |
Upload a JD PDF (or other document) to a job posting (owner or admin) |
| GET | /api/job/all |
Get active jobs, paginated and filterable by title/location (cached) |
| GET | /api/job/:jobId |
Get a single job β including its rounds, tags, skills, questions, attachments, and applicant count (cached) |
| GET | /api/job/application/:jobId |
Get applications for a job, paginated (owner or admin) |
| PUT | /api/job/application/update/:id |
Update an application's overall status (owner or admin) |
| GET | /api/job/application/:id/summary |
Resolve who an application belongs to, for authorization checks (applicant, job owner, or admin) |
| GET | /api/job/application/:id/history |
Get an application's full round-by-round stage history, oldest first (applicant, job owner, or admin) |
| PUT | /api/job/application/stage |
Bulk-advance one or more applications to a hiring round with a status and optional note; auto-resolves the application to Hired/Rejected on a terminal round (owner or admin) |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/payment/checkout |
Create a Razorpay checkout order |
| POST | /api/payment/verify |
Verify payment success |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/utils/upload |
Upload or replace media in Cloudinary |
| POST | /api/utils/career |
Generate career guidance using Gemini |
| POST | /api/utils/resume-analyser |
Analyze a resume for ATS compatibility |
Every service (including the gateway) exposes:
| Method | Endpoint | Description |
|---|---|---|
| GET | /health |
Liveness check β service name + timestamp |
| GET | /metrics |
Prometheus-format metrics (request duration histogram, request counter, in-flight gauge, cache hit/miss, rate limit counters, process metrics) |
| Service | Port / URL | Description |
|---|---|---|
| Prometheus | http://localhost:9090 |
Scrapes /metrics from Gateway and all 5 microservices every 5s |
| Grafana | http://localhost:3001 |
Pre-provisioned dashboards for service health, RPS, P95 latencies, error rates, RSS memory, and Redis cache performance (credentials: admin / admin) |
Audited against Chrome DevTools Lighthouse & Core Web Vitals across Desktop and Mobile:
| Metric | Score / Value | Target / Standard | Status |
|---|---|---|---|
| Lighthouse Performance | 98 / 100 | π’ Optimal | |
| Lighthouse Accessibility | 96 / 100 |
|
π’ Optimal |
| Largest Contentful Paint (LCP) | 1.1s |
|
π’ Fast |
| Interaction to Next Paint (INP) | 48ms |
|
π’ Responsive |
| Cumulative Layout Shift (CLS) | 0.008 |
|
π’ Stable |
| First Contentful Paint (FCP) | 0.7s |
|
π’ Optimal |
Stat cards (applicants, CTC, apply-by countdown), an expandable hiring-process stepper driven entirely by rounds the recruiter defines, eligibility criteria, tags, skills, and JD attachments.
Required-field validation, a round-by-round hiring pipeline builder, and chip inputs for skills/tags/questions.
Applicants watch their application move through the recruiter's rounds on a live timeline, filterable by company, status, and job type.
Bulk-advance selected applicants to a round with a status and optional note β the final round auto-resolves the application to Hired or Rejected.
- Add Kubernetes manifests / Helm chart for production orchestration beyond the local Docker Compose setup.
- Add distributed tracing (e.g. OpenTelemetry) across the gateway and services, correlated by request ID.
- Add sorting and advanced filters (salary range, job type, work location) to the job search flow.
- Use a proper secrets manager (e.g. your deployment platform's env var store, or Doppler/Vault) instead of local
.envfiles as the project grows beyond a single deployer. - Add end-to-end tests (Playwright/Cypress) covering the full signup β apply β payment flow through the gateway.
- Add token-bucket or per-user (not just per-IP) rate limiting for authenticated endpoints.
- Coordinating a multi-service architecture without losing feature cohesion.
- Keeping recruiter, job seeker, and admin flows separate while sharing a common UX and auth model.
- Managing authenticated requests across services with short-lived access tokens, refresh rotation, and Redis-backed revocation.
- Fronting five independently deployable services with a single gateway without breaking existing route contracts.
- Sharing code (error handling, Redis, validation, logging, metrics, tokens) across services via an npm workspaces package instead of copy-pasting it five times.
- Handling binary uploads for resumes, logos, and profile media.
- Returning structured JSON from AI models reliably enough for UI rendering.
- Integrating payment, messaging, storage, and AI systems into one workflow.
- Modeling a recruiter-defined, per-job hiring pipeline (rather than a fixed global one) while keeping a separate, simpler application status untouched for backward compatibility β new stage-tracking data is additive, not a breaking migration of the original schema.
- Making multi-table writes (a job plus its rounds/tags/skills/questions) atomic on a serverless HTTP-only Postgres driver that doesn't support interactive transactions the way a pooled connection would.
- Microservices design, service boundary definition, and API gateway patterns.
- Access/refresh token authentication with server-side revocation, and role-based authorization (jobseeker/recruiter/admin).
- Frontend state orchestration with React context, including a transparent token-refresh interceptor.
- Database modeling for job portals, applications, and user skills, including a zero-downtime enum migration (adding the
adminrole). - Practical integration of third-party APIs and hosted services.
- Testable Node.js services: dependency-injectable Redis clients, pure validation schemas, and fast unit tests with no live infrastructure.
- Real-world TypeScript patterns across a monorepo, frontend and backend alike.
- It combines a polished consumer-facing frontend with a production-style distributed backend, including a gateway, observability, and CI.
- It solves a real hiring workflow end to end, from discovery to application to payment, with admin moderation on top.
- It adds AI features that are useful rather than ornamental: resume analysis and career guidance.
- It demonstrates integration depth across databases, storage, caching, messaging, payments, and authentication.
- It is recruiter-friendly because the domain, architecture, and feature scope are immediately understandable β and it holds up under scrutiny: tests pass, builds are clean, and the deployment story is documented.
Licensed under the ISC License.











