Skip to content
Nitesh Shrivastav.
← All case studies
CRMLaravelAWS

InsureCRM — a multi-tenant policy CRM running in production on AWS

A US insurance back office needed to run policies, customers, renewals and compliance from one role-aware system. I built and now operate a Laravel + React CRM on AWS ECS — including the production-stability and performance work that keeps it fast and reliable.

Multi-tenant
policy CRM on AWS
Role
Full-stack & DevOps engineer
Timeline
2025–2026
Stack
Laravel · React · TypeScript · PostgreSQL · Redis · AWS ECS · Terraform

The problem

A US insurance back office was running its book of business — customer policies, renewals, appointments and agent activity — across tools that didn't talk to each other. They needed a single, role-aware system that several teams could use side by side, with a complete, auditable history of every change for compliance.

The work wasn't just to build it: it had to run reliably in production under real daily use, which is where most of the engineering ended up.

Architecture

The system is split into a Laravel REST API and a React (TypeScript) SPA, talking over HTTP. PostgreSQL is the system of record; Redis holds sessions, cache and queues. The whole thing is containerised and runs on AWS ECS Fargate behind an Application Load Balancer, with RDS for Postgres, ElastiCache for Redis, S3 for documents, and Secrets Manager for configuration. Infrastructure is described in Terraform and shipped through GitHub Actions.

The decisions that mattered:

DecisionChoseWhy
TenancyShared schema + tenant scope + RBACSimple to operate, safe by default
StateSessions/cache in Redis, not the containerStateless tasks, safe to scale and replace
DeploysSHA-pinned task defs, blue/green via ALBRoll forward and back with confidence
AuditPermanent change history + access logCompliance and traceability

The build

Tenant isolation is enforced at the framework level so a forgotten where clause can never leak another tenant's data:

app/Models/Concerns/BelongsToTenant.php
trait BelongsToTenant
{
    protected static function bootBelongsToTenant(): void
    {
        static::addGlobalScope('tenant', function (Builder $query) {
            if ($tenantId = auth()->user()?->tenant_id) {
                $query->where($query->getModel()->getTable().'.tenant_id', $tenantId);
            }
        });
        static::creating(fn ($m) => $m->tenant_id ??= auth()->user()?->tenant_id);
    }
}

On top of that sits a role-and-permission layer (admin / manager / agent) checked on the server for every action, plus two distinct audit trails: a permanent change history of customer/policy edits for compliance, and a separate access log of logins, permission denials and sensitive page views.

Running it in production

This is where the real work was. After launch, ECS tasks were being killed under load — traced to a PHP-FPM worker limit and sessions living on ephemeral containers. I tuned FPM, moved sessions and cache to Redis, and added a blue/green deploy with a pre-flight validation gate and tolerant health checks, so a bad release can't take the product down.

Then I went after latency. The dashboard was firing a dozen API calls on every mount and one notifications query was taking ~11 seconds on millions of audit rows. I bundled the startup calls, added Redis caching and trigram indexes, and deduplicated queries on the frontend:

11s → 5ms
notifications query
12 → 2
dashboard API calls
Redis
sessions & cache
Blue/green
zero-downtime deploys

I also found and removed a duplicate audit-write path that was doubling the access-log growth rate, and added a safe retention policy (chunked, batched deletes inside transactions, with snapshots) so the audit tables stay bounded.

Outcome

InsureCRM runs in production as the team's day-to-day system: policies, customers, renewals and appointments in one role-aware place, with a complete audit trail and a stable, fast experience under real load. Discovery, the Laravel API, the React dashboard, and the AWS/Terraform infrastructure were delivered and are operated by one developer — the client coordinates a single person across the whole stack.

Reflection

The lesson that stuck: shipping the features was the easy half — keeping it fast and up in production is the half that earns trust. Enforcing tenant isolation in the framework and pushing state into Redis early made everything afterwards safer to scale. Next time I'd put the latency budgets and load-testing in from day one rather than discovering them under real traffic.

Want a result like this?

Tell me what you’re building. I reply within one business day.