Multi-Tenant Architecture 101: One Codebase, Many Car Rental Brands
A walkthrough of how CAREcho serves multiple independently-branded car rental tenants from a single codebase, and where the tenant boundary actually sits.
Multi-tenancy is easy to describe and easy to get wrong. The description is simple: one codebase serves many customers, each isolated from the others. The hard part is deciding exactly where isolation lives -- at the database level, the application level, or somewhere in between.
CAREcho's approach: every tenant-facing route is scoped under `/t/{tenantSlug}`, resolved to a specific `Tenant` record, and every model that belongs to a tenant carries a `tenant_id` foreign key. A vehicle, a reservation, a customer -- all of it is filtered by tenant at the query level, not just hidden in the UI. That distinction matters: a UI-level filter can be bypassed by a crafted request; a query-level scope can't be, if it's applied consistently.
That consistency is the actual engineering work. It's not glamorous -- it's making sure every new model that should be tenant-scoped actually gets a `tenant_id` column and every query that touches it respects it. A recent example: `job_offers` shipped without `tenant_id` at first, which meant a tenant admin browsing the shared panel could technically see SmartAgents' own hiring content. It was a real gap, caught in QA, fixed the same day -- and it's a useful reminder that multi-tenancy isn't a one-time architecture decision, it's a discipline applied to every new table going forward.
There's also a platform sentinel tenant -- a `Tenant` record representing SmartAgents itself, not a customer -- used for content that belongs to the platform rather than any single tenant. Careers listings and this news content both live there.