Platform Architecture
Overview
NuSaaS is a multi-tenant ERP and POS platform composed of:
| Layer | Technology | Role |
|---|---|---|
| Frontend SPA | React, Vite, Tailwind CSS | Browser UI for all business modules |
| REST API | Laravel, PHP 8.2+ | Business logic, auth, integrations |
| Real-time | WebSockets | Live updates (POS, notifications) |
| Primary database | MySQL 8.0 | Transactional data, tenant isolation |
| Cache & queues | Redis | Cache, sessions (prod), queue broker |
| Search | Full-text search engine + Laravel Scout | Global and module search indexes |
| Docs portal | Docusaurus (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 role | Default public port | Purpose |
|---|---|---|
| API | 4000 | REST API |
| Web | 4001 | React SPA |
| Docs | 4002 | Product documentation site |
| WebSocket | 8083 | Real-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:
| Surface | Dev URL | Notes |
|---|---|---|
| React app | http://localhost:5173 | Vite dev server |
| Docs | http://localhost:4002 | Docusaurus dev server |
| API | Local domain or :8000 | Laravel Herd or artisan serve |
| WebSocket | ws://localhost:8083 | Matches 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:
| Surface | Public URL |
|---|---|
| Frontend | https://nusaas.com |
| Docs | https://docs.nusaas.com |
| API | https://api.nusaas.com |
| WebSocket | VPS-backed public Reverb host, for example wss://ws.nusaas.com |

Request flow: browser to database

- The React SPA is a static build served by Nginx in production, or Vite in development.
- All business operations call the REST API at
/api/*with a JWT stored client-side. - The API runs authentication, tenant scoping, onboarding checks, permissions, and optional module gates before controllers execute.
- Controllers use tenant-scoped models and services; reads may hit Redis cache; search uses the Scout integration; writes go to MySQL.
- 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
| Mechanism | Details |
|---|---|
| JWT | Token-based API auth with access and refresh tokens |
| Permissions | Fine-grained permission slugs per action (e.g. view sales, manage settings) |
| Roles | Assigned per tenant; gate navigation and API access |
| Platform admin | Separate 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.
| Type | Examples | Gating |
|---|---|---|
| Business modules | POS, accounting, inventory, HR, agro, logistics, manufacturing | Entitlement middleware per module |
| Connectors | Mobile money, eTIMS, bank integrations, SMS, messaging | Same 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:
| Trigger | Effect |
|---|---|
| Sale completed | Auto-post to general ledger (revenue, COGS, tax) |
| Payroll confirmed | Post payroll journals |
| Supplier bill approved | Post supplier bill to GL |
| HR workflow events | Email 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
| Component | Responsibility |
|---|---|
| Queue worker | Emails, search indexing, GL posting, webhooks |
| Scheduler | Subscriptions, payroll accrual, backups, forecasts, reports |
| Search sync | Push 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
| Concern | Choice |
|---|---|
| UI | React, Radix UI, Tailwind CSS |
| Routing | React Router |
| Server state | TanStack Query |
| Client state | Zustand |
| HTTP | Axios with JWT interceptor |
| Real-time | Laravel Echo |
| Offline POS | IndexedDB + service worker (PWA, selectively enabled) |
| Charts | ECharts, 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
- The HTTP client attaches the JWT from browser storage on every request.
- The API base URL is configured per environment (production URL vs local proxy).
- 401 redirects to login; 403 onboarding redirects to onboarding; 403 trial expired redirects to billing (cloud).
- 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)
| Aspect | Implementation |
|---|---|
| Engine | MySQL 8.0 |
| Tenancy | Shared database, row-level tenant isolation |
| Migrations | Version-controlled schema migrations |
| Transactions | Critical paths (sales, GL, payroll) wrapped in database transactions |
| Soft deletes | Common on business entities |
Do not expose the database port to the public internet in production.
Redis (cache, queue, WebSocket scaling)
| Use | Production |
|---|---|
| Cache | Application cache, config cache, rate limiting |
| Queue | Background jobs consumed by workers |
| Sessions | Optional in production |
| WebSocket scaling | Pub/sub for real-time fan-out |
Redis uses persistence and LRU eviction with a configurable memory cap.
Full-text search
| Aspect | Details |
|---|---|
| Integration | Laravel Scout |
| Indexed entities | Products, customers, suppliers, and other searchable models |
| Tenant isolation | All search queries scoped by tenant |
| Sync mode | Index updates queued in production |
| Global search | Aggregates 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)
| Pattern | When | Example |
|---|---|---|
| REST API | Frontend ↔ Backend | POS sale creation |
| Shared database | Module reads another module's data | Accounting reads sales for GL posting |
| Domain events | Loose coupling after a business action | Sale completion triggers GL posting |
| Integration services | Explicit cross-module writes | Agro production synced to accounting |
| Search | Cross-entity discovery | Global search across products and customers |
| WebSockets | Real-time UI | Live POS terminal updates |
| Scheduled jobs | Periodic cross-module work | Forecast snapshots, subscription renewals |
| Webhooks (inbound) | External → API | Payment provider callbacks |
Modules are modular monolith boundaries inside one Laravel application, gated by middleware and tenant module flags — not separate microservices.
Security boundaries
| Layer | Control |
|---|---|
| Network | Database, Redis, and search on internal network only |
| API | JWT, permission middleware, tenant scope |
| CORS | Restricted to approved frontend origins |
| Proxies | Trusted proxy configuration for reverse proxy / CDN |
| Webhooks | IP allowlists for payment and integration callbacks |
| Secrets | Environment 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.
Related documentation
- Docker Stack Setup — container installation
- Configuration & Environment — environment reference
- Reverse Proxy — Nginx/SSL in production