Case study · 2026
Nexus
ApexMedix — multi-tenant operations platform for private medical event companies
Role
End-to-end product & engineering
Stack
Nuxt 3 · Vue · Fastify · PostgreSQL · Stripe · Docker

Overview
It started while I was working in the private medical sector. Most companies I saw were running events, staff, compliance, and hiring across spreadsheets, WhatsApp, and a handful of disconnected tools. There was no single system for day-to-day operations.
I built Nexus to be that system: one branded staff portal per organisation, with rostering, HR, compliance, recruitment, and training in one place — and extra modules they can turn on as they grow.
Role
I designed and built the product end to end: product direction, multi-tenant architecture, the Fastify API, the Nuxt staff portal, permissions, billing/modules, and production deployment.
The problem
Event medical companies typically juggle:
- Staffing events and tracking who is on which shift
- Compliance (licences, DVLA, medicines, equipment)
- HR, documents, and onboarding
- Hiring into the same operational pool they roster from
Without a central system, data is duplicated, access is messy, and nothing looks like their company. Nexus needed to feel like each tenant’s own product, not a generic SaaS dashboard.
What I built
A multi-tenant staff portal. Each organisation gets its own branding, login, roles, and data. Core operations sit in one app; optional modules are purchased and gated.
Core
- Event rostering and staff directory
- Staff onboarding (accounts, temp passwords, first-login / 2FA)
- Roles, grades, and permission-based navigation
- HR: people, leave, finance, documents
- Compliance: DVLA, medicines, equipment
- Company settings, audit log, custom portal domains
Modules
- Recruitment — jobs, forms, applications, GDPR retention/anonymise
- Interview scheduling — shared availability, booking/amend links, confirmations
- Training bookings — courses, learners, timetable
- WhatsApp Business — shift notifications via Meta Cloud API
Hire → account
When an application is marked hired, staff can import the applicant and create a portal account (name, email, phone, address from the form; role and grade chosen in a dialog). The new user is linked back to the application.
Architecture
| Layer | Choice |
|---|---|
| Frontend | Nuxt (App Router-style pages), Vue, Tailwind, shadcn/Reka UI |
| API | Fastify, file-based /api routes, PostgreSQL (pg) |
| Auth | HttpOnly cookies, JWT, tenant resolution by host / custom domain / slug |
| Tenancy | Row-level tenantId on every resource; CORS and cookies for custom domains |
| Access | RBAC + purchasable modules (requireTenantModule) |
| Files | Private S3/Spaces; served only through the API |
| Billing | Stripe; module catalogue with monthly pricing |
| Deploy | Frontend on Vercel; API as a long-running Node service (Docker / Coolify), auto-migrate on boot |
The frontend never talks to the database. SSR and the browser call the public API with tenant headers and credentials. TRUST_PROXY and explicit CORS origins matter because tenants can sit on *.apexmedix.net or a custom host like ops.example.co.uk.
Hard problems
Tenancy and custom domains
Resolve the tenant from hostname, forwarded host, or slug, then scope every query. Cookies and CORS have to work across the marketing/admin hosts, tenant subdomains, and custom CNAMEs.
Session performance
Owner permission repair used to run on every authenticated request and could lock the database. That work was moved off the session path so logins and API calls stay fast.
Recruitment → staff
Applications and users were separate. Hire now optionally creates a real staff user, maps form fields, requires role/grade, and records hiredUserId so the same person isn’t imported twice.
Interview pool
Availability is shared, not per-applicant. Published slots have to round-trip as real ISO timestamps (Postgres timestamptz vs the browser) or the calendar shows “Invalid Date”.
Auth UX
First login, forgot/reset, and forced 2FA had to match the branded login shell and actually complete (middleware was bouncing “change password” back to the dashboard).
Outcome
Nexus is a production operations platform, not a demo: live tenants, module billing, branded portals, and a path from applicant to rostered staff. The idea was simple — one place to run the company — and the work was making tenancy, permissions, and those operational workflows hold up in production.
Stack
Nuxt 3 · Vue · Tailwind · Fastify · PostgreSQL · JWT cookies · Stripe · AWS S3 / DigitalOcean Spaces · Meta WhatsApp Cloud API · Vercel · Docker / Coolify