Let's Talk Resume
Portfolio / Case Studies / Serene Management

Serene Management

A hotel property management system for one or many properties, from reservation to night audit, built so the money and the data can be audited.

Role Full-Stack Engineer
Category Hospitality Platform
Timeline 2026
Core Stack Next.js · TypeScript · PostgreSQL · Prisma · Zod

Problem & Challenge

Bugs in hotel software cost real money. A guest billed twice means refunds and complaints, a room sold twice means an overbooked night, and one property seeing another's data is a privacy breach.

Serene Management serves 12 staff roles across one or more hotels, and every night an audit has to close the books correctly.

Rules Before the First Line

  • Thin routes, rules in services: route handlers authorize, validate and scope; business rules live in domain services.
  • Property scope from the server: the property a request can touch comes from the session, never from the client.
  • Exact money: amounts are stored exactly, never as floats.
  • Append-only ledgers: financial records are never edited; corrections add a row.
  • Slow work goes to a worker: long jobs never depend on a browser staying open.

These rules are enforced by lint and tests, so they held across 127 database models.

Architecture

Request Path
Staff browser
Next.js with RTK Query
↓
Request boundary
Session, CSRF protection and rate limits
↓
Route handler
Authorize, validate with Zod, scope to the property
↓
Domain services → PostgreSQL
Business rules applied, then persisted through Prisma

A second, background path handles night audit: the job is queued as a row in PostgreSQL, a worker claims it, the business date rolls over, charges are posted once, and room boards refresh through database notifications. A hidden button is never the only protection; every action is authorized on the server.

Key Decisions

1. Retries can't double-charge

Payments and other financial actions carry an idempotency key. If a response is lost and the client retries, the server returns the first result instead of recording a second payment. An acceptance test sends the same payment twice and checks that one is recorded.

2. The books close in a worker

A worker claims the night-audit job with FOR UPDATE SKIP LOCKED, runs it under a lease and retries up to three times. Postings are held while the date closes. Duplicate night charges in testing: zero.

3. Offline is read-only, on purpose

When the connection drops, the front desk can still see arrivals, in-house guests and departures, but cannot book, check in or take payment. That avoids the hardest problem in hotel software: merging conflicting offline bookings.

Performance Results

Measured before optimizing. One innocent-looking ORM count was scanning every reservation room on each request.

BottleneckBeforeAfter
Global search8.3 s0.45 s
Guest statistics168 ms0.16 ms
Room board (buffers read)38,3464,967
List page (ORM count)~1 sFixed

Slow list queries were holding the connection pool, so simple auth checks rose from 8 ms to 550 ms. Before the fixes the server saturated at 10–25 requests per second; after four fixes it handled 50 requests per second with 0 errors and a P95 latency of 192 ms. Measured on one instance on a 4-core laptop.

Auditing My Own Work

A pre-release audit found a release blocker: a routine rate-plan change could silently re-bill nights that had already been charged. Nothing crashed; guests would simply have paid twice.

  • P0: silent re-billing, fixed and covered by tests.
  • P1: no request size limit, now capped at 1 MiB.
  • P1: the offline page exposed guest lists, now fixed.