Lead capture MVP for a plumbing business, with a React frontend and an Express/Postgres API.
web/: React + Vite frontendapi/: Express API + PostgreSQL storage
- SQL-file migrations applied automatically on API startup
- Expanded
leadsschema with operational fields such assource,page,next_action_at,assigned_to,quote_amount,created_at, andupdated_at lead_notestable for lead history- Indexed filtering on
created_at,status, andservice_type - Admin-protected lead listing and status updates
- Input validation and lightweight rate limiting on lead submission
Create a database and provide its connection string through DATABASE_URL.
Example:
createdb lead_capture_plumbingCopy api/.env.example to api/.env and update values as needed.
Required for local API use:
DATABASE_URLADMIN_TOKEN
Optional:
PORT- SMTP settings for new-lead notification emails
cd api
npm install
npm run devOn startup, the API will apply any pending SQL migrations automatically.
cd web
npm install
npm run devIf the frontend is served separately from the API, set:
VITE_API_URL=http://localhost:4000/apiOptional frontend env:
VITE_DEMO_MODE=trueWhen VITE_DEMO_MODE is not set to true, the form and admin page use the live API.
Creates a lead.
Example payload:
{
"name": "Jane Smith",
"phone": "0400 111 222",
"email": "[email protected]",
"service_type": "Blocked drain",
"urgency": "medium",
"suburb": "Robina",
"contact_time": "morning",
"message": "Kitchen sink blocked",
"source": "webform",
"page": "/contact"
}Allowed values:
urgency:low,medium,emergencycontact_time:any,morning,afternoon,eveningsource:webform,call
Requires:
Authorization: Bearer <ADMIN_TOKEN>Supported query params:
service_typestatusfrom(YYYY-MM-DD)to(YYYY-MM-DD)
Requires:
Authorization: Bearer <ADMIN_TOKEN>
Content-Type: application/jsonExample payload:
{
"status": "contacted",
"next_action_at": "2026-04-04T09:00:00.000Z"
}Allowed status values:
newcontactedbooked
API tests are mock-based and do not require a running database:
cd api
npm test