Shelter4U — Property Brokerage & Channel Partners CRM
Led development of a property brokerage CRM covering the full lead-to-closure lifecycle, self-deployed on AWS EC2 behind NGINX, and directly diagnosed the production issues that came with owning that infrastructure.
Contents
Executive Summary
Shelter4U is a CRM for a property brokerage's channel partners, built to own the full lead-to-closure lifecycle — lead intake, follow-ups, site visits, and deal finalization — as one connected flow rather than disconnected tools. Led development and owned the technical decisions end to end, including a relational model mapping Clients to the Properties they're interested in, backend RBAC via NestJS Guards, and a self-managed deployment on AWS EC2 with PM2 and an NGINX reverse proxy under a unified domain with SSL. Also diagnosed and resolved the production issues that came with running that infrastructure directly — permission errors, NGINX rewrite loops, SQL enum mismatches, and database constraint violations.
Business Context
Constraints
Full lifecycle ownership
Technical decisions spanned the entire lead-to-closure flow — intake, follow-ups, site visits, deal finalization — so the data model had to hold up consistently across every stage, not just one module.
Self-managed infrastructure
There was no managed platform underneath the application. EC2, PM2, NGINX, and SSL were all configured and operated directly, which meant infrastructure failure modes — permissions, reverse-proxy config, process supervision — were fully owned rather than abstracted away.
Unified domain routing
The SPA frontend and backend API had to be served under one domain through NGINX, making reverse-proxy configuration a central, correctness-critical piece of the deployment rather than an incidental detail.
Relational integrity for many-to-many interest mapping
Clients and Properties needed structured relational mapping so a client's interest across multiple properties, and vice versa, stayed consistent as leads progressed through stages.
System Blueprint
A React SPA is served, together with the backend API, through a single NGINX reverse proxy on a unified domain, talking to a NestJS backend that enforces RBAC and reads/writes a relational PostgreSQL schema for Properties and Clients.
Frontend
React + TypeScript SPA covering lead intake, follow-ups, site visits, and deal finalization.
API Layer
NestJS backend enforcing role-specific access to CRM modules via NestJS Guards.
Data Layer
PostgreSQL schema with structured relational mapping between Clients and the Properties they're interested in.
Deployment
Self-managed AWS EC2 (Ubuntu) host, process-managed with PM2, fronted by an NGINX reverse proxy serving both the SPA and the API under one domain with SSL.
Architecture
Lead-to-closure lifecycle ownership
The system covers lead intake, follow-ups, site visits, and deal finalization as one connected flow, with technical decisions owned across the whole lifecycle rather than one slice of it.
Relational modeling for Properties and Clients
Clients and Properties are mapped relationally rather than denormalized, so a client interested in multiple properties — or a property with multiple interested clients — stays consistent as records change.
RBAC via NestJS Guards
Role-specific access to CRM modules is enforced with NestJS Guards on the backend, controlling what each channel partner or staff role can reach.
Self-managed deployment topology
Services run on an AWS EC2 Ubuntu host under PM2, with NGINX reverse-proxying both the SPA and the API under a unified domain and SSL — a topology fully configured and operated directly rather than delegated to a managed platform.
Engineering Decisions
Unified NGINX domain vs. separate frontend/backend domains
- Serve the frontend and backend on separate subdomains.
- Serve both under one domain, with NGINX routing requests to the SPA or the API based on path.
Relational mapping vs. denormalized records for Properties and Clients
- Store client interest as a field on the property record (or vice versa), duplicating data per relationship.
- Model Clients and Properties with a structured relational mapping between them.
Production Stories
Diagnosing infrastructure failures on a self-managed EC2 stack
Lessons Learned
Self-managed infrastructure (EC2 + PM2 + NGINX) puts every layer's failure modes directly in your hands — there's no managed platform smoothing over permission, proxy, or schema issues.
Routing both a frontend and an API through one NGINX config under a unified domain keeps deployment simple, but makes that config a single point where a bad rule affects the whole application at once.
Modeling a many-to-many relationship relationally, instead of denormalizing it onto one side, is what keeps a client's interest across properties (and a property's interest from clients) from silently drifting out of sync.
Related Notes
- One NGINX Config, Two Apps, One Failure Domain
Routing a frontend and backend through a single NGINX config under one domain simplifies deployment — and turns that config into a single point where one bad rule takes down both.