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
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
All bookable resources across a weekly timeline. Bookings appear as colored blocks you can drag to move or resize.
Monthly Calendar View
Overview of all bookings across resources. Click any date to see its reservations in the sidebar.
Booking Creation
Pick a resource, set the time range, assign a customer, and attach products and services from the selected branch.
Analytics Dashboard
KPIs at a glance with revenue charts, utilization metrics, and popular products and services.