Full-stack Next.js application that predicts housing prices from square footage and number of bedrooms.
The model is trained in Python (scikit-learn), exported to ONNX, and served server‑side in Next.js API routes using onnxruntime-node. Predictions and history are stored in SQLite via Prisma ORM.
This app demonstrates a compact, maintainable production pattern:
- One Next.js app for UI and API
- ONNX runtime for fast model inference
- Prisma + SQLite for persistence (easy to swap with Postgres/MySQL)
- Frontend: Next.js 14 (App Router) + React + TypeScript
- Backend: Next.js API routes (Node.js)
- ML: scikit-learn (training) → ONNX (serving via onnxruntime-node)
- DB: Prisma ORM + SQLite (file-based, zero-config)
Prerequisites
- Node.js 18+
- Python 3.11+
- Install Node dependencies
cd Geviti_App
npm install- Train and export ONNX model
# from repo root
py -m pip install --upgrade pip
py -m pip install scikit-learn skl2onnx onnx onnxruntime
py train_model.pyOutputs:
Geviti_App/public/model.onnx– ONNX model served by APIGeviti_App/public/model.json– lightweight metadata
- Configure Prisma (first time only)
# from Geviti_App
echo DATABASE_URL="file:./data.sqlite" > .env
npm install @prisma/client prisma
npx prisma generate
npx prisma migrate dev -n init- Run the app
npm run dev
# http://localhost:3000Training data: 8 provided samples (sqft, bedrooms → price).
Model: degree‑2 polynomial features + linear regression with non‑negative coefficients.
- Enforces monotonicity (price ↑ as sqft/bedrooms ↑)
- Very fast and stable for small datasets
Export to ONNX (snippet):
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType
initial_type = [('float_input', FloatTensorType([None, 2]))]
onnx_model = convert_sklearn(model, initial_types=initial_type)UI highlights:
- Form with input validation and helper text
- Results card with formatted price
- Predictions table with pagination controls and total count
Server highlights:
- ONNX session loaded once per process (
Geviti_App/lib/onnx-model.js) - API input validation and robust error handling
- Prisma client singleton to avoid connection leaks in dev
Request
{ "squareFootage": 1800, "bedrooms": 3 }Response
{
"predictedPrice": 294787,
"prediction": {
"id": 1,
"squareFootage": 1800,
"bedrooms": 3,
"predictedPrice": 294787,
"createdAt": "2025-10-30T00:00:00.000Z"
}
}Pagination parameters:
- New:
page(1‑based),pageSize(default 10) - Legacy:
limit,offset
Example
/api/predictions?page=2&pageSize=20
Response
{
"items": [ ... ],
"total": 123,
"limit": 20,
"offset": 20,
"page": 2,
"pageSize": 20,
"totalPages": 7,
"hasMore": true
}ORM: Prisma (SQLite)
- Schema:
frontend/prisma/schema.prisma
model Prediction {
id Int @id @default(autoincrement())
squareFootage Int
bedrooms Int
predictedPrice Int
createdAt DateTime @default(now())
}- Client:
frontend/lib/prisma.js(singleton) - Access layer:
frontend/lib/database.js(add/get/count) - History endpoint returns
total,page,pageSize,totalPages,hasMorefor easy pagination UI
Common commands (from frontend):
npm run dev– start Next.js dev servernpm run build– production buildnpm run start– start production servernpx prisma studio– browse/edit DB data in a web UInpm run prisma:generate– regenerate Prisma clientnpm run prisma:migrate– create/apply SQL migrations
Geviti_App/
├── app/
│ ├── api/
│ │ ├── predict/route.js # ONNX inference
│ │ └── predictions/route.js # History with pagination
│ ├── layout.tsx # App shell
│ └── page.tsx # UI (form, result, table)
├── lib/
│ ├── onnx-model.js # onnxruntime-node loader (server-only)
│ ├── prisma.js # Prisma client singleton
│ └── database.js # Data access (Prisma)
├── prisma/
│ └── schema.prisma # DB schema
└── public/
├── model.onnx # exported model
└── model.json # metadata
train_model.py # training + ONNX export