BastionGlass Architecture: Decisions Behind a Multi-Tenant ERP
The architectural decisions that shaped BastionGlass — a multi-tenant SaaS ERP for the auto glass industry. Trade-offs, patterns, and what I would do differently.
James Ross Jr.
Strategic Systems Architect & Enterprise Software Developer
The Problem That Started Everything
Chris S. Runs an auto glass repair business in DFW. When we started working together, his "system" was a combination of spreadsheets, text messages, and memory. Customer information lived in his phone contacts. Job scheduling happened via text thread. Invoicing was manual. Insurance claim tracking was a notebook.
This is not unusual for small field service businesses. The tools designed for them are either too expensive (enterprise field service platforms priced for companies with 500+ employees) or too generic (general-purpose CRMs that require extensive customization to model the auto glass workflow). The industry-specific options that do exist tend to be legacy systems built fifteen years ago, with interfaces that reflect it.
BastionGlass was born from the observation that the auto glass industry needed software built specifically for its workflows — quoting based on vehicle specifications and glass types, insurance claim management, mobile dispatch, and compliance tracking — but packaged as a modern SaaS platform that a shop owner could adopt without a six-figure implementation budget.
The architectural challenge was building a system that served Chris's single-shop operation today but could scale to serve hundreds of shops as a multi-tenant platform.
Choosing Multi-Tenant From Day One
The first and most consequential architectural decision was committing to multi-tenant architecture before we had a second customer. This was a bet. Multi-tenancy adds complexity to every layer of the system — data isolation, query scoping, configuration management, billing. Building it for one customer is objectively over-engineering.
But the alternative — building a single-tenant system and retrofitting multi-tenancy later — is worse. I have seen that migration on other projects, and it touches every query, every authorization check, every background job. It is effectively a rewrite. By accepting the upfront complexity, we avoided a much more expensive migration later.
The multi-tenant strategy uses a shared database with row-level tenant isolation. Each record includes a tenantId foreign key, and a middleware layer ensures that every database query is scoped to the authenticated tenant. This is the most common approach for SaaS applications at our scale — it keeps infrastructure costs low while providing logical data isolation.
The trade-off is that a bug in the tenant scoping middleware could theoretically expose data across tenants. We mitigated this with database-level row security policies as a second layer of defense, plus comprehensive test coverage for the tenant isolation boundary.
The Domain Model
BastionGlass models the auto glass business workflow as a state machine. A job moves through defined stages: Lead, Quoted, Scheduled, In Progress, Completed, Invoiced, Paid. Each transition has preconditions (you cannot schedule a job that has not been quoted) and side effects (completing a job triggers invoice generation).
This state machine approach was chosen over a more flexible free-form workflow because the auto glass repair process is genuinely linear. Unlike general-purpose project management, where tasks can move in any direction, an auto glass job follows a predictable path. Encoding that path in the system means the software enforces business rules that would otherwise depend on human discipline.
The core entities are straightforward: Customers, Vehicles, Jobs, Quotes, Invoices, Payments, Insurance Claims, and Technicians. The relationships between them model the real-world domain — a Customer has Vehicles, a Vehicle can have multiple Jobs, each Job has a Quote and eventually an Invoice. Domain-driven design principles guided the entity boundaries, though we kept the implementation pragmatic rather than academically pure.
Technology Stack Decisions
The stack is Nuxt 3 for the frontend and server-side rendering, Prisma as the ORM, and PostgreSQL for the database. This combination was chosen for specific reasons rather than default preferences.
Nuxt 3 provides both the customer-facing interface and the internal dashboard in a single application. Server routes handle API logic through Nitro, which means the entire application deploys as one unit. For a small team, this reduces the operational overhead of managing separate frontend and backend deployments.
Prisma was selected for its TypeScript integration. Every database query returns typed results, and the schema is defined in a single file that generates both the database migrations and the TypeScript types. For an ERP where the data model is complex and touches every feature, this type safety prevents an entire category of bugs.
PostgreSQL was chosen over alternatives for its solid support for complex queries, JSON columns for flexible configuration storage, and the ecosystem of extensions we anticipated needing — specifically PostGIS for geographic dispatch calculations and pg_trgm for fuzzy search on customer and vehicle records.
What I Would Reconsider
Looking back after running BastionGlass in production with AutoGlass Rehab as the first tenant, a few decisions merit reconsideration.
The shared-database multi-tenant approach works well at our current scale, but as we onboard more tenants with varying data volumes, the query performance characteristics will change. I would consider a hybrid approach where high-volume tenants get their own database schemas while smaller tenants share. The multi-tenant database design space has patterns for this that we may adopt as we grow.
The monolithic Nuxt application handles both the marketing site and the ERP dashboard. This simplified deployment initially, but the two surfaces have different performance profiles and update cadences. Splitting them into separate applications sharing a common API layer would allow independent scaling and deployment without risking the marketing site's uptime during ERP deployments.
These are not regrets — they were the right decisions given what we knew and the resources we had. Architecture is about making the best decision with the information available, then adapting as you learn more. BastionGlass was designed to be adaptable, and that may be the most important architectural decision of all.