Case study · Jun – Jul 2026
WorkforceIQ
Workforce management for a multi-brand restaurant group — 370+ staff, end to end.
- What it is
- A full-stack workforce management platform
- For
- A multi-brand, multi-outlet restaurant group
- Scale
- 370+ staff, complete employee lifecycle
- My role
- Designed and built it
- Timeline
- Jun – Jul 2026
- Built at
- K. Girdharlal International Ltd. / Bookends Hospitality
The problem
The group runs several restaurant brands across multiple outlets, and employs more than 370 people between them. When I arrived, the work of running that workforce was manual. Rosters were built by hand each week from the previous week’s sheet. Staff records lived scattered across whatever file or notebook had been convenient at the time. Attendance, leave, and allocation each had their own informal process.
That approach doesn’t fail loudly. It fails quietly — a person rostered in two places, a day off that gets lost between versions, a new hire nobody adds to the sheet. Every one of those becomes someone’s problem at the start of a shift, which is the worst possible time to discover it.
I designed and built WorkforceIQ to cover the whole lifecycle in one place: directory, shift scheduling, attendance, leave, allocation, and demand forecasting.
What I built
- Auto-scheduling engine. Generates weekly rosters from three rotating shift templates across seven days. Per-staff manual overrides survive future rotations, so a one-off arrangement isn't silently overwritten next week.
- Role-based access control. A database-backed role→permission matrix across six account types, editable live rather than hardcoded and redeployed.
- Attendance, leave & allocation. Modules covering attendance, leave requests, staff allocation, and a performance dashboard — the day-to-day operations of the group.
- WhatsApp & email notifications. Rosters and updates reach staff on WhatsApp via the Meta Graph API, with email as the second channel.
- Demand forecasting service. A Python/FastAPI microservice that forecasts staffing demand with ML, so scheduling can be planned against expected load.
- Light/dark theming. Full light and dark theming built on semantic design tokens rather than one-off color values.
Architecture
It’s a pnpm + Turborepo monorepo: a NestJS API, a Next.js 14 + React 18 frontend, and a shared TypeScript package so both sides derive the same types and logic from one source. Redis handles job queues, and a separate Python/FastAPI microservice does the demand forecasting.
Two decisions in there are worth explaining, because they were choices rather than defaults.
Raw parameterized SQL on PostgreSQL 16, rather than an ORM. Scheduling queries get genuinely complicated, and I wanted to read the exact query that runs rather than infer it from an abstraction. Parameterized means the safety is still there. When something is slow or wrong, I’m debugging SQL I wrote, not SQL a library generated.
Forecasting as a separate FastAPI service, rather than inside the API. The ML tooling lives in Python, and I didn’t want model changes coupled to API deploys. The API asks the service what next week looks like; the scheduling engine plans against the answer. The two move independently.
It’s deployed to production on Render — API, web, and Redis — with PostgreSQL hosted on Supabase.
Security
This system holds records for hundreds of real people, so the auth work wasn’t decoration.
- Single-use refresh-token rotation, SHA-256 hashed. A refresh token works exactly once and is replaced on use. Tokens are stored hashed, so a database read doesn’t hand over usable credentials. If a stolen token is replayed, the reuse is detectable rather than silent.
- Rate limiting. The login endpoint is on the public internet. Rate limiting an API and writing a firewall rule are the same instinct in different vocabulary — decide what’s allowed, decide how much, and assume something will try more.
- Forced password change on first login. Admin-set initial passwords get shared over WhatsApp and never changed. Forcing the change closes that window, at the cost of mild annoyance once.
- Audit trails on migrations. When someone asks why a record looks the way it does, the system can answer without archaeology.
Data honesty
WorkforceIQ was built for my employer and holds real employee data. I imported and cleaned that data through numbered, reversible SQL migrations with audit trails — not a one-off script run from a laptop. Numbered, so the order is knowable. Reversible, because “we need to undo that import” is a real sentence and the answer should never be that we can’t.
For the same reason, this case study covers architecture and features only. There are no screenshots and no demo link here, because that data isn’t mine to show.
What I’d improve next
Three things I’d change, in the order I’d do them:
- Model scheduling overrides as an append-only log from day one. I retrofitted audit trails, and retrofitting history onto a table that’s been mutated in place means the history starts the day you added it — not the day the data started mattering.
- Move shared logic into the shared package earlier. Roster generation lived in the API longer than it should have, which meant the frontend couldn’t preview a roster without a round trip.
- Put real integration tests around the notification path. WhatsApp delivery via the Meta Graph API is the part most likely to fail silently in production, and silent failure in a system people rely on for their shift times is the worst kind.
Stack
- TypeScript
- Next.js
- React
- NestJS
- Node.js
- PostgreSQL
- Redis
- Python
- FastAPI
- Tailwind CSS
- TanStack Query
- Turborepo