Back to Portfolio
Next.jsPostgreSQLERPArchitecture

Building Scalable ERP Systems with Next.js and PostgreSQL

May 2026 8 min readBy Shivaji Tiwari

Why ERP Systems Are Hard to Scale

Enterprise Resource Planning systems seem straightforward until you hit real-world requirements: multi-tenant data isolation, complex permission hierarchies, real-time dashboards with thousands of concurrent users, and audit trails for compliance. Most tutorials show you how to build a CRUD app — not how to architect a system that survives production.

I've built ERP systems for real estate companies, schools, inventory management, and legal firms. Here's the architecture pattern I now use consistently.

The Three-Layer Architecture

Every ERP I build uses three clear layers:

  • Presentation layer — Next.js 14 with SSR for dashboard pages, client components for interactive tables and charts
  • API layer — Next.js API routes (or a separate Node.js/NestJS service for larger systems) with typed request/response schemas
  • Data layer — PostgreSQL with a well-designed schema, row-level security for multi-tenancy, and indexed queries for report generation

The key insight: keep business logic in the API layer, never in components or raw SQL. This makes testing straightforward and lets you swap databases if needed.

Multi-Tenancy with Row-Level Security

PostgreSQL's Row Level Security (RLS) is underused in Next.js stacks. Instead of filtering every query manually with WHERE tenant_id = ?, you define policies at the database level:

ALTER TABLE projects ENABLE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation ON projects
  USING (tenant_id = current_setting('app.tenant_id')::uuid);

Set app.tenant_id in your API middleware per request and every query is automatically scoped. No accidental data leaks across tenants, even if a query forgets the WHERE clause.

RBAC — Role-Based Access That Actually Works

I use a simple but flexible permission model: roles define what a user can do, resources define what they can do it to. A middleware checks both before any API route runs:

  • ADMIN — full CRUD on all resources
  • MANAGER — full CRUD on their department's resources
  • STAFF — read + create, no delete
  • VIEWER — read-only

Store permissions in PostgreSQL, cache them in JWT claims on login. Re-fetch only on role change. This keeps API calls fast while permissions stay accurate.

Real-Time Dashboards Without WebSockets

For most ERP dashboards, you don't need WebSockets. Next.js 14's server components with revalidate every 30–60 seconds gives "good enough" real-time for operational dashboards. Save WebSockets for truly live data like stock prices or chat.

For report generation (which often involves expensive JOIN queries), I queue them as background jobs and show a loading state. Users get their report in seconds, not a timed-out browser request.

Key Takeaways

  • Use PostgreSQL RLS for multi-tenancy — it's safer than application-level filtering
  • Keep business logic in the API layer, not in components
  • Cache RBAC permissions in JWT to avoid a database round-trip per request
  • Queue heavy reports — don't run them synchronously in API routes
  • Next.js 14 + PostgreSQL handles most ERP workloads without microservices complexity
Need an ERP System Built? →