Travel group platform
Nine applications for one travel group, on a server I run
A staff ERP, a hotel property-management system, a B2B agent booking panel, an accounting workspace, a customer booking app and the backend under them. I build them, run the server they live on, and answer when something breaks.
// The Problem
The group ran on a managed backend whose cost and lock-in grew with use, while sales, ticketing, hotel onboarding and staff operations lived in systems that didn't know about each other.
Moving off it could not mean rewriting five frontends, and it could not lose a single booking.
// How It's Built
Caddy
TLS, routing by hostname, an allow-list in front of every app
Staff ERP
Booking site
Hotel PMS
three portals, split by hostname and role
Accounting
Backend A
ERP and accounting, one SQLite database
Backend B
PMS, its own SQLite database
Nightly snapshots
kept for a retention window
// Decisions & Trade-offs
- 01
Keep the API, change what's behind it
The new Node.js + SQLite backend serves the same realtime, document-shaped API the apps already used, so the migration changed the data layer without touching the frontends on top of it.
- 02
Two databases, on purpose
Accounting shares the ERP's database because it reconciles against bookings, invoices and wallet entries. The PMS gets its own instance because its bookings and roles would otherwise collide with the ERP's.
- 03
Deny by default
Authorization lives in one server-side rules module whose last word is "no": a collection the rules don't know about is refused. Each instance names the extra collections its app is allowed to use.
- 04
Merge, don't overwrite
The cut-over merged by timestamp, because the straightforward import would have overwritten rows that were already newer on the new server.
- 05
One deployment, three portals
The PMS serves a hotel's front desk, the onboarding team and listing reviewers from one codebase. Hostname, role and the database rules all enforce the split, so staff never land in the wrong portal.
// Problems Worth Telling
- 01
The office couldn't reach the server
The office's ISP silently dropped traffic to the server's IPv4 address. Publishing AAAA records made IPv6 the working path; they are now mandatory in the DNS setup.
- 02
A rewrite that broke behind TLS
A hostname rewrite inside the app proxied to itself over HTTPS on a plain-HTTP port once a TLS-terminating proxy sat in front (EPROTO, then a 500). The rewrite moved into Caddy.
- 03
An export that quietly skipped data
The export script's hard-coded collection list missed five collections. Exports are now checked against the database's own list of collections before anyone trusts them.
// Quality
- 190 unit tests on the ERP, plus security-rules tests that run against the emulator.
- Smoke tests on the backend, nightly snapshots with retention, and a hardened host: firewall, fail2ban, non-root services, loopback-only ports.
// Where It Stands
- All nine applications run on one server I manage, with the frontends unchanged by the migration.
// Stack
Building something similar?
Let's talk