Building Four-Points: A Hotel Management System from Scratch
The story behind building a full-stack hotel PMS from zero to production - problems faced, solutions found, and lessons learned
Building Four-Points: A Hotel Management System from Scratch
🏨 Why Build a Whole PMS?
After 15+ years working hotel reception, I got tired of slow, clunky, overpriced software that hotels are forced to use so that everything placed at excel. So I did what any developer would do: decided to build my own. Classic developer move, right?
Four-Points started as a simple parking tracker for my hotel. Fast forward to today, and it's a complete property management system designed to scale from 1 hotel (10 users) to 12+ hotels with 100 concurrent users.
🎯 What I Actually Built
Core Modules
- Parking Management: Real-time availability tracking, automated reservations, maintenance schedules
- Groups & Bookings: Payment tracking, contact management, group status workflows
- Cashier System: Multi-shift operations (4 shifts/day), denomination tracking, voucher handling
- Logbook: Operational notes with role-based permissions and comment threads
- Maintenance Reports: Image upload workflows with Sharp processing
- User Management: JWT auth, role-based access control, httpOnly cookies
The Stack
Frontend: Next.js 14 + TypeScript + Zustand
Backend: Node.js + Express + REST API
Database: MySQL on Aiven Cloud
Auth: JWT with httpOnly cookies
Images: Multer + Sharp
Styling: Tailwind CSS
💀 Problems I Faced (And How I Fixed Them)
1. "Just Use JavaScript" - Famous Last Words
Started the whole project in vanilla JavaScript. Seemed fine for the first few weeks. Then the codebase grew. Then the bugs appeared. Then the refactoring nightmares began.
The Problem: No type safety meant runtime errors everywhere. Passing wrong data types, typos in object properties, undefined values breaking production.
The Solution: Migrated everything to TypeScript. Took me 2 weeks of pain, but worth every second. Now my IDE catches bugs before I even run the code. Should've done this from day one.
2. MySQL Triggers Going Wild
Implemented automatic calculations using database triggers. Sounds smart, right? Wrong.
The Problem: Triggers updating other tables which triggered more triggers which updated more tables... infinite loops. Plus debugging trigger logic is absolute hell - you can't console.log() a trigger.
The Solution: Moved calculations to the application layer. Yes, more code to maintain, but at least I can debug it. Sometimes "smart" database logic isn't smart at all.
3. Timezone Hell
Classic backend vs frontend timezone nightmare. Server in one timezone, database in another, users in a third.
The Problem: Cashier shifts showing wrong times, reports spanning incorrect date ranges, timestamps all over the place.
The Solution:
- Store everything in UTC in the database
- Convert to user's timezone only in frontend
- Use
toISOString()religiously - Never trust
new Date()without timezone handling
4. Database Schema Drift
Local development MySQL, production on Aiven Cloud. Schema changes in dev not matching production.
The Problem: Features working perfectly in dev, crashing in prod because a column doesn't exist or has wrong constraints.
The Solution:
- Migration scripts for EVERY schema change
- Version control for database schema
- Testing against cloud database before deploying
- Learned the hard way: schema is code, treat it like code
5. Role-Based Access Control Complexity
Different users need different permissions. Receptionists, managers, admins, maintenance staff...
The Problem: Started with simple if/else checks. Became unmaintainable fast. "Can user X do action Y in module Z?" got complicated.
The Solution:
// Centralized permission system
const permissions = {
parking: {
view: ['receptionist', 'manager', 'admin'],
edit: ['manager', 'admin'],
delete: ['admin']
},
cashier: {
openShift: ['receptionist', 'manager'],
closeShift: ['manager', 'admin'],
viewReports: ['manager', 'admin']
}
}
Middleware checks permissions before any action. Clean, testable, maintainable.
6. Image Upload Optimization
Hotels upload maintenance photos constantly. Started with direct uploads to server.
The Problem: 10MB phone photos killing server storage and bandwidth. Loading times horrible.
The Solution:
- Sharp for image processing
- Resize to max 1920px width
- Compress to 80% quality
- Generate thumbnails automatically
- Went from 10MB images to 200KB without noticeable quality loss
7. Concurrent User State Management
Multiple receptionists updating the same data simultaneously.
The Problem: User A books room 101, User B books room 101 2 seconds later. Both succeed. Disaster.
The Solution:
- Optimistic locking with version numbers
- Real-time availability checks before confirming
- Database constraints as last line of defense
- User feedback when conflicts occur
8. Local MySQL to Cloud Migration
Started with local MySQL, had to migrate to Aiven Cloud for production.
The Problem: Connection strings, SSL certificates, performance differences, migration without downtime.
The Solution:
- Tested migration on staging environment
- Used mysqldump for data export/import
- Updated connection configs with SSL
- Learned: cloud databases have different performance characteristics
🚀 Features That Actually Work
✅ Authentication Flow
- Login with JWT tokens
- httpOnly cookies (no localStorage XSS risks)
- Automatic token refresh
- Role-based route protection
✅ Parking System
- Real-time spot availability
- Automated reservation handling
- Maintenance mode scheduling
- Pre-calculated availability (no dynamic queries)
✅ Groups Management
- Payment tracking with multiple methods
- Contact organization
- Status workflows (inquiry → confirmed → checked-in)
- Professional booking codes
✅ Cashier Operations
- 4 shifts per day
- Denomination tracking (bills, coins)
- Voucher management
- Automated shift reports
- Cross-shift reconciliation
✅ Logbook System
- Operational notes
- Comment threads
- Role-based visibility
- Timestamp tracking
✅ User Management
- Create/edit/deactivate users
- Role assignment
- Activity tracking
- Password reset flows
📚 What I Learned
TypeScript is Non-Negotiable
If your project will have more than 500 lines of code, use TypeScript from day one. The time you "save" writing JS, you'll spend 10x debugging runtime errors.
Database Design Matters More Than You Think
Spent the first month just designing the database schema. Best investment ever. A good schema makes everything easier. A bad schema haunts you forever.
Don't Optimize Prematurely (But Do Optimize Strategically)
Started with simple queries, identified bottlenecks with real usage data, then optimized. Don't guess what's slow - measure it.
Authentication is Hard, Use Libraries When Possible
Rolling your own auth taught me a lot, but also taught me that battle-tested libraries exist for a reason. Security is not where you want to be creative.
Testing Saves Time (Eventually)
Yes, writing tests feels slow at first. But catching bugs before production? Priceless. Especially for critical flows like payments and bookings.
Documentation is for Future You
Comments, README files, API docs - all seemed like wasted time until I had to debug my own code 3 months later. Future you will thank present you.
🎯 Real-World Impact
Currently running in production at my hotel with:
- 10 daily users
- 50+ parking reservations/day
- 200+ logbook entries/month
- 4 cashier shifts/day
- Zero downtime in the last 2 months
Planning to scale to 12 hotels (100 users) in 2025.
💭 Personal Take
Building Four-Points taught me more than any course ever could. Yeah, I watched midudev's Node.js course, followed SuperSimpleDev tutorials, read Stack Overflow until 3am - but nothing beats building something people actually use.
Would I do it again? Absolutely. But I'd:
- Start with TypeScript immediately
- Spend more time on database design
- Write tests from the beginning
- Use established patterns instead of reinventing wheels
- Deploy to cloud earlier
Is it perfect? Hell no. There's tech debt, features I want to refactor, and bugs I know exist but haven't fixed. But it works. Real people use it every day. And that's what matters.
🔮 What's Next
- React Query for better state management
- Real-time notifications with WebSockets
- Mobile app (React Native maybe?)
- Multi-language support
- Advanced reporting dashboard
- Integration with payment gateways
🙏 Shoutout
Thanks to everyone who unknowingly helped: Stack Overflow heroes, midudev for Node.js mastery, SuperSimpleDev for fundamentals, and Claude (yes, AI) for being a rubber duck that actually talks back.
Bottom line: If you have domain knowledge in any industry + can code, build the tools you wish existed. Hotel software sucks? Build better. Workflow inefficient? Automate it. It won't be perfect, but it'll be yours. And you'll learn more than any tutorial can teach.
Keep shipping. 🚀