Back to projects

2025–2026 · In production · Contract

BuildFlowCRM

Commercial real-estate CRM with maker-checker governance

Full-stack Engineer · sole builder

Asset classes
5
Approvals
Maker-checker
Data I/O
Excel round-trip
Stack
MERN

The problem

A commercial real-estate brokerage was tracking properties, deals, and contacts across spreadsheets and disconnected tools. Five different asset classes — warehouses, corporate offices, coworking, retail/malls, and managed offices — each carry their own attributes, so a single flat 'property' table never fit. And with several agents editing shared records, the firm needed edits and deletions to be reviewable rather than silently overwritten — without blocking day-to-day work.

My approach

I built the CRM around two ideas. First, polymorphic entities: one base schema per concept (Projects, Inventory, Contacts) with Mongoose discriminators layering asset-class-specific fields on top — so a coworking listing carries seats and setup fees while a warehouse carries dock and area fields, all in one collection under shared validation. Second, a maker-checker workflow: an employee's update or delete doesn't apply directly — it creates a PendingAction routed to their manager for approval, while the source record is flagged as under-review so the UI reflects its pending state.

Architecture

A Node/Express + Mongoose 8 backend (ESM) is organized into routes → controllers → services → models. Discriminators (project_type / inventory_type / contact type) model the asset classes, while a single PendingAction collection uses a dynamic refPath to reference any entity — Lead, Contact, DeveloperCompany, Project, Inventory, or Land — capturing the requested diff as a Mixed payload and assigning it to the requester's boss. RBAC is two-tier (admin / employee) with an owner-boss hierarchy that governs assignment and record visibility. Auth is JWT over httpOnly cookies, hardened with helmet, a configurable CORS allowlist, express-rate-limit, and proxy-aware trust for Railway. Documents — profile photos, land papers, and project/inventory attachments — stream to Cloudflare R2 through the S3 SDK's multipart uploader behind Multer. A full Import/Export hub round-trips every entity through Excel (ExcelJS) with per-entity schemas, validation, and bulk post-import. Tested with Vitest + Supertest.

Highlights

  • Polymorphic Projects/Inventory/Contacts via Mongoose discriminators — five commercial asset classes in one schema family.
  • Maker-checker approval workflow: employee edits and deletes become PendingActions routed to a manager; records stay flagged under-review until approved.
  • Dynamic refPath approvals — a single collection references any entity type (Lead, Contact, Developer, Project, Inventory, Land).
  • Two-tier RBAC with a manager (boss) hierarchy driving assignment and record visibility.
  • Excel import/export for every entity with per-schema validation and bulk post-import.
  • Document management on Cloudflare R2 (S3-compatible) with multipart streaming uploads.
  • Hardened API: JWT cookie auth, helmet, CORS allowlist, rate limiting, and proxy-aware trust for Railway.

Tech stack

Backend

Node.jsExpressMongoose 8 (discriminators)

Auth / Security

JWT (httpOnly cookies)helmetexpress-rate-limit

Storage / Docs

Cloudflare R2 (S3 API)MulterExcelJS

Frontend

React 19ViteReact Router 7Tailwind 4Framer Motion

Testing / Deploy

VitestSupertestRailwayVercel