FocusApp
Building a production-grade, multi-tenant ERP and CRM platform from scratch — with database-per-tenant isolation, real-time collaboration, zero-downtime deploys, and GST-ready accounting.
About the Project
What is FocusApp?
FocusApp is a fully hosted, subscription-based ERP and CRM platform built for SMBs that need sales pipeline management, task workflows, production tracking, email campaigns, and accounting — all under one roof, scoped entirely to their own tenant.
The product is multi-tenant by design: every customer gets an isolated subdomain (client.focusapp.cloud), their own database, their own encryption key, their own set of permissions governed by the subscription plan they are on, and no visibility into any other tenant's data.
The challenge was not just building the features. It was building a platform architecture that could hold all of them — reliably, securely, and with the ability to deploy continuously without taking any tenant offline.
The Core Problem
Most off-the-shelf ERP tools are either too rigid or too expensive for growing businesses. None of them offered multi-tenant isolation, dynamic plan-based permissions, SPA-quality UX, zero-downtime CI/CD, PWA mobile support, and GST accounting in a single product. It had to be built.
Isolation Model
Database-per-tenant
A mis-scoped query fails with a connection error — not a data leak.
Permission Model
Plan-driven, live updates
Upgrade a client in the admin panel — their instance reflects it within minutes.
Deploy Model
17-step zero-downtime
Single git push deploys both nodes, byte-identical assets, no tenant interruption.
Engineering Deep-Dive
Eight Hard Problems, Eight Real Solutions
Each challenge required a deliberate architectural decision — not just a library install. Here is what we built and why.
Multi-Tenancy Without a Bloated ORM
Problem
True database-per-tenant multi-tenancy means every query must be automatically scoped to the correct tenant's database. One mis-scoped query is a data breach.
Solution
Built on stancl/tenancy with subdomain middleware. Each tenant gets a dedicated MySQL database and a per-tenant encryption key generated at provisioning time. A CompanyRequiredMiddleware further narrows scope so a misrouted request fails loudly rather than leaking data silently.
Dynamic Permissions Tied to Subscription Plans
Problem
Permission systems are usually static — seeded once, done. But in a SaaS product where tiers unlock different modules, permissions must change when a plan changes — instantly, without developer involvement.
Solution
A two-layer architecture: a central Admin Panel defines plans and permission groups; a permissions:update artisan command fetches the current plan via JWT-authenticated API and upserts it into the tenant DB, clearing all caches. Runs on every deploy and can be triggered via a GitLab CI variable alone.
Modern SPA Feel Without Ditching the Server
Problem
A pure React SPA means a full JS bundle hydration before first paint — poor for a CRM with heavy data tables. Pure Blade means no shared component state and no React ecosystem.
Solution
Inertia.js as the bridge. The server renders initial page data (no API round-trip on first load), Inertia handles client-side navigation (no full page reloads), and React components manage all interactivity. Core high-traffic pages migrated first; a v2.0 consolidation path was documented as explicit technical debt.
Real-Time Collaboration Across Tenants
Problem
Task comments, status changes, and import progress needed to appear instantly. Polling at the scale of hundreds of concurrent users across tenants would saturate the server.
Solution
Laravel Reverb (first-party WebSocket server) backed by Redis. Each tenant's events broadcast on private, tenant-scoped channels — a comment on tenant-a never appears on tenant-b. Laravel Echo on the React frontend subscribes and updates UI state without re-rendering the page.
Email Infrastructure at Scale
Problem
Transactional emails (OTPs, device approvals) and bulk marketing emails are fundamentally different workloads. Mixing them on one SMTP lets marketing bounce rates contaminate transactional delivery.
Solution
Brevo for transactional email with per-tenant domain verification and webhook secret validation. A separate bulk mail system with a step-based wizard, per-credit deduction only on confirmed delivery, live job cancellation, and a low-credit warning banner before the tenant hits zero.
Zero-Downtime Deployments Across Three Environments
Problem
A Laravel app with migrations, permission reseeding, and a Vite build is not trivially zero-downtime. On a two-server load-balanced production cluster, both nodes must serve byte-identical assets without serving a mix of old and new code mid-deploy.
Solution
A 17-step GitLab CI script: pull → composer install → Vite build (with OOM fix) → maintenance mode → migrations → permissions update → cache rebuild → maintenance mode off → PHP-FPM reload. The primary node builds and tarballs the frontend; the CI runner transfers it to the replica — the replica never runs npm run build.
Mobile-First PWA on a Complex CRM
Problem
Field sales reps use the app on mobile. A CRM with rich-text editors, audio recording, drag-and-drop attachments, and real-time task feeds is hard to make feel native on a phone browser.
Solution
PWA manifest with iOS/Android install support and a Service Worker for offline caching. Absolute positioning for iOS viewport bar compensation, keyboard offset handling so inputs are never hidden behind the software keyboard, and prefetch visit handling to skip loaders on hover-triggered navigation.
GST-Ready Accounting Without Contaminating the CRM
Problem
Indian SMBs need GST-ready accounting — outward/inward supply aggregation, GSTR-3B summaries, per-financial-year scoping — bolted onto a CRM without polluting the contact data model.
Solution
A double-entry-style module with auto-provisioned ledgers from contacts and suppliers, sale/purchase invoices with HSN codes and GST breakdowns, and a GstSummaryService that aggregates supplies per period and exports GSTR-3B/GSTR-1 compatible JSON and CSV. All company-scoped with atomic DB transactions.
From Architecture → Implementation → Production
Technology Stack
What it's built on
Every technology was chosen for a specific reason — cost at scale, first-party integration, or architectural fit.
Backend
Laravel 11 · PHP 8.4
Frontend
React 18 · Inertia.js · Vite · Tailwind CSS
Real-Time
Laravel Reverb · Laravel Echo · Redis
Database
MySQL (per-tenant)
Search
MeiliSearch
Storage
DigitalOcean Spaces (S3-compatible)
Brevo · Zoho Mail
Payments
Razorpay (webhooks)
Infrastructure
DigitalOcean Droplets · Managed DB · Pulumi (IaC)
CI/CD
GitLab CI · Self-hosted runner
PWA
Service Worker · Web Push API
Why Inertia.js?
Full SPA would have required API versioning, token management, and CORS — tripling the surface area for a team focused on shipping features. Inertia gives 90% of the SPA feel at 30% of the architectural complexity.
Why Laravel Reverb?
First-party integration, no per-connection pricing, self-hosted within the DigitalOcean network (zero egress cost for WebSocket traffic). For a multi-tenant product with hundreds of persistent connections, Pusher would have made real-time economically unviable at scale.
Why database-per-tenant?
Shared schema with a tenant_id column is faster to build but dangerous to maintain. One missing where clause is a data breach. Database-per-tenant makes isolation architectural — a mis-scoped query fails with a connection error, not a data leak.
Outcome
Before and After
5 major versions. 1 platform. Zero-downtime in production since go-live.
Deployment
Before
Manual SSH, ad-hoc steps
After
17-step CI/CD, zero-downtime, auto on push
Permissions
Before
Hardcoded permissions — code change required
After
Admin panel → CI variable → live in minutes
Multi-Tenancy
Before
Single-tenant or shared-schema
After
Database-per-tenant, per-tenant encryption keys
Frontend
Before
Server-rendered Blade only
After
Inertia.js SPA with React, full PWA
Collaboration
Before
Refresh to see updates
After
WebSocket-powered live tasks, comments, imports
Before
Single SMTP for everything
After
Transactional + bulk separated, credit-metered
FocusApp went from a prototype with hardcoded permissions and manual deploys to a production SaaS serving real tenants — with isolated databases, live collaboration, native mobile UX, GST-ready accounting, and a CI pipeline that deploys the entire stack in a single git push.
Have a complex platform to build or scale?
We specialise in architecting and shipping full-stack SaaS products — from day one to production. Let's talk.