Skip to content
MW.

01HealthcareMarketplace SaaSUS Market

Physician Employment

A three-sided recruitment platform connecting US physicians and nurse/allied professionals with employers — public job portal, recruiter portal, and admin console on one Turborepo monorepo.

Visit physemp.com
PhysEmp candidate database onboarding graphic explaining specialty and location search with expandable result rows
In-product onboarding graphic for the recruiter candidate database01 / 01

Overview

PhysEmp is a healthcare recruitment platform serving three distinct audiences through dedicated applications: healthcare professionals browsing and applying to jobs, recruiters and HR teams posting jobs and searching a candidate database, and platform administrators managing taxonomy, feeds, campaigns, and analytics.

The problem

Physician and nurse/allied recruitment in the US is slow, high-touch, and fragmented. Employers need qualified candidates filtered by specialty, visa status, and location; candidates need trustworthy job listings they can search by specialty and commute distance; and the operations team needs to syndicate jobs from external HRIS/ATS systems without drowning in dirty data.

Context

The product modernizes a long-running recruitment business (operating since 2003) into a self-serve platform: SEO-friendly public job hierarchies for inbound candidates, a subscription-style recruiter portal with a searchable candidate database, and an internal console that automates the previously manual work of feed ingestion, matching, and outreach.

My role

Full-stack engineer across the monorepo — NestJS backend modules organized by consumer panel, plus all three Next.js frontends and the shared package.

  • Built XML job-feed import/export pipelines with per-client field mapping, ingestion audit logs, and automated sync-summary and error-report emails
  • Implemented recruiter–candidate matching, favorites/notes/hidden/recent workflows, and a dedicated matches portal
  • Developed distance-based job search using MapBox geocoding and location normalization, including city cleanup tooling
  • Engineered automated job alerts: cron-driven matching against seeker preferences, SendGrid delivery, and event webhook tracking for opens/clicks/bounces
  • Built guest (unauthenticated) application flows, profile-completion email campaigns, and newsletter tooling for admins
  • Wired analytics tracking across the funnel — profile views, job post views, candidate database clicks, CV downloads, login audit logs

Architecture & technical decisions

Turborepo monorepo
Four apps — a NestJS 11 REST API and three Next.js 15 (App Router, React 19) frontends — sharing one packages/shared library with subpath exports for hooks, utils, API clients, and types.
Panel-oriented backend
37 NestJS modules / 38 controllers / 52 services organized into shared, superadmin-panel, clients-panel, and job-seeker-panel layers following a Controller → Service → Prisma → PostgreSQL pattern, with class-validator DTOs.
Auth & sessions
Passport JWT on the API with a @Public() decorator for open endpoints; next-auth manages frontend sessions and attaches Bearer tokens through a shared authenticated base query that handles 401 redirects uniformly.
Data model
56 Prisma models covering clients, teams, seekers, job posts, specialty/city geography, applications, recruiter workflow entities, analytics tables, feed configurations, and campaign/email-bounce records.
Frontend patterns
RTK Query feature slices per domain, Zustand for UI state, React Hook Form + Zod validation, TanStack Table for dense admin grids, Tiptap rich text for job posts, ECharts/Recharts reporting, Google Maps clustering for geo results.

Key features

  • External job-feed ingestion

    Clients register XML feed URLs from their HRIS/ATS; the system parses, maps fields, creates/updates/archives jobs, flags unmapped specialties and cities for admin review, and emails a confirmation or failure report per run.

  • Recruiter candidate database

    Searchable seeker database with specialty/location/visa/position filters, CV downloads via S3 signed URLs, favorites, hidden lists, private notes, recently-viewed history, and click/CV-download usage logs.

  • Automated job alerts & campaigns

    Scheduled jobs match new postings to seeker preferences and send templated SendGrid alerts; webhook events track opens and clicks while bounce handling protects sender reputation.

  • Guest applications

    Candidates can apply without an account; submissions land as GuestApplication records routed into the recruiter review flow, lowering friction at the top of the funnel.

  • SEO job architecture

    Public hierarchies like /physician/jobs/[specialty]/[jobType]/[location] generate crawlable, keyword-relevant landing pages for every specialty-location combination.

Challenges & solutions

  1. Challenge 01

    External feeds arrive with inconsistent schemas and messy taxonomy — unmapped specialties would silently break search relevance.

    Solution — Designed an ingestion pipeline with per-feed field mapping, an audit log per run, and staging tables for unknown specialties/cities so admins can resolve mappings before data reaches production search.

  2. Challenge 02

    Distance-based search ('jobs within N miles') requires reliable coordinates across millions of US location strings.

    Solution — Centralized geocoding behind a dedicated module with normalized City/LocationArea models and cleanup scripts, letting both job posts and seeker preferences share one consistent geographic spine.

  3. Challenge 03

    Three separate frontends risked divergent auth handling, types, and data-fetching behavior.

    Solution — Moved cross-app concerns into packages/shared — authenticated base queries with JWT injection and 401 redirects, typed hooks, and shared constants — enforced by convention before any app-specific code is written.

Outcome & what makes it interesting

In active development as the company's core platform, replacing manual recruitment workflows with self-serve tools — dozens of modules, 56 database models, and three production frontends shipped inside a single coherent monorepo.

  • Modeling a real three-sided marketplace (seeker × recruiter × admin) without letting any panel's concerns leak into another's code path
  • Treating external HRIS feeds as a first-class product surface — mapping, auditing, and human-in-the-loop resolution instead of optimistic parsing
  • Building SEO job hierarchies programmatically so every specialty/location pairing becomes a landing page