All posts
SaaSAug 2026 · 11 min

How I Built ERPX, a Multi-Tenant ERP SaaS

Multi-tenancy, double-entry accounting, field-level permissions, subscriptions and an AI copilot - a field report from architecting ERPX, my first SaaS ERP, with NestJS, PostgreSQL and React.

ERPX is the largest system I have built, and the first product I would genuinely call software-as-a-service. It is a multi-tenant enterprise ERP - think accounting, sales, procurement, inventory, CRM, HR and payroll living in one application - where many organizations run at the same time, each inside its own isolated workspace.

This post is the engineering story: the stack decisions, the multi-tenancy design, the modules that are actually real, and the lessons that only show up when you build something this wide.

Why an ERP at all

I came to development from a commerce background, which means I think about products as systems that move money and data around, not just screens. Most projects teach you one vertical slice. An ERP forces you to model an entire business - and every module has to agree with every other module.

A todo app teaches you CRUD. An ERP teaches you that one table you ignored will eventually become someone's balance sheet.

Choosing the stack

The stack was chosen for long-term maintainability over hype. A strongly-typed backend with a schema you can read, a frontend where routes and types are generated, and a database where relationships are first-class.

LayerChoiceWhy
FrontendReact 19 + TanStack Router / QueryType-safe file-based routes, server state, one framework for 76 screens
BackendNestJS (Node.js)Modular structure that scales across many domains
DataPostgreSQL + PrismaRelational integrity for ledgers and inventory; readable schema
Caching & limitsRedisRate limiting and AI semantic caching
PaymentsRazorpay + StripeTwo production gateways behind one interface
DeliveryDocker → RenderMulti-stage images, containerised production

The hardest part: multi-tenancy

Multi-tenant means one codebase, one database, many organizations - and the single rule that no tenant can ever see another tenant's data. I enforce it in two layers. First, almost every table carries an organizationId and is indexed on it. Second, a global tenant-isolation interceptor compares the organization in the signed JWT with the organization in the URL, and returns 403 on any mismatch.

// Tenant isolation, enforced at the API boundary
if (user.org !== orgId && !isSuperAdmin(user.email)) {
  throw new ForbiddenException('tenant mismatch');
}
// ...and every query stays scoped
return this.prisma.account.findMany({
  where: { organizationId: orgId },
});

Tenant isolation is only trustworthy if it happens automatically. Interceptors and guards that run on every request beat remembering to add a where clause in every service.

Real modules, not mockups

Accounting that actually balances

Accounting was the module that separated ERPX from a generic admin panel. Every money movement is a double-entry posting: debit and credit sides, ledger lines, and fiscal periods that can be opened, locked and closed. Sales invoices, vendor bills, payments and journal entries all flow into the same ledger, which is what makes the trial balance, profit and loss, balance sheet and cash-flow reports trustworthy.

Access control with field-level permissions

Beyond basic roles, ERPX models granular permissions - lead:create, invoice:void, payment:refund - plus field-level permissions and record scopes. A role can see a record but not edit certain fields, and the same model drives the UI: sidebar items, buttons and route guards all read from the same permission list the API enforces. There is no client-side-only security.

Auth, MFA and email OTP

Authentication is JWT access and refresh tokens with rotation and reuse detection, argon2 password hashing, database-backed sessions, and RFC 6238 TOTP multi-factor authentication with backup codes. Email verification, password reset and invitations all go through six-digit OTP codes sent with SMTP - deliberately stateless and simple to reason about.

Subscriptions and payments

Because ERPX is SaaS, it has a plan layer - FREE, STARTER, PRO and ENTERPRISE - with features, entitlements and usage counters, plus coupons. Checkout runs through Razorpay or Stripe behind a single provider interface, and subscription activation only happens after a signature-verified webhook, never from an unverified client redirect.

The AI layer

The product includes an AI copilot in the UI and insight cards on the dashboard. Underneath is a provider-agnostic AI gateway that can route to OpenAI, Gemini, Claude, Ollama, Azure OpenAI or AWS Bedrock, with domain agents, RAG knowledge indexing and a semantic cache. The AI never bypasses permissions - it answers over the same role-scoped data the user can already see.

Testing and observability

The backend carries hundreds of test specs across auth, billing, accounting, reporting and the AI subsystems, and the whole API is documented with Swagger. Redis-backed rate limiting, Helmet security headers, CSRF protection, pino logging and OpenTelemetry tracing were part of the build from day one - observability is not a feature you can add after launch.

What I would tell myself next time

ERPX taught me more about architecture than any tutorial ever could. If you want the deep technical tour with the module breakdown and the system diagram, the full case study walks through every part of the build.

Enjoyed this?

Let's build something meaningful together.

Get In Touch