Skip to main content
Architecture8 min readJanuary 28, 2026

What I Learned Building an ERP From Scratch

Lessons from building BastionGlass, a multi-tenant ERP for the auto glass industry — what surprised me, what I got wrong, and what I would tell someone starting one today.

James Ross Jr.

James Ross Jr.

Strategic Systems Architect & Enterprise Software Developer

The Decision to Build

Nobody should build an ERP from scratch unless the existing options have been genuinely evaluated and found insufficient. I want to be clear about that up front, because "let's build our own ERP" is one of the most common and most expensive mistakes in enterprise software.

For BastionGlass, the decision was justified. The auto glass industry has specific workflows — vehicle-based quoting, insurance claim management, mobile dispatch with geographic constraints, ADAS recalibration tracking — that general-purpose ERPs cannot model without extensive customization. The industry-specific options that exist are legacy systems with outdated interfaces and no API capabilities. The opportunity was real: build a modern, multi-tenant platform that serves this specific niche better than generic alternatives.

But understanding the opportunity and executing on it are different things. Here is what I learned from the experience of building BastionGlass.

Lesson 1: The Data Model Is Everything

The most important work in an ERP happens before anyone writes application code. The data model — the entities, their relationships, and their constraints — determines what the system can and cannot do. Getting the data model wrong is expensive because every feature is built on top of it, and changing it later means migrating data, updating queries, and retesting everything.

We spent three weeks on the data model before writing a single API endpoint. Chris and I mapped out every entity in his business: customers, vehicles, jobs, quotes, invoices, payments, insurance claims, technicians, service areas. For each entity, we defined its attributes, its relationships to other entities, and its lifecycle states.

The payoff was that once we started building features, they snapped into place against the data model. The quoting engine was a function over the vehicle, parts, and pricing entities. The dispatch system was a function over jobs, technicians, and service areas. The data model made the application logic obvious rather than arbitrary.

The investment we made in understanding domain-driven design principles paid for itself here. Modeling the domain accurately — not the UI, not the database tables, but the actual business domain — made the system intuitive for users because it reflected how they already thought about their work.

Lesson 2: Start With One Tenant, Design for Many

BastionGlass was designed as a multi-tenant SaaS platform from day one, but it launched with a single tenant: Chris's AutoGlass Rehab. This is the right approach and I would do it again.

Building for one tenant keeps you honest. You cannot hide behind abstraction when the single user of your system is telling you exactly what works and what does not. Every feature gets tested in a real business context immediately. The feedback loop is days, not months.

But designing for multi-tenancy from the start means you do not have to retrofit it later. Every query is scoped by tenant ID. Every configuration is per-tenant. Every feature is aware that it exists in a shared environment. The incremental cost of this during initial development is small — adding a tenantId column and a middleware filter is not a major engineering effort. The cost of adding it later, when every query and every test assumes a single-tenant context, is enormous.

Lesson 3: Workflows Are More Important Than Features

Early in the project, I was thinking in terms of features: quoting, scheduling, invoicing, payment processing. Each feature was a module that could be built and tested independently. This is a reasonable engineering decomposition, but it misses the point.

Users do not think in features. They think in workflows. Chris does not "use the quoting module" and then "use the scheduling module." He receives a call, qualifies the lead, quotes the job, schedules it, dispatches a technician, collects payment, and reconciles the insurance claim. That is one continuous workflow that happens to cross four modules.

The shift from feature-oriented to workflow-oriented thinking changed the system's user experience significantly. Instead of building each module with its own navigation, its own screen layout, and its own mental model, we built guided workflows that move the user through the process step by step. Completing a quote presents the option to schedule immediately. Completing a job presents the option to collect payment. Each step flows naturally into the next.

This workflow orientation also exposed integration requirements that feature-oriented thinking obscured. The quoting module needs to know about scheduling availability. The scheduling module needs to know about geographic constraints from the dispatch module. The payment module needs to know about insurance details from the quoting module. In a feature-oriented architecture, these are integrations that get bolted on later. In a workflow-oriented architecture, they are designed in from the start.

Lesson 4: The Last 20% Takes 80% of the Time

The Pareto principle hits hard in ERP development. Getting the core workflow — quote, schedule, complete, invoice — working took about 30% of the total development time. The remaining 70% went to edge cases, error handling, administrative features, and the operational infrastructure needed to run the system reliably.

What happens when a job is cancelled after the technician is already en route? What happens when an insurance company partially denies a claim? What happens when a customer's card is declined after the work is completed? What happens when two dispatchers schedule the same technician for overlapping jobs? Each of these scenarios required specific handling, specific UI states, and specific test coverage.

The lesson is not that edge cases are avoidable — they are inherent to business software. The lesson is that estimating ERP development effort based on the core workflow dramatically underestimates the total scope. If I were advising someone starting an ERP today, I would tell them to estimate the core workflow effort and then multiply by four for the production-ready system.

Lesson 5: Build for the Admin, Not Just the User

Every ERP needs administrative capabilities that no user ever sees but that the system cannot function without. Tenant management, feature flag configuration, system health monitoring, data migration tools, audit log querying — these are not user features, but they consume significant development time.

I underestimated this initially. The first version of BastionGlass had great user-facing features and minimal admin tooling. When something went wrong — a data inconsistency, a stuck job, a misconfigured tenant — the fix required direct database access rather than an admin interface. This is acceptable during early development but unsustainable as the system grows.

Building an ERP from scratch is one of the most challenging projects a developer can undertake. It touches every aspect of software engineering: data modeling, business logic, user experience, integrations, security, performance, and operations. The experience of building BastionGlass has made me a significantly better architect, and the patterns I developed have informed every project since, including Routiine.io and the North TX RV Resort platform.