Chrispo E.P.O Ltd is a serviced accommodation and event centre in Kumasi, Ghana. When I took on the project, they had no digital presence at all — bookings were handled by phone and paper records. My job was to build something that replaced all of that: a customer-facing site, a staff management backend, and an admin view that gave the owners visibility into what was actually happening with their business.
This is a walkthrough of how I built it and what I learned.
Starting with the data model
Before writing any frontend code, I spent time mapping out the data model. For a booking system, getting this right matters more than almost anything else — a bad schema makes every subsequent feature harder to build and harder to change.
The core entities were: Rooms (type, capacity, pricing), Bookings (guest info, room, check-in/check-out dates, status), Guests (contact details, booking history), and Staff (accounts, roles, action logs). I kept the relationships simple intentionally — the client needed something maintainable, not a system that required a DBA to understand.
The key decision was separating booking status from payment status. A booking can be confirmed before payment is complete, and payment can arrive in stages. Conflating the two would have created edge cases that were hard to reason about later. Keeping them separate made the receipt generation logic much cleaner.
The API structure
The backend is a REST API with clearly separated concerns. I organised routes around resources: /bookings, /rooms, /guests, /staff. Each resource had standard CRUD endpoints, with a few additional actions for things like confirming a booking or generating a receipt.
Authentication used JWT tokens with role-based access control — staff could manage bookings and generate receipts, while admins had access to the analytics endpoints and user management. I kept the permission model simple: two roles, clearly defined. Adding more granularity wasn't needed for this client and would have added complexity without value.
The admin dashboard without a dedicated analytics service
The most interesting engineering problem was the admin dashboard. The client needed to see live occupancy, revenue trends, and booking activity — but I wasn't going to spin up a separate analytics service for a single-property hotel in Kumasi. The budget and operational complexity didn't justify it.
Instead, I built lightweight aggregation queries directly on the booking database. Revenue by day, week, and month. Occupancy rate by room type. Average booking lead time. These ran on a polling interval — the dashboard refreshed every 60 seconds, pulling pre-aggregated data from a small set of indexed views.
The right answer isn't always the architecturally elegant one. Sometimes the right answer is a well-indexed SQL query and a 60-second poll.
This worked well within the constraints of the project. The queries were fast enough that polling didn't create load issues, and the simplicity meant I could maintain and modify the analytics logic without touching a separate system. For a business at this scale, it was the correct trade-off.
Receipt generation
Staff needed to generate PDF receipts for guests on checkout. I handled this server-side — when a receipt is requested, the API assembles the relevant booking data, formats it into a template, and returns a PDF. No client-side generation, no third-party service.
The template was built to match the client's branding. This sounds minor but it mattered to the client — handing a guest a receipt with their logo and proper formatting was a meaningful upgrade from handwritten notes.
Deployment and the operational side
Getting the system live involved more than deploying the application. I configured the domain, set up professional email addresses for the hotel (reservations@, info@), and documented the system for the staff who would be using it day-to-day. The technical deployment was the easy part — the documentation and handover took longer and mattered more.
The system has been in active use since launch. Staff manage all bookings through the backend, receipts are generated on demand, and the owners check the dashboard regularly to track occupancy and revenue.
What I'd do differently
A few things stand out in retrospect. I'd write more integration tests from the beginning — specifically around the booking status transitions, which had the most edge cases and were the hardest to reason about under pressure. I'd also be more deliberate about error messages early on: the first version had too many generic "something went wrong" responses that made debugging harder than it needed to be.
The data model held up well. The API structure held up well. The thing that needed more upfront investment was the testing coverage around the business logic — the rules that govern when a booking can move from one status to another, when a receipt can be issued, when a room is considered unavailable. That logic is the core of what the system does, and it deserved more test coverage than it got initially.