Sajad

EsenceLab

Multi-service product system: TypeScript API with authentication and RBAC, Python AI service, PostgreSQL.

Problem

Students can't map resumes to skill gaps, recruiters screen manually, admins lack one control surface. The engineering problem underneath: three deployables (web, API, AI service) share one data model, and the boundaries hold auth, role-gated onboarding, resume parsing, and job matching.

Constraints

Architecture

Frontend (Next.js)
  ↓
API (Express: JWT + RBAC, rate limits, request IDs)
  ↓
PostgreSQL (Supabase: users, resumes,
  candidates, jobs, applications)
  ↓
AI service (FastAPI: /ai/parse-resume, /ai/match)

Service calls are synchronous fetch with an internal service token and 8–12s timeouts, no queues yet, documented as the next step. Matching is deterministic first (overlap×0.55 + TF-IDF×0.45 with fallbacks); the LLM path is optional with local fallback.

Implementation

Auth with JWT plus role-based access across student/recruiter/admin, including a gated recruiter flow (request → admin approve → temp password → login). Resume upload (PDF, 4MB cap) with structured parsing. Jobs CRUD plus applications, skill-overlap matching with explanations. Per-endpoint in-memory metrics and /api/health; Docker per service; CI with RBAC smoke and stress suites running against a mocked Supabase.

Engineering decisions

Challenges

The API grew into a ~6K-line single file that still passes its tests, the classic "works but can't evolve" state. The fix is mechanical, not clever: extract dependency-free modules first (done for auth roles), convert closures to arguments for the rest, and keep the RBAC suite green at every step. The split plan with verification gates lives in docs/ARCHITECTURE.md.

Benchmarks

RBAC smoke + stress suites green (SUPABASE_MOCK=1). Latency thresholds exist (slow-endpoint 1200ms, p95 alert 2500ms) but are alerts, not measurements, so no latency numbers are cited.

Trade-offs

Lessons

Product engineering is boundary work: API contracts, auth semantics, data ownership, and knowing which features to leave out of the narrative. A monolith you can split step by step beats a microservice diagram you can't deploy.

Source

github.com/sajadinmaker/esencelab