Basefloor Dev
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_id foreign 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_id

Adding New Tenant-Scoped Models

When creating a new model that belongs to a tenant:

  1. Add hotel_chain_id to the migration
  2. Add belongs_to :hotel_chain to the model
  3. 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

DecisionChoiceRationale
Backend frameworkRuby on RailsRapid development, mature ecosystem, ActiveRecord for multi-tenancy
FrontendTanStack StartFine-grained reactivity, TypeScript-first, modern routing
DatabasePostgreSQLRelational integrity, JSONB support, strong Rails integration
Cache/QueueRedis + Solid QueueLightweight, co-deployed with app
CSSTailwind v4 + shadcn/uiUtility-first, shared component library
DeploymentKamal + CloudflareSelf-hosted backend, edge-cached frontend

On this page