- Web
MaidMatch
A home-cleaning service in three projects: a booking site where customers book a cleaning in four steps with a price from the API, an admin dashboard to plan jobs and send the right person, and an API that owns every business rule.
- Personal project, full-stack demonstration
- 3 projects, 15 pages, 26 API endpoints
- 58 API unit tests and no lint problems
- Next.js, React, TypeScript, Tailwind CSS, Zustand, NestJS, Prisma, PostgreSQL, Socket.io

The problem
A home-cleaning service needs two things: customers who can book without a phone call, and a team that can plan the jobs, send the right person and follow payments. Without rules in one place, the same person gets two jobs at once, a cancelled job comes back, and the price shown to the customer differs from the price saved.
The approach
Three projects work together: a Next.js booking site, a Next.js admin dashboard and a NestJS API with PostgreSQL. The API owns every business rule, and both websites only display what it decides.
The booking site sends the booking to the API, which checks every field, computes the price from the hourly rate in the settings and saves the booking. It then pushes a notification to the signed-in admins. The dashboard reads and changes bookings only through API routes protected by a JWT, and every change is checked and written to an audit log.
Who uses it
- Customers
- Book a cleaning on the public site, with a price before they confirm.
- The team
- Plan the jobs, assign staff, follow requests and payments in the admin dashboard.
What it does
- A four-step booking form with a price estimate from the API, for one-time, weekly or every-two-weeks cleanings
- Every date handled in Paris time, so a booking made from abroad keeps the right hour
- A dashboard with revenue, bookings, new requests, completed jobs, live notifications and a search that ignores accents
- A fixed status order: requested, confirmed, assigned, completed, cancelled or refused
- Staff assignment that refuses people on leave and overlapping jobs
- Recurring bookings: completing one creates the next at the same Paris time
- Internal notes, a weekly planning and a staff catalogue with filters
- A staff profile with a printable sheet, and an audit log of every change
- A legal notice and a privacy page written for a demonstration
Where the engineering is
One price, computed in one place
At first the site and the API each had a copy of the price formula, so the price shown to a customer could differ from the price saved. Now the API gives the estimate, from the hourly rate in the settings, and a unit test proves that the estimate equals the price that is saved.
, Open the full-size image (opens in a new tab)Nobody booked twice at the same time
The first version had a single busy flag per person, which blocked them for every day. It was replaced by a check of overlapping time slots, so a person can be assigned to any job that does not overlap another one.
Assignment also refuses people who are on leave, and a booking can only move through the fixed status order, so a cancelled job cannot come back.
, Open the full-size image (opens in a new tab)A weekly job that stays at 9:00
A weekly 09:00 job must stay at 09:00 in Paris after the switch to winter time. Adding 7 times 24 hours is not enough, so the next date is computed in Paris clock time, and completing a recurring job creates the next one at the same hour.
Every date is handled in Paris time, so a booking made from abroad keeps the right hour. Recurring bookings across clock changes are covered by unit tests.
, Open the full-size image (opens in a new tab)Live bookings behind a token
New bookings reach the signed-in admins through Socket.io, and that live channel also needs a token. Every API route needs a JWT unless it is marked public, which 5 of the 26 endpoints are.
Sign-in, bookings and price estimates have their own rate limits on top of a global one, and text fields are trimmed and cleaned of HTML before they are saved.
, Open the full-size image (opens in a new tab)Screens
, Open the full-size image (opens in a new tab)The public home page.
, Open the full-size image (opens in a new tab)Describing the home to clean.
, Open the full-size image (opens in a new tab)The request is received.
, Open the full-size image (opens in a new tab)The week's jobs.
, Open the full-size image (opens in a new tab)Adding a job from the admin.
, Open the full-size image (opens in a new tab)The staff catalogue, with filters.
, Open the full-size image (opens in a new tab)A staff profile, with a printable sheet.
, Open the full-size image (opens in a new tab)The API test run.
How it's built
| Layer | Technology |
|---|---|
| Booking site | Next.js 16, React 19, Tailwind CSS 4 and Zustand, to keep the form between steps |
| Admin | Next.js 16 and React 19, with a Socket.io client for new bookings |
| API | NestJS 11 with JWT sign-in, rate limits, helmet and class-validator |
| Database | PostgreSQL with Prisma 7: 6 models and 12 migrations, including data migrations |
| Tests | Jest for the API, with lint and type checks on the three projects |
, Open the full-size image (opens in a new tab)Testing
58 unit tests in 12 files cover the business rules: the status order, overlapping jobs, recurring bookings across clock changes and a price estimate equal to the saved price. The three projects have no lint problems, and every one of them type-checks and builds.
Results
- 3 repositories with 140 source files and 9,367 lines of code.
- 5 pages on the booking site and 10 in the admin.
- 26 API endpoints, 5 of them public, over 6 database models.
- 58 unit tests and 78 commits.
What I would build next
- Send real emails and text messages through a provider, instead of the simulated ones.
- Move the admin session from browser storage to a secure, http-only cookie.
- End-to-end tests for the two websites, and a CI pipeline.
- Let customers cancel or move a booking themselves.
Built with
- Next.js
- React
- TypeScript
- Tailwind CSS
- Zustand
- NestJS
- Prisma
- PostgreSQL
- Socket.io