Architecture
Architecture Overview
How Basefloor is structured — multi-tenancy, service boundaries, and technology choices.
Architecture Overview
Basefloor is a multi-tenant Hotel Management System. This page describes the high-level system design and how its components relate to each other.
System Diagram
Internet
│
┌─────────────┴──────────────┐
│ Cloudflare │
│ (DNS, Zero Trust, Pages) │
└────┬──────────┬────────────┘
│ │
┌─────────▼──┐ ┌───▼──────────────┐
│ Marketing │ │ Web App │
│ (Next.js) │ │ (TanStack Start) │
│ Pages CDN │ │ khyber.hms.com │
└─────────────┘ └────────┬──────────┘
│ REST API
┌─────────▼──────────┐
│ hms-core │
│ (Ruby on Rails) │
│ hms-api.domain.com │
└────────┬────────────┘
│
┌──────────────┴──────────────┐
│ │
┌──────▼──────┐ ┌────────▼──────┐
│ PostgreSQL │ │ Redis │
│ (primary) │ │ (cache/jobs) │
└─────────────┘ └───────────────-┘Multi-Tenancy Model
Basefloor uses subdomain-based multi-tenancy with complete database-level isolation:
- Each hotel chain (tenant) gets a unique subdomain:
chain-name.hms.com - All database tables include a
hotel_chain_idforeign key - Models are automatically scoped to the current tenant via
default_scope - The current tenant is resolved from the request's subdomain on every API call
Tenant Resolution
Request: https://khyber.hms.com/api/v1/properties
↓
Extract subdomain → "khyber"
↓
Lookup HotelChain by slug
↓
Set Current.hotel_chain
↓
All DB queries auto-scoped to hotel_chain_idAdding New Tenant-Scoped Models
When creating a new model that belongs to a tenant:
- Add
hotel_chain_idto the migration - Add
belongs_to :hotel_chainto the model - Add the default scope:
default_scope { where(hotel_chain: Current.hotel_chain) }
For models that should exist globally across all tenants (e.g., AmenityType), inherit from GlobalRecord instead of ApplicationRecord.
Service Boundaries
hms-core (Backend)
- Handles all business logic, data persistence, and authentication
- Exposes a versioned REST API at
/api/v1/ - Background jobs via Solid Queue
- Deployed via Kamal to a dedicated server
Web App (Frontend)
- TanStack Start + React application
- Communicates exclusively with the hms-core API
- Subdomain detection resolves the active tenant on load
- Deployed as a static/SSR app on Cloudflare
Marketing Site
- Next.js 15 (App Router) public-facing site
- Static pages deployed to Cloudflare Pages
- No direct API communication
Technology Choices
| Decision | Choice | Rationale |
|---|---|---|
| Backend framework | Ruby on Rails | Rapid development, mature ecosystem, ActiveRecord for multi-tenancy |
| Frontend | TanStack Start | Fine-grained reactivity, TypeScript-first, modern routing |
| Database | PostgreSQL | Relational integrity, JSONB support, strong Rails integration |
| Cache/Queue | Redis + Solid Queue | Lightweight, co-deployed with app |
| CSS | Tailwind v4 + shadcn/ui | Utility-first, shared component library |
| Deployment | Kamal + Cloudflare | Self-hosted backend, edge-cached frontend |