Tunas Akara
Back to Blog

Building a Hotel Management System for a Real Hotel

by Rayhan7 min read
hotel managementnextjspostgresqldrizzlewhatsapphospitality
Building a Hotel Management System for a Real Hotel

Building a Hotel Management System for a Real Hotel

Building a hotel management system for a hotel in Pekalongan taught me that hospitality software is not only about rooms and bookings. The hard part is modeling the operational reality of the hotel: a room can be reserved, checked in, dirty, under maintenance, blocked for an owner, or ready but waiting for inspection. A front-desk employee needs a simple screen, but the database needs enough structure to prevent mistakes that become guest complaints.

I partnered with the hotel team and used Next.js 16 for the application layer, PostgreSQL for durable records, and Drizzle ORM to keep database access readable and type-safe. The goal was not to create a generic booking app. The goal was to give the hotel a system that could survive daily operations: walk-in guests, long-stay guests, housekeeping pressure, multi-language staff workflows, and the need to know exactly what happened when something went wrong.

Start With the Room, Not the Booking

The first design decision was to make room status explicit. In many early hotel systems, a room is simply "available" or "occupied." That is not enough. A real room has several independent dimensions:

  • Reservation availability: can this room be sold for a date range?
  • Physical status: clean, dirty, inspected, maintenance, out of order.
  • Operational status: ready for check-in, waiting for inspection, blocked.
  • Guest status: checked in, checked out, no-show, extended stay.

I modeled room status as a state machine instead of a free-text field. For example, a room can move from Dirty to Cleaning, then to Inspected, then to Ready. A room marked OutOfOrder cannot become Ready until maintenance is completed and an authorized user clears it. This prevented housekeeping from accidentally preparing a room that engineering had blocked for repair. I wrote about this approach in more depth in modeling hotel room status as a state machine.

The same idea applied to reservations. A reservation is not just "booked." It can be Held, Confirmed, CheckedIn, CheckedOut, Cancelled, NoShow, or Extended. Every transition is recorded with an actor, timestamp, and reason. That audit trail matters when the front desk needs to explain why a room changed status or why a booking was cancelled.

Preventing Overbooking Before It Reaches the Guest

Overbooking prevention was one of the most important requirements. The system needed to handle date ranges, room types, walk-in bookings, and last-minute changes without allowing two guests to be assigned the same physical room.

The approach was to separate room-type inventory from physical room assignment. A guest can book a "Deluxe Twin" before the hotel knows the exact room number. The database checks available inventory for the requested dates, then assigns a physical room closer to check-in or during front-desk confirmation. This reduces the chance of blocking a specific room too early while still protecting capacity.

For exact room assignments, I used database constraints and transaction checks. A room assignment must not overlap with another active assignment for the same room. The application also keeps a short-lived reservation lock when a front-desk employee is creating a booking, so two staff members cannot complete conflicting check-ins at the same time.

The lesson from Pekalongan was that overbooking prevention cannot rely on UI logic alone. A button can say "available" and still be stale by the time the user clicks it. The database must enforce the rule. I broke down the full strategy in a separate piece on preventing hotel overbooking.

Multi-Language Workflows Without Duplicating the Product

The hotel needed EN, ID, and CN labels in the operational flow. Some staff were more comfortable in Indonesian, some guests needed Chinese labels, and management wanted English reports. Rather than building separate screens, I kept the data model language-neutral and moved language into labels, messages, and generated documents.

Room types, service names, receipt lines, and notification templates were stored with translation keys. The UI rendered the active language, while the database kept one canonical record. For example, a room type had one ID, one pricing rule, and one status history, but multiple display names.

This also helped WhatsApp messages. A booking confirmation could be sent in the guest's preferred language, while the internal front-desk notification remained in Indonesian. The system did not translate business rules; it translated the human-facing layer.

Permissions That Match Hotel Roles

A hotel system has very different users. The owner wants revenue visibility. The manager needs override power. Front desk needs booking and check-in workflows. Housekeeping needs room status updates. Maintenance needs repair tasks. Accounting needs invoices and payment records.

I avoided a single admin role. Instead, permissions were grouped by capability: booking:create, booking:cancel, room:change_status, room:block, invoice:view, report:revenue, and system:configure. Each role was composed from capabilities. This made it possible to give housekeeping access to room status without exposing revenue reports or system settings.

The practical benefit was trust. Staff could use the system for their own work without seeing data they did not need. Managers could still audit every override.

WhatsApp as an Operational Layer

WhatsApp automation became more than a notification feature. It connected the system to the way the hotel actually worked.

For guests, WhatsApp handled booking confirmations, pre-arrival reminders, room-ready messages, and checkout follow-ups. For staff, it became an alert channel for high-priority events: VIP arrival, room status stuck too long in Cleaning, maintenance escalation, or a front-desk task that needed attention.

The important part was to make every WhatsApp message traceable to a business event. A message should have a recipient, template, related booking or room, delivery status, and retry state. If a notification fails, the front desk should know. If a guest replies, the conversation should be linked back to the correct guest record.

Lessons From Deploying in a Real Hotel

The hardest lessons came after deployment. A system that works in a demo can fail in a hotel because the real environment has slow internet, shared devices, noisy front-desk operations, and staff who need fast answers.

I learned to optimize for clarity over cleverness. Housekeeping screens needed large buttons and obvious room colors. Managers needed revenue reports that matched their daily closing routine. The database needed indexes for date-range queries because availability checks happen constantly. Audit logs needed to be simple enough to read during an incident.

The biggest architectural lesson was this: a hotel management system should not only store transactions. It should guide the next action. When a guest checks out, the room should become dirty and housekeeping should receive a task. When a room becomes ready, front desk should know. When a booking is confirmed, the guest should receive a WhatsApp message. The system becomes valuable when operational events flow automatically from one team to another.

The system worked because the architecture followed the hotel's real workflow, not the other way around. Next.js gave the team a fast interface, Drizzle and PostgreSQL kept the data reliable, and WhatsApp connected the software to the people who needed it most. For a broader, less story-driven overview of what these systems need to do, see the complete hotel management system guide.

Related Posts

Building something similar?

Hotel Management System Development

Custom ERP-style hotel management software: bookings, room status, invoicing, staff, and WhatsApp automation — built around how your hotel actually runs.

See how I can help