Skip to content
Seifeldin Ali

MERX Systems, an Iano product · 2026, actively developed

MERX

Self-hosted, multi-tenant ERP platform with pluggable Accounting, Inventory, and Supply modules.

Built + maintained by Iano

Visit live site ↗

1 / 25

Summary

MERX is a self-hosted, multi-tenant ERP platform built as a Turborepo monorepo on Next.js 15 and Postgres. It ships three first-party business modules (Accounting, Inventory, and Supply) on top of a pluggable module system, so new capabilities self-register into the shell without touching the core. Tenant isolation, role-based permissions, an audit log, and a branded PDF document engine are built into the platform layer.

Context

Businesses needed an ERP they could run themselves, covering accounting, inventory, and supply, without SaaS lock-in, and extensible enough to add their own modules rather than being boxed into a fixed feature set.

Role & team

Iano, a two-person marketing agency I co-founded. Both founders engineered the build.

Stack

  • Next.js 15 (App Router, RSC, Server Actions)
  • TypeScript (strict)
  • pnpm + Turborepo
  • PostgreSQL 16
  • Drizzle ORM
  • Redis 7 + BullMQ
  • MinIO (S3-compatible)
  • Auth.js v5
  • Tailwind + shadcn/ui + Radix
  • react-hook-form + Zod
  • @react-pdf/renderer + Puppeteer
  • Nodemailer + React Email
  • Caddy
  • Vitest + Playwright

Key decisions

  1. Chose a pluggable module registry over a monolith: each module ships a manifest ({ key, nav, permissions, templates, hooks }) and the Next.js shell auto-mounts navigation, routes, permissions, and PDF templates at boot, so adding a module is `cp` + manifest, not core surgery.
  2. Chose AsyncLocalStorage-based tenant isolation over ad-hoc filtering: a TenantContext is set per request and every query is scoped by org_id via requireOrgId(), making isolation a platform guarantee rather than a per-query discipline.
  3. Chose Drizzle ORM over a heavier ORM for type-safe, migration-first schema work with drizzle-kit.
  4. Chose self-hosted Docker + Caddy over a managed SaaS deploy to deliver the “run it yourself, auto-HTTPS on your own domain” promise.

Architecture

A single Next.js shell (`apps/web`) hosts all modules; shared packages provide the database (`db`), auth/RBAC/audit (`auth`, `core`), UI primitives (`ui`), and the PDF engine (`docs`). Tenant context flows through AsyncLocalStorage; RBAC is permission-string based (e.g. `accounting.write`) resolved per membership, throwing ForbiddenError when missing. Documents are React-PDF templates registered by key and rendered with shared brand styling. Background work runs on Redis + BullMQ; files live in MinIO; email goes through Nodemailer/React Email; Caddy fronts the production stack with automatic HTTPS.

Challenges

  • Guaranteeing tenant isolation: Across every query, without leaking data, this is solved with a request-scoped TenantContext and mandatory org_id scoping enforced in @merx/core.
  • Making the platform genuinely extensible: Solved with self-registering module manifests that the shell auto-mounts at boot, so modules contribute nav, routes, permissions, and document templates declaratively.
  • A reusable, on-brand document pipeline: Solved with a keyed React-PDF template registry (with a Puppeteer fallback) shared across modules.