Documentation Index
Fetch the complete documentation index at: https://mintlify.com/tech-dipesh/yeti-Jobs/llms.txt
Use this file to discover all available pages before exploring further.
Yeti Jobs is structured as a classic layered PERN architecture where each tier has a single, well-defined responsibility. The React client handles all rendering and user interaction. Requests flow through versioned Express API routes, pass through a middleware chain, and are processed by a service layer before reaching PostgreSQL. File uploads bypass the API and land directly in Supabase Storage, with the returned public URL persisted in the database. Background work — job expiry, email dispatch — is handled by dedicated services that run alongside the main HTTP server.
System Overview
┌─────────────────────────────────────────────────────────────┐
│ CLIENT LAYER │
│ React 19 + Vite │ TailwindCSS │ React Router 7 │
│ Axios (API calls) │ Context API (auth state) │
└───────────────────────────┬─────────────────────────────────┘
│ HTTP / JSON (cookies for JWT)
▼
┌─────────────────────────────────────────────────────────────┐
│ API LAYER (/api/v1) │
│ Express 5 │ Helmet │ CORS │ Rate Limit │
│ Cookie-Parser │ Multer (file upload) │
│ │
│ Routes: │
│ /jobs /users /companies │
│ /applications /admin /notifications │
│ /health /swagger │
│ │
│ Middleware Chain: │
│ isLoggedIn → isJobSeeker | isEmployee | isAdmin │
│ Zod validation schemas │
└──────────┬───────────────────────────┬──────────────────────┘
│ │
▼ ▼
┌──────────────────────┐ ┌───────────────────────────────────┐
│ SERVICE LAYER │ │ BACKGROUND SERVICES │
│ Business logic, │ │ node-cron (midnight job expiry) │
│ pdf-parse, │ │ Nodemailer (email verification, │
│ Groq AI (ATS score) │ │ password reset) │
└──────────┬───────────┘ └───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ DATABASE LAYER │
│ PostgreSQL 15 │ pg driver (connection pool) │
│ 10 tables │ GIN full-text index │ Triggers │ Enums │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ EXTERNAL STORAGE & SERVICES │
│ Supabase Storage (résumés, profile pictures, logos) │
│ Supabase PostgreSQL (production database host) │
└─────────────────────────────────────────────────────────────┘
Layer Reference
React Frontend
The client is a single-page application built with React 19 and bundled by Vite 7. TailwindCSS 4 handles all styling with a utility-first approach, and React Router 7 manages client-side routing with lazy-loaded routes using React.lazy() and Suspense to keep the initial bundle small.
State management relies on the Context API — a central useAuth context stores the verified status, logged-in flag, and user role so that every component can read authentication state without prop drilling or redundant network calls.
HTTP communication is handled exclusively through Axios, configured with the VITE_SERVER_URL base URL. Cookies are sent with every request (withCredentials: true) so the JWT stored in the HTTP-only cookie is included automatically.
Client-side RBAC enforces route guards before the server is ever reached:
- Guests cannot visit recruiter or admin pages.
- Recruiters cannot access job-seeker–specific pages or admin dashboards.
- Admins have broader access but are still restricted from guest and recruiter action pages.
Key frontend libraries: react-router 7.13.1, axios 1.13.5, react-toastify 11.0.5, react-select 5.10.2, react-phone-number-input 3.4.16, @vercel/analytics, @vercel/speed-insights.
Express API — MVC Backend
The backend is an Express 5 application structured with a strict MVC pattern. All routes are prefixed with /api/v1 for versioning, making it straightforward to introduce a /api/v2 namespace without breaking existing clients.
Middleware chain (applied in order):
cors — whitelists only CLIENT_BASE_URL; blocks all other origins.
express-rate-limit — 200 requests per minute globally; 2 requests per minute on password-reset routes.
helmet — strips X-Powered-By, adds security response headers (CSP, HSTS, X-Frame-Options, etc.).
express.json() — parses JSON request bodies.
cookie-parser — makes the JWT cookie available as req.cookies.
authUserMiddleware (isLoggedIn) — verifies the JWT on protected route groups.
isAdminMiddleware — additional role check applied before the /admin route group.
multer — handles multipart/form-data for résumé and profile-picture uploads.
Route groups:
| Prefix | Auth required | Role guard |
|---|
/api/v1/jobs | Partial | Public read, auth for write |
/api/v1/users | Partial | Public for login/signup |
/api/v1/companies | Yes | isLoggedIn |
/api/v1/applications | Yes | isLoggedIn |
/api/v1/admin | Yes | isLoggedIn + isAdmin |
/api/v1/notifications | Yes | isLoggedIn |
/api/v1/health | No | None |
/api/v1/swagger | No | None |
Validation is applied at three levels — Zod schemas on the server, client-side form validation, and database-level constraints — so that invalid data cannot persist even if the upper layers are bypassed.
The global error handler returns consistent JSON shapes: { message: string } with appropriate 2xx, 4xx, or 5xx status codes.
PostgreSQL Database
The database uses raw SQL through the pg connection pool — no ORM. This gives full control over query plans, index usage, and schema design.
Schema: 10 tables
| Table | Purpose |
|---|
users | All user accounts (job seekers, recruiters, admins) |
companies | Company profiles |
jobs | Job listings with tsvector full-text search column |
applications | Job applications with status tracking |
saved_jobs | Bookmarked jobs per user |
user_companies_follows | Company follow relationships |
email_verified | Email verification and password-reset tokens |
ats_score | AI résumé scoring results (JSONB feedback) |
user_education | User education history |
notifications | Per-user notification events |
PostgreSQL features in use:
- Enums at the database level for
role, education, job_type, application_status, and email_verification_type — invalid values are rejected before they reach application code.
- GIN index on
jobs.search_title (a tsvector column) for performant full-text job search, replacing slow ILIKE prefix scans.
- B-tree indexes on
companies.name and email_verified.verified_code for fast lookups on the most queried columns.
- Trigger on
jobs that auto-populates the search_title tsvector column on every INSERT or UPDATE, keeping the search index current without application-level intervention.
ON DELETE CASCADE — deleting a user automatically removes their email verifications, follows, ATS scores, and notifications.
ON DELETE RESTRICT — tables where linked data must be explicitly cleared before the parent record can be deleted.
SELECT EXISTS(SELECT 1 ...) pattern for condition checks — returns a boolean without fetching any rows.
- Pagination on all list endpoints to bound response payload size.
Queries are reviewed with EXPLAIN ANALYZE to catch slow plans before reaching production.
Supabase — Storage and Hosted Database
Supabase serves two distinct roles in Yeti Jobs.
1. File storage — résumés, profile pictures, and company logos are uploaded via the @supabase/supabase-js SDK directly into Supabase Storage buckets. After a successful upload the SDK returns a public URL which is saved to the corresponding column in PostgreSQL (users.resume_url, users.profile_pic_url, companies.logo_url). The Express server acts as a proxy: Multer holds the file in memory, the service layer streams it to Supabase, and the URL is persisted atomically with the profile update.
2. Production database host — in production the PostgreSQL database is hosted on Supabase. The backend connects through Supabase’s IPv6-compatible connection pooler using the URL_SUPABASE_CONNECT string and authenticates with ANON_KEY_SUPABASE. The SSL option { rejectUnauthorized: false } is enabled to allow inbound connections from Render’s network.
Configuration env vars:
URL_SUPABASE_CONNECT=https://your-project-id.supabase.co
ANON_KEY_SUPABASE=your_supabase_anon_key
Third-Party Integrations
Groq AI — ATS Résumé Scoring
Résumé files uploaded by job seekers are parsed with pdf-parse to extract plain text. That text is sent to the Groq API (GROK_API) via the openai SDK (Groq exposes an OpenAI-compatible interface). The API returns a numeric ATS score and structured feedback which are stored in the ats_score table as score (integer) and feedback (JSONB).
Nodemailer — Transactional Email
Email verification codes and password-reset tokens are delivered via Nodemailer using SMTP. Configuration requires three env vars:
NODEMAILER_MY_EMAIL=you@example.com
NODEMAILER_MY_PASSWORD=your_app_password
NODEMAILER_MY_HOST=smtp.gmail.com
Before initiating an SMTP connection, the server validates the recipient’s email domain using Node’s built-in dns/promises module — this prevents unnecessary SMTP round-trips for addresses with non-existent domains.
The password-reset route is strictly rate-limited to 2 requests per minute to prevent abuse.
node-cron — Scheduled Job Expiry
A cron task registered in src/services/cron-task.js fires every night at midnight (00:00). It executes a single UPDATE query that sets is_job_open = 'closed' on all jobs rows where created_at is older than 30 days and the job is still marked open. The number of affected rows is logged after each run. Because the update is a single query it does not hold a table lock for a meaningful duration even at scale.
Schedule: 0 0 * * * (every day at 00:00)
Action: UPDATE jobs SET is_job_open = 'closed'
WHERE created_at < NOW() - INTERVAL '30 days'
AND is_job_open = 'active'
Swagger UI
Interactive API documentation is served from /api/v1/swagger using swagger-ui-express and a YAML spec loaded with yamljs. This provides a live, explorable reference for all backend endpoints including request parameters, body schemas, and response formats.
Security Architecture
The platform applies defence-in-depth across every layer.
| Control | Implementation |
|---|
| Authentication | JWT in HTTP-only cookie, signed with JSON_SECRET_KEY, configurable expiry via MAXAGE |
| Authorisation | RBAC enforced at client route guards, Express middleware, and PostgreSQL constraints |
| Rate limiting | 200 req/min globally; 2 req/min on password-reset via express-rate-limit |
| Security headers | helmet (CSP, HSTS, X-Frame-Options, and more) |
| CORS | Only CLIENT_BASE_URL is whitelisted |
| Input validation | Zod schemas server-side + database-level enums and constraints |
| Password hashing | bcryptjs |
| File upload | multer guards on file field name; unsupported field names return 401 |
Deployment Topology
Internet
│
├──▶ Vercel (React SPA) ──────────────────────▶ Render (Express API)
│ VITE_SERVER_URL │
│ ├──▶ Supabase PostgreSQL
│ └──▶ Supabase Storage
│
└──▶ Render /api/v1/swagger (Swagger UI, public)
- Frontend — deployed to Vercel; every push to
main triggers an automatic redeploy. Set VITE_SERVER_URL in the Vercel dashboard.
- Backend — deployed as a Web Service on Render. All backend env vars are configured in the Render dashboard. The Render free tier sleeps after 15 minutes of inactivity; a keep-alive cron ping runs every 15 minutes to prevent cold starts.
- Database & Storage — both hosted on Supabase. The backend connects via the connection pooler URL with SSL enabled.