System Architecture Overview
What Is Prism
Prism is a commercial real estate (CRE) intelligence platform for industrial property markets. It provides a centralized system for tracking buildings, lease comparables (comps), tenant-in-market activity (TIMs), ownership, brokerage relationships, analytics, and reporting — all within a multi-tenant, organization-isolated environment.
High-Level Architecture
┌──────────────────────────────────────────────────────────────────────────┐
│ End Users │
│ (Browsers / BI Tools / API Clients) │
└─────────────────────────────┬────────────────────────────────────────────┘
│
┌─────────▼──────────┐
│ Cloudflare Edge │ ← TLS termination, WAF, caching
│ (CDN + Proxy) │
└──┬──────────────┬───┘
│ │
┌────────────▼──┐ ┌──────▼──────────┐
│ Cloudflare │ │ Render │
│ Pages │ │ (Go Backend) │
│ (Frontend) │ │ Port 8080 │
└───────────────┘ └──────┬───────────┘
│
┌────────────┼────────────┐
│ │ │
┌──────▼──┐ ┌─────▼────┐ ┌─────▼─────┐
│ Postgres │ │ Redis │ │ Clerk │
│ + PostGIS│ │ (Cache) │ │ (Auth) │
│ (Supabase│ │ (Upstash)│ │ (JWT) │
│ / RDS) │ │ │ │ │
└──────────┘ └──────────┘ └───────────┘
Service Boundaries
| Service | Technology | Hosting | Purpose |
|---|---|---|---|
| Frontend | React 19 + Vite + TypeScript | Cloudflare Pages | SPA for all user interactions |
| Backend API | Go (Chi router) | Render | REST API, business logic, auth enforcement |
| Database | PostgreSQL 15 + PostGIS | Supabase (managed) | Persistent storage, spatial queries, RLS |
| Cache | Redis | Upstash (managed) | Query caching, rate limit state |
| Auth Provider | Clerk | Clerk (SaaS) | SSO, MFA, JWT issuance, org management |
| Secrets | Infisical | Infisical (SaaS) | Centralized secrets, auto-sync to platforms |
| Error Tracking | Sentry | Sentry (SaaS) | Frontend + backend error/performance monitoring |
| Marketing Site | Next.js | Cloudflare Pages | Public marketing website |
| Docs Site | Next.js + Fumadocs | (Dev/staging) | User-facing documentation |
Tech Stack
Frontend
| Category | Technology |
|---|---|
| Framework | React 19 + TypeScript |
| Build Tool | Vite |
| Routing | TanStack Router (code-based route tree) |
| Server State | TanStack Query |
| Tables | TanStack Table |
| UI Library | Mantine 7 |
| Forms | React Hook Form + Zod validation |
| Maps | MapLibre GL JS |
| Charts | ECharts (via echarts-for-react) |
| Auth | Clerk React SDK |
| Testing | Vitest + React Testing Library + Playwright |
| Linting | ESLint + Prettier |
Backend
| Category | Technology |
|---|---|
| Language | Go 1.22+ |
| HTTP Router | go-chi/chi v5 |
| Database Driver | jackc/pgx v5 (connection pooling) |
| Cache Client | go-redis/redis v9 |
| Auth | Clerk JWT validation (JWKS) |
| Logging | go.uber.org/zap (structured JSON) |
| Metrics | Prometheus client (promhttp) |
| API Docs | swaggo/swag (Swagger/OpenAPI generation) |
| CSV Processing | encoding/csv (stdlib) |
| Testing | Go stdlib testing + testcontainers-go |
Database
| Category | Technology |
|---|---|
| Engine | PostgreSQL 15 |
| Extensions | PostGIS (spatial), pg_cron (optional scheduling) |
| Security | Row-Level Security (RLS) policies |
| Export | Dedicated export schema for BI tools |
Infrastructure
| Category | Technology |
|---|---|
| CI/CD | GitHub Actions |
| Containerization | Docker + Docker Compose |
| Monitoring (local) | Prometheus + Alertmanager + Grafana |
| Monitoring (prod) | Sentry + Render built-in metrics |
| Load Testing | k6 |
| Observability | Grafana Alloy (OpenTelemetry collector, local) |
Backend Architecture Pattern
The Go backend follows Clean Architecture (Ports & Adapters) with strict layering:
┌─────────────────────────────────────────────────────────────┐
│ HTTP Layer (Handler) │
│ - Parse HTTP requests, validate input format │
│ - Call service layer │
│ - Format HTTP responses │
│ - NO business logic │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Service Layer (Business Logic) │
│ - All business rules and validation │
│ - Orchestrate multiple repositories │
│ - Cache management │
│ - NO HTTP knowledge, NO SQL queries │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Repository Layer (Data Access) │
│ - SQL queries and data mapping │
│ - Interface-based (swappable) │
│ - NO business logic │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Database (PostgreSQL) │
└─────────────────────────────────────────────────────────────┘
Dependency Rule: Source code dependencies point inward only. Inner layers never depend on outer layers. Services depend on repository interfaces, not implementations.
Cross-Cutting Concerns
| Concern | Implementation |
|---|---|
| Authentication | Clerk JWT middleware on all protected routes |
| Authorization | Scope-based middleware (RequireScope) |
| Org Isolation | Org ID extracted from JWT, passed to repository queries |
| Rate Limiting | Token bucket per IP (configurable burst/rate) |
| Request Tracing | InjectRequestID middleware, propagated to error responses |
| Logging | Structured zap logger injected into all layers |
| Caching | Redis with pattern-based invalidation on mutations |
| Error Handling | Centralized apierror package with uniform envelope |
| Metrics | Prometheus counters/histograms on all routes |
| Recovery | Panic recovery middleware |
Frontend Architecture Pattern
The frontend follows a module-based architecture organized by feature domain:
frontend/src/
├── app/ # App shell, providers, global styles
├── modules/ # Feature modules (one per domain)
│ ├── buildings/ # Building search, detail, edit
│ ├── comps/ # Comp lease search, detail, edit
│ ├── tims/ # TIM search, detail, edit
│ ├── dashboard/ # Main dashboard
│ ├── map/ # Explore map (multi-layer)
│ ├── analytics/ # Vacancy, absorption, pipeline, inventory
│ ├── operations/ # Needs review, missing data, recent changes
│ ├── data/ # Owners, brokers, leasing cos, reports, parks
│ ├── admin/ # Imports, errors, system setup, data export
│ └── lookups/ # Shared lookup data (regions, corridors, etc.)
├── shared/ # Cross-module shared code
│ ├── api/ # Typed API client, query keys, error handling
│ ├── ui/ # Shared UI components
│ ├── hooks/ # Shared hooks
│ ├── map/ # Shared map utilities
│ └── utils/ # Formatting, validation utilities
└── test/ # Test setup and utilities
Key Frontend Patterns
- Server state: TanStack Query for all API data (no global stores)
- Form state: React Hook Form + Zod schemas
- URL state: Filter/sort/pagination persisted in URL search params
- Module isolation: No cross-module imports except through shared/
- API layer: All API calls through typed wrappers (never raw
fetch)
Data Model Overview
The database has two scoping models:
Public Data (No Org Isolation)
Platform-wide reference data accessible to all authenticated users:
- Buildings — physical properties with address, classification, metrics
- Geography — regions, corridors, submarkets (with PostGIS boundaries)
- Lookups — size tranches, amp tranches, building parks, rail lines, NAICS codes, owner types
- Key Points — named locations (ports, airports, interchanges) for distance calculations
- Labor Data — census block group labor statistics with polygon boundaries
Org-Scoped Data (Org Isolation Enforced)
Private data belonging to a specific organization:
- Comp Leases — lease transaction records with terms, rent, tenant/owner links
- TIMs — tenant-in-market activity tracking with status workflow
- Owners — property owner entities with type classification
- Tenants — tenant entities with NAICS code classification
- Contacts — people linked to owners, tenants, or brokerage firms
- Brokerage Firms — brokerage company entities
- Broker Assignments — relationships between brokers and owners/firms
- Reports — named collections of buildings, comps, and TIMs
- Building Overrides — org-private annotations on shared buildings
- Audit Log — append-only record of all data changes
- Import Jobs — CSV import job tracking
- Error Log — API error telemetry
- API Keys — service-to-service authentication tokens
Export Schema
A separate export schema provides denormalized, BI-tool-friendly tables:
export.export_buildings— platform-wide denormalized buildingsexport.buildings_export— org-scoped merged building + metrics + overridesexport.export_comp_leases— org-scoped denormalized compsexport.export_tims— org-scoped denormalized TIMs
Refresh functions rebuild export tables on demand or via scheduled jobs.
Authentication & Authorization Flow
┌──────────┐ ┌──────────┐ ┌──────────────┐ ┌──────────┐
│ User │────▶│ Clerk │────▶│ Frontend │────▶│ Backend │
│ (Browser)│ │ (SSO) │ │ (Bearer JWT)│ │ (Verify)│
└──────────┘ └──────────┘ └──────────────┘ └──────────┘
│
┌─────────▼─────────┐
│ Extract from JWT: │
│ - user_id │
│ - org_id │
│ - org_permissions │
│ (scopes) │
└───────────────────┘
- User authenticates via Clerk (SSO/MFA)
- Frontend obtains JWT from Clerk React SDK
- Frontend sends
Authorization: Bearer <token>on all API requests - Backend validates JWT signature via Clerk JWKS endpoint
- Backend extracts user ID, org ID, and permission scopes from JWT claims
- Backend middleware enforces required scopes per route
- Backend repository layer filters all org-scoped data by org ID
Role Model
| Role | Scopes | Use Case |
|---|---|---|
viewer | All *:read scopes | Read-only stakeholders |
member | All *:read + *:write + export | Standard operational users |
admin | All member scopes + api_keys:manage, audit:read, export:admin | Org owners/operators |
API Contract
- Base path:
/api/v1 - Auth: Clerk JWT Bearer token (all protected routes)
- Content type: JSON request/response
- Error envelope:
{ code, message, details?, request_id } - List response:
{ data: [], total, has_more } - Pagination: Backend-driven via query params (
page,page_size,sort,order) - OpenAPI: Auto-generated via swaggo, drift-checked in CI
Error Codes
| Code | Meaning |
|---|---|
not_found | Resource does not exist |
validation_error | Input validation failed (field details in details) |
unauthorized | Missing or invalid auth token |
forbidden | Authenticated but insufficient permissions |
conflict | Duplicate or constraint violation |
internal_error | Server error |
Release & Deployment Flow
feature branch → qa → [CI + QA validation] → PR: qa → main → production
- Feature branches merge into
qavia PR - CI runs: Go tests, race detection, vet, Swagger drift check, Docker compose validation
- QA validation on staging
- PR from
qatomainrequires review + green CI - Merge to
maintriggers production deploy - Post-deploy: verify
/health,/ready, spot-check key endpoints
Hosting
| Component | Platform |
|---|---|
| Frontend | Cloudflare Pages (static SPA) |
| Backend | Render (Docker container) |
| Database | Supabase (managed Postgres) |
| Cache | Upstash (managed Redis) |
| Secrets | Infisical → auto-synced to Render + Cloudflare |