ReserveFlow: Multi-Tenant Booking Platform

.NET 10Angular 20PostgreSQL 17ValkeySignalRKeycloakOpenTelemetrySigNozDockerGitHub Actions
ReserveFlow: Multi-Tenant Booking Platform

Try It Live

ReserveFlow is deployed and available for you to explore at reserveflow.alvinalmodal.dev. Log in with any of the accounts below and try creating bookings, browsing the calendar, and checking the dashboard.

Demo Accounts

The demo has two completely isolated tenants. Log in with each to see that they have separate resources, bookings, products, and customers. Data from one tenant is invisible to the other.

Username Password Tenant Role
admin R3serveFlow#2026 Tenant 1 Admin (full access)
alice R3serveFlow#2026 Tenant 1 Staff (bookings only)
carol R3serveFlow#2026 Tenant 2 Admin (full access)

Try this: Log in as admin, create a booking, then sign out and log in as carol. You will see a completely different set of resources and data. That is multi-tenancy in action. Then try alice to see the staff role, which can view the calendar and create bookings but cannot manage resources, products, services, or access reports.

This is a shared demo environment with seed data. Feel free to create bookings, add customers, and explore all features. Data resets periodically.

The Challenge

Scheduling systems look simple on the surface. A calendar, some time slots, a save button. But the moment two people try to book the same resource at the same time, things break. Double bookings, oversold inventory, stale UI state. Add multi-branch operations with per-location pricing and stock, and the complexity multiplies fast.

I wanted to build a booking platform that handled all of this correctly from the start, not as an afterthought.

What I Built

ReserveFlow is a multi-tenant SaaS booking system designed for service businesses. The primary use case is dental clinics, but the architecture is general enough for any resource-based scheduling.

How It Works Under the Hood

Distributed Locking for Conflict Prevention. The core booking transaction acquires a distributed lock via Valkey before checking for overlaps in PostgreSQL. This prevents double bookings even under concurrent load. The lock has a 30-second TTL with exponential backoff retry, and every attempt is traced through OpenTelemetry.

Multi-Branch with Per-Location Overrides. Resources can belong to multiple branches. Products track stock per branch. Services have pricing overrides per branch (hourly or fixed rate). A dentist can work at two clinics with different service prices at each.

Real-Time Updates via SignalR. When a booking is created, moved, or cancelled, every connected client in the same tenant sees the change instantly. The SignalR backplane runs on Valkey so it scales across multiple API nodes.

Full Observability. OpenTelemetry traces every booking from lock acquisition through database write to SignalR broadcast. Custom spans capture resource IDs, lock states, and outcomes. SigNoz provides the APM dashboard with ClickHouse storing the telemetry data.

Key Technical Decisions

  • Valkey over pessimistic database locks: Faster, decouples lock lifetime from transaction lifetime, and the TTL prevents deadlocks from crashed processes.
  • .NET Minimal APIs with TypedResults: Compile-time route safety without the ceremony of controllers. Every endpoint returns explicit status codes.
  • Angular Signals over RxJS: Simpler reactive state, better performance, cleaner templates. The calendar grid uses computed signals for slot calculations.
  • Keycloak for identity: Standards-based OIDC, tenant claims in JWTs, and it keeps auth concerns completely separate from the application.
  • Testcontainers for integration tests: Spins up real PostgreSQL and Valkey instances per test run. Tests verify actual concurrency behavior, not mocks.

Features

  • Drag-to-select calendar with week and month views
  • Drag-to-move and drag-to-resize bookings
  • Recurring bookings (daily, weekly) with series management
  • Product and service line items per booking with branch-scoped pricing
  • Per-branch inventory tracking with automatic stock deduction
  • Customer management with search
  • Analytics dashboard with revenue charts, utilization metrics, and event feed
  • Multi-tenant isolation via global EF Core query filters

What I Learned

The hardest part of building a booking system is not the UI or the database schema. It is getting the concurrency model right. A booking that looks available when the user clicks "confirm" might not be available 200 milliseconds later when the write hits the database. Distributed locking solved this, but it introduced its own complexity around lock timeouts, retry strategies, and observability. Tracing every lock acquisition and release through OpenTelemetry made debugging production issues straightforward instead of guesswork.

Screenshots

ReserveFlow login screen powered by Keycloak

Authentication via Keycloak

Users sign in through Keycloak, which handles OIDC/OAuth2 authentication. Each user's JWT carries a tenant claim that scopes all data access.

Weekly resource view showing dentist schedules with time-slot bookings

Weekly Resource View

All bookable resources across a weekly timeline. Bookings appear as colored blocks you can drag to move or resize.

Monthly calendar view with booking entries and sidebar details

Monthly Calendar View

Overview of all bookings across resources. Click any date to see its reservations in the sidebar.

New booking modal with resource, time, branch, and product/service selection

Booking Creation

Pick a resource, set the time range, assign a customer, and attach products and services from the selected branch.

Dashboard with KPI cards, revenue charts, utilization metrics, and popular items

Analytics Dashboard

KPIs at a glance with revenue charts, utilization metrics, and popular products and services.