A Gradient Boosting model behind a Node.js REST API, estimating residential property prices from location, size, condition and connectivity.
The estimator is a Gradient Boosting Regressor trained on a generated dataset of Indian residential listings.
| Metric | Value | Reading |
|---|---|---|
| R² (validation) | 0.948 |
The model accounts for ~95% of the variance in price |
| MAE | ₹22.3 L |
Typical miss on an individual property |
| RMSE | ₹41.6 L |
Well above MAE, so a minority of large errors dominate — expected where a few luxury properties sit far above the median |
Feature importance, grouped:
| Group | Share |
|---|---|
| Location (city & locality) | 59.2% |
| Size & BHK | 31.3% |
| Amenities | 4.6% |
| Age & connectivity | 2.5% |
| Property type | 2.0% |
| Furnishing | 0.4% |
Location and size together explain over 90% of the prediction, which matches how Indian residential pricing behaves — the same flat in two localities is two different assets. Furnishing barely moves the number.
Browser ──► Express API ──► Python inference ──► model.joblib
(Node.js) (child_process) (Gradient Boosting)
│
▼
MySQL, or a JSON
fallback if unconfigured
Express owns the HTTP layer, serves the frontend as static files, and shells out to Python only for inference. Keeping the model in Python means training and serving share one scikit-learn pipeline, so features are transformed identically in both — the usual source of silent drift when a model is reimplemented in the serving language.
The database is optional. With no MySQL credentials configured the server falls back to a local JSON store, so the project runs immediately after cloning. Configure MySQL when history and analytics need to persist properly.
- Pan-India coverage — 20 cities and 262 localities, each with its own rate basis
- Instant valuation — property attributes in, price estimate out
- Analytics dashboards — average city rates and feature-importance breakdowns
- Prediction history — seeded with 8,000+ records so the analytics views have something to show on first run
- Feedback capture — users can rate an estimate against what they believe the property is worth
- Graceful degradation — no database, no problem; the JSON fallback keeps everything working
Requires Node.js 18+ and Python 3.10+.
Installs Node packages and the pinned Python dependencies, creates backend/.env
from the example, and trains the model if ml/model.joblib is missing:
scripts\setup.batscripts\start.batThen open http://127.0.0.1:8000.
Both scripts anchor themselves to the repository root, so they work whether you double-click them or run them from another directory.
The server starts on a JSON fallback. To use MySQL instead, edit
backend/.env:
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASSWORD=your_mysql_password
DB_NAME=house_price_dbRestart the server — it initialises the schema and migrates existing records on boot.
| Method | Endpoint | Purpose |
|---|---|---|
GET |
/api/health |
Service status and which datastore is active |
GET |
/api/cities |
Supported cities and their localities |
POST |
/api/predict |
Estimate a price from property attributes |
GET |
/api/history |
Past predictions |
POST |
/api/feedback |
Submit feedback on an estimate |
GET |
/api/analytics |
City rate averages and feature importance |
House_Price_Prediction/
├── backend/ # Express REST API
│ ├── config/ # Per-city pricing configuration
│ ├── db/ # Connection, schema, JSON fallback driver
│ ├── routes/ # API route handlers
│ └── server.js # App entrypoint; also serves ../frontend
├── frontend/ # Static UI, served by Express
│ ├── index.html
│ ├── styles.css
│ └── app.js
├── ml/ # Model training and inference
│ ├── dataset_gen.py # Synthetic dataset generation
│ ├── train.py # Training pipeline
│ ├── predict.py # stdin/stdout inference bridge
│ ├── model.joblib # Trained pipeline
│ ├── model_metadata.json # Scores and feature importance
│ └── requirements.txt # Pinned Python dependencies
├── scripts/ # Windows setup and launch scripts
└── render.yaml # Deployment specification
Deployed as a single Render web service: Express serves both the API and the
frontend, and Python runs alongside it for inference. Configuration lives in
render.yaml.
Python dependencies are installed from ml/requirements.txt rather than resolved
fresh. model.joblib is a pickle, and a pickle only reloads reliably on the
library versions that wrote it — an unpinned install would eventually pull a
newer numpy whose internals have moved, breaking inference on the host while it
continued to work locally.
The first request after an idle period is slow. The free tier spins the service down after roughly 15 minutes without traffic, and Render shows a "waking up" page while the container boots again. Loading the app once beforehand makes everything after it immediate.
MIT — see LICENSE.