Skip to main content

Platform Architecture

Overview

NuSaaS is a multi-tenant ERP and POS platform composed of:

LayerTechnologyRole
Frontend SPAReact, Vite, Tailwind CSSBrowser UI for all business modules
REST APILaravel, PHP 8.2+Business logic, auth, integrations
Real-timeWebSocketsLive updates (POS, notifications)
Primary databaseMySQL 8.0Transactional data, tenant isolation
Cache & queuesRedisCache, sessions (prod), queue broker
SearchFull-text search engine + Laravel ScoutGlobal and module search indexes
Docs portalDocusaurus (static Nginx)Product documentation

In production and self-hosted deployments, these run as separate containers on a private network. Only selected ports are published to the host; data services remain internal.

For the managed NuSaaS public deployment, the web/ frontend and docs-site/ docs can also be shipped as static builds on Cloudflare Pages while the Laravel API, Reverb, workers, and data services stay on the VPS.


Deployment topology

Service roleDefault public portPurpose
API4000REST API
Web4001React SPA
Docs4002Product documentation site
WebSocket8083Real-time channel server
Worker— (internal)Background job processing
Scheduler— (internal)Cron-style scheduled tasks
Database— (internal)MySQL 8.0
Cache— (internal)Redis
Search— (internal)Full-text search indexes
Updater— (internal)Automated container image updates

Local development uses different ports by design:

SurfaceDev URLNotes
React apphttp://localhost:5173Vite dev server
Docshttp://localhost:4002Docusaurus dev server
APILocal domain or :8000Laravel Herd or artisan serve
WebSocketws://localhost:8083Matches production default when configured

Host ports can be overridden via environment configuration at deploy time.

When Cloudflare Pages hosts the public frontend/docs, the important public origins become:

SurfacePublic URL
Frontendhttps://nusaas.com
Docshttps://docs.nusaas.com
APIhttps://api.nusaas.com
WebSocketVPS-backed public Reverb host, for example wss://ws.nusaas.com

NuSaaS production deployment stack


Request flow: browser to database

