A full-stack real estate listing platform with AI-assisted search filter assistant, in-memory page caching, searchable property grid, multi-column sorting, favorite property bookmarking, open house calendar with date range filtering, property detail pages with photo carousels, full-screen galleries, interactive Google Maps, and open house schedules.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Browser (:3000) β
β β
β React Router (BrowserRouter) + ErrorBoundary β
β βββ / β IntroductionPage (hero + features) β
β βββ /search β ListingsPage (chat assistant + filters/sort)β
β β βββ ChatAssistant (collapsible AI chat) β
β β βββ PropertyFilters β
β β βββ SortControls (multi-column) β
β β βββ Pagination (top & bottom) β
β β βββ PropertyCard[] (target="_blank") β
β β βββ PropertyImageCarousel β
β β βββ Open House Badge (green) β
β β βββ Status Badge (Active/Pending) β
β β βββ Favorite Heart Button (β‘ / β₯) β
β β β
β βββ /chat-search β ChatSearchPage (conversational AI search) β
β β βββ ChatAssistant (open by default) β
β β βββ Direct API execution on filter update β
β β βββ Pagination (top & bottom) β
β β βββ PropertyCard[] (target="_blank") β
β β β
β βββ /favorites β FavoritesPage (saved listings view) β
β β βββ Remove All Button (header) β
β β βββ ChatAssistant & PropertyFilters β
β β βββ SortControls (multi-column) β
β β βββ PropertyCard[] (instant list shift) β
β β β
β βββ /openhouses β OpenHousesPage (calendar + filters + cards) β
β β βββ ChatAssistant (AI dates + filters) β
β β βββ react-big-calendar (month view) β
β β βββ Date Range Filter (start/end inputs) β
β β βββ PropertyFilters & SortControls β
β β βββ Pagination (top & bottom) β
β β βββ OpenHouseCard[] (date/time/status) β
β β β
β βββ /property/:id β PropertyDetailPage (opens in new tab) β
β βββ Save / Saved Favorite Button β
β βββ PropertyImageGallery (swipe/lightbox)β
β βββ Centered Stat Icons (Square Feet) β
β βββ Listing Agent Info Card β
β βββ PropertyDetails (dynamic grid) β
β βββ PropertyMap (address directions) β
β βββ Open Houses (active/expired/upcom) β
β β
β useFavorites Hook (localStorage persistence + cross-tab sync) β
β ErrorBoundary (catches rendering crashes + recovery UI) β
β β
β fetch('/api/properties?city=Portland&sortBy=price,date') β
β fetch('/api/properties/:id') β
β fetch('/api/properties/:id/openhouses') β
β fetchFavoriteProperties({ ids, ...filters, sortCriteria }) β
β fetchAllOpenHouses({ startDate, endDate, sortCriteria, ... }) β
β sendChatMessage({ messages, currentFilters, pageContext }) β
β β β
βββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
β Vite dev proxy (vite.config.js)
β /api/* β http://localhost:5000
β
βββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Express Backend (:5000) β
β β
β server.js β app.js β
β βββ cors (origin whitelist & Vercel domain validation) β
β βββ rateLimiter (apiLimiter 300/15m + chatLimiter 15/m) β
β βββ requestLogger (middleware + high-res ms + X-Response-Time)β
β βββ /api/health β health.js β SELECT 1 β
β βββ /api/chat β chat.js β OpenRouter API β
β βββ /api/properties β properties.js β SELECT ... FROM β
β β βββ / (search + hasOpenHouse + sort) β
β β βββ /favorites (POST: ID list query + sort) β
β β βββ /:id (driven by PROPERTY_DETAIL_COLS)β
β β βββ /:id/openhouses (JOIN + status logic) β
β βββ /api/openhouses β openhouses.js β INNER JOIN β
β βββ / (date range + filters + sort) β
β β β
β db.js (connection pool) β
β β β
ββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββ
β
β mysql2/promise (port 3306)
β
ββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββ
β MySQL 8 Docker Container β
β (idx-mysql-local) β
β β
β Database: rets β
β βββ rets_property (53,122 rows β listings, photos, coords) β
β βββ rets_openhouse (4,282 rows β open house events) β
β β
β Imported from database/ SQL files on first container start β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
IDXExchange/
βββ backend/ # Node.js + Express REST API
β βββ src/
β β βββ server.js # Entry point β starts Express
β β βββ app.js # Express app (middleware + routes)
β β βββ config/db.js # MySQL connection pool
β β βββ utils/logger.js # Zero-dependency logger (URL redaction + error sanitization)
β β βββ middleware/ # requestLogger & rateLimiter
β β βββ routes/ # health.js, properties.js, openhouses.js, chat.js
β βββ tests/ # Jest + Supertest (101 tests across 5 test suites)
β βββ .env # Backend env vars (LLM_API_KEY, DB vars, ALLOWED_ORIGINS)
β βββ package.json
β
βββ frontend/ # React + Vite
β βββ src/
β β βββ main.jsx # React entry point β wraps App in StrictMode
β β βββ App.jsx # React Router setup + ErrorBoundary
β β βββ api/ # API client (propertyApi, chatApi)
β β βββ hooks/ # useFavorites hook (localStorage + cross-tab sync)
β β βββ utils/ # parsePhotos, formatPrice, formatTime, formatDate, prefetchCache
β β βββ test/ # Vitest setup + tests (84 tests across 13 suites)
β β βββ stylesheets/ # All CSS stylesheets
β β βββ components/ # UI components (Sidebar, PropertyCard, PropertyFilters, ChatAssistant, ErrorBoundary, etc.)
β β βββ pages/ # IntroductionPage, ListingsPage, ChatSearchPage, FavoritesPage, OpenHousesPage, PropertyDetailPage
β βββ .env # VITE_GOOGLE_MAPS_API_KEY (gitignored)
β βββ .env.example # Template for frontend env vars
β βββ vercel.json # Vercel deployment config (API proxy rewrites + SPA fallback)
β βββ vite.config.js # Dev server + API proxy + Vitest config
β βββ package.json
β
βββ database/ # SQL imports (mounted into Docker)
β βββ 01_rets_openhouse.sql
β βββ 02_rets_property.sql
β βββ 03_add_indexes.sql
β
βββ docs/ # Project documentation
β βββ ARCHITECTURE.md
β βββ CODING_STANDARDS.md
β βββ OVERVIEW.md
β βββ TASKS.md
β βββ SUPPORT_TASKS.md
β βββ LOCAL_RUN_GUIDE.md # Dev vs Production local workflow & verification guide
β βββ CLOUD_DEPLOYMENT_GUIDE.md # Railway + Render + Vercel deployment guide
β βββ FILE_GUIDE.md # Comprehensive file-by-file reference guide
β βββ TEST_GUIDE.md # Comprehensive 185-test breakdown & diagnostic guide
β βββ change_log.md
β βββ decision_log.md
β
βββ docker-compose.yml # MySQL 8 container config
βββ .env # Docker env vars (gitignored)
βββ .gitignore
Docker Compose starts a MySQL 8 container (idx-mysql-local) on port 3306. On first start, it automatically imports the SQL files from the database/ directory via Docker's /docker-entrypoint-initdb.d/ mechanism. This creates two tables:
rets_propertyβ 53,122 property listings with address, price, beds, baths, photos (JSON), coordinates,StandardStatus, etc.rets_openhouseβ 4,282 open house events linked to properties byL_DisplayId
The Express server connects to MySQL using a connection pool (db.js). It exposes REST endpoints under /api/:
GET /api/healthβ RunsSELECT 1to verify the database is reachableGET /api/propertiesβ Queriesrets_propertywith filters, multi-column sorting, pagination, and ahasOpenHouseboolean flagPOST /api/properties/favoritesβ Accepts an array of property display IDs in JSON body ({ ids: [...] }) and returns matching properties with filter, sort, and pagination supportGET /api/properties/:idβ Fetches property details dynamically using a configurablePROPERTY_DETAIL_COLUMNSarrayGET /api/properties/:id/openhousesβ Fetches open house events fromrets_openhousejoined withrets_property, adding server-side status logic (active,expired,upcoming)GET /api/openhousesβ Lists all open houses with optionalstartDate/endDatefiltering, INNER JOIN withrets_propertyfor property context, pagination (up to 500), and status computation
Every request is protected by origin-whitelisted CORS validation and tiered rate limiting (apiLimiter & chatLimiter), and logged by the requestLogger middleware with timestamp, method, URL, status code, duration in milliseconds, and attaches an X-Response-Time header.
The React app runs on port 3000 with a split-screen dashboard layout: a fixed sidebar on the left and a main content canvas on the right protected by a React Error Boundary (ErrorBoundary.jsx).
Navigation uses react-router-dom to route between pages:
/β Introduction Page (hero section with headline, CTA, and feature cards)/searchβ Search Page (property filters + multi-column sort + listings grid + pagination + photo carousels + favorite heart buttons)/favoritesβ Favorites Page (view and manage favorited listings, with Remove All button, filters, sort, pagination, and instant card removal)/openhousesβ Open Houses Page (react-big-calendar with range selection, date range filter panel, open house cards with property details, pagination)/property/:idβ Property Detail Page (Save/Saved favorite button, photo gallery with touch swipe + lightbox, centered stat icons with Square Feet label, Listing Agent Information card above Description, dynamic details grid, Google Maps embed with same-line Get Directions address link, and open house schedule)
Favorites state is managed globally by the custom useFavorites hook, which persists to localStorage and syncs across browser tabs in real time.
Clicking any property card on the Search or Favorites page opens the detail page in a new browser tab (target="_blank"), allowing users to keep browsing listings while examining property details.
# From project root
docker compose up -d
# Wait for the import to complete (rets_property is ~632 MB, takes several minutes)
docker compose logs -f mysql
# Look for: "ready for connections"cd backend
cp .env.example .env # First time only β edit if needed
npm install
npm run dev # Starts on http://localhost:5000cd frontend
cp .env.example .env # First time only β add your VITE_GOOGLE_MAPS_API_KEY
npm install
npm run dev # Starts on http://localhost:3000Open http://localhost:3000 to see the property listings.
# Backend tests (101 tests across 5 suites)
cd backend
npm test
# Query Performance & EXPLAIN Benchmark Suite
npm run perf
# Frontend tests (84 Vitest tests across 13 suites)
cd frontend
npm test| Variable | Description |
|---|---|
MYSQL_ROOT_PASSWORD |
MySQL root password |
MYSQL_DATABASE |
Database name (default: rets) |
MYSQL_PORT |
Host port for MySQL (default: 3306) |
| Variable | Description |
|---|---|
DB_HOST / MYSQLHOST |
MySQL host (default: localhost for local dev) |
DB_USER / MYSQLUSER |
MySQL user |
DB_PASSWORD / MYSQLPASSWORD |
MySQL password |
DB_NAME / MYSQLDATABASE |
Database name |
DB_PORT / MYSQLPORT |
MySQL port (default: 3306) |
MYSQL_URL / MYSQL_PUBLIC_URL |
Optional full connection URL string (Railway cloud / TCP proxy) |
PORT |
Express server port (default: 5000) |
ALLOWED_ORIGINS |
Comma-separated CORS allowed origins (production/staging) |
| Variable | Description |
|---|---|
VITE_GOOGLE_MAPS_API_KEY |
Google Maps API key (Maps Embed API enabled) |