NuSaaS request path and module integration

  1. The React SPA is a static build served by Nginx in production, or Vite in development.
  2. All business operations call the REST API at /api/* with a JWT stored client-side.
  3. The API runs authentication, tenant scoping, onboarding checks, permissions, and optional module gates before controllers execute.
  4. Controllers use tenant-scoped models and services; reads may hit Redis cache; search uses the Scout integration; writes go to MySQL.
  5. Long-running work (search indexing, emails, financial posting) is dispatched to Redis queues and processed by worker processes.

Backend architecture

API surface

  • A central API entry handles public auth, webhooks, and health checks.
  • Module routes are grouped by business domain (POS, accounting, HR, inventory, agro, logistics, etc.).
  • Health endpoint — used by container health checks and load balancers.

Authentication & authorization

MechanismDetails
JWTToken-based API auth with access and refresh tokens
PermissionsFine-grained permission slugs per action (e.g. view sales, manage settings)
RolesAssigned per tenant; gate navigation and API access
Platform adminSeparate scope for system-level operations

Multi-tenancy

Tenant data is isolated at the row level using a tenant identifier on scoped models:

  • Queries are automatically filtered to the authenticated user's tenant.
  • Route model binding resolves resources within the current tenant only.
  • New records inherit the tenant from the authenticated session.

Platform administrators can bypass tenant scoping only where explicitly allowed.

Middleware pipeline (typical tenant request)

CORS
→ Authentication (JWT)
→ Email verification
→ Tenant access validation
→ Onboarding completion
→ Usage tracking
→ Trial / subscription check (cloud deployments)
→ Module entitlement check (module-specific routes)
→ Permission check (action-specific routes)
→ Controller

Module system

Modules are registered in the database and enabled per tenant via subscription and add-ons.

TypeExamplesGating
Business modulesPOS, accounting, inventory, HR, agro, logistics, manufacturingEntitlement middleware per module
ConnectorsMobile money, eTIMS, bank integrations, SMS, messagingSame gating + tenant configuration

Before a module route is served, the platform verifies the tenant has that module enabled. The frontend loads a module-aware navigation tree from the navigation API, filtered by enabled modules and user permissions.

Cross-module integration

Modules communicate primarily through domain events and listeners, not direct controller calls:

TriggerEffect
Sale completedAuto-post to general ledger (revenue, COGS, tax)
Payroll confirmedPost payroll journals
Supplier bill approvedPost supplier bill to GL
HR workflow eventsEmail and in-app notifications

Agro and accounting integrate through dedicated integration services that create cost entries and journal references with idempotent upserts.

Model observers enforce invariants and trigger side effects on create, update, and delete.

Background processing

ComponentResponsibility
Queue workerEmails, search indexing, GL posting, webhooks
SchedulerSubscriptions, payroll accrual, backups, forecasts, reports
Search syncPush model changes to the search engine (queued in production)

Production deployments use Redis-backed queues. Local development may run jobs synchronously for simplicity.

WebSockets

  • A dedicated WebSocket service handles real-time channels.
  • Redis supports horizontal scaling of WebSocket fan-out.
  • The frontend uses Laravel Echo; channel auth uses the same JWT as the REST API.
  • Used for real-time POS updates, notifications, and live dashboard refreshes where enabled.

Server-rendered pages

A separate Vite build serves Blade templates for PDF reports, email templates, and legacy auth views. These are not part of the React SPA.


Frontend architecture

Stack

ConcernChoice
UIReact, Radix UI, Tailwind CSS
RoutingReact Router
Server stateTanStack Query
Client stateZustand
HTTPAxios with JWT interceptor
Real-timeLaravel Echo
Offline POSIndexedDB + service worker (PWA, selectively enabled)
ChartsECharts, Recharts

Structure

The SPA is organized by pages (route-level views per module), shared components, API service modules, hooks, contexts (auth, tenant, theme), and utilities.

API communication

  1. The HTTP client attaches the JWT from browser storage on every request.
  2. The API base URL is configured per environment (production URL vs local proxy).
  3. 401 redirects to login; 403 onboarding redirects to onboarding; 403 trial expired redirects to billing (cloud).
  4. Module pages call module-specific API clients that map to REST endpoints under /api/....

Module UI loading

  • Navigation is fetched from the navigation API — backend filters by tenant modules and permissions.
  • App dashboards per module are loaded from dedicated dashboard endpoints.
  • Routes are organized per module in the frontend router.

Real-time client flow

React component
→ WebSocket client initialization
→ Connect to WebSocket host
→ Private channel auth via API (JWT)
→ Listen for domain events

Data layer

MySQL (primary store)

AspectImplementation
EngineMySQL 8.0
TenancyShared database, row-level tenant isolation
MigrationsVersion-controlled schema migrations
TransactionsCritical paths (sales, GL, payroll) wrapped in database transactions
Soft deletesCommon on business entities

Do not expose the database port to the public internet in production.

Redis (cache, queue, WebSocket scaling)

UseProduction
CacheApplication cache, config cache, rate limiting
QueueBackground jobs consumed by workers
SessionsOptional in production
WebSocket scalingPub/sub for real-time fan-out

Redis uses persistence and LRU eviction with a configurable memory cap.

AspectDetails
IntegrationLaravel Scout
Indexed entitiesProducts, customers, suppliers, and other searchable models
Tenant isolationAll search queries scoped by tenant
Sync modeIndex updates queued in production
Global searchAggregates hits across searchable entity types

File storage

  • Default Laravel filesystem for uploads, PDFs, and generated documents.
  • In Docker, a persistent volume is mounted for storage across container restarts.

How modules communicate (summary)

PatternWhenExample
REST APIFrontend ↔ BackendPOS sale creation
Shared databaseModule reads another module's dataAccounting reads sales for GL posting
Domain eventsLoose coupling after a business actionSale completion triggers GL posting
Integration servicesExplicit cross-module writesAgro production synced to accounting
SearchCross-entity discoveryGlobal search across products and customers
WebSocketsReal-time UILive POS terminal updates
Scheduled jobsPeriodic cross-module workForecast snapshots, subscription renewals
Webhooks (inbound)External → APIPayment provider callbacks

Modules are modular monolith boundaries inside one Laravel application, gated by middleware and tenant module flags — not separate microservices.


Security boundaries

LayerControl
NetworkDatabase, Redis, and search on internal network only
APIJWT, permission middleware, tenant scope
CORSRestricted to approved frontend origins
ProxiesTrusted proxy configuration for reverse proxy / CDN
WebhooksIP allowlists for payment and integration callbacks
SecretsEnvironment configuration only — never committed to source control

Local development

Core team members start the full stack with the project's unified start command (see repository README). This runs queue worker, scheduler, WebSocket server, React dev server, docs, and asset builds concurrently.