A comprehensive home service discovery platform connecting users with local service providers.
Features • Architecture • Data Model • API Reference • Getting Started
Homely is a full-stack web application that enables users to discover, book, and review local service providers. The platform supports multiple user roles (customers, providers, administrators) and includes AI-powered service recommendations, real-time location tracking, and secure payment processing.
- Service Discovery — Browse and search services with AI-powered semantic search
- Smart Booking — Schedule appointments with real-time availability
- Secure Payments — Integrated Stripe payment processing with webhook verification
- Location Tracking — Real-time provider/customer location via Redis
- AI Recommendations — Local, on-device semantic recommendations (Transformers.js)
- Review System — Rate and review completed services
- Admin Dashboard — Comprehensive management interface
Recommendations are powered by on-device embeddings, not an external API.
On first use, the app loads the Xenova/all-MiniLM-L6-v2
feature-extraction model via @huggingface/transformers.
Service descriptions are embedded and ranked by cosine similarity against the
user's query. If the model cannot be downloaded (offline/restricted environments),
the system degrades gracefully to a deterministic lexical (token-overlap) scorer so
search requests never fail.
- Stripe webhooks —
/api/webhooks/stripeverifies signatures and is the authoritative source of payment state, with idempotent event processing. - Rate limiting — Redis-backed, per-IP limits (stricter on auth routes), with an in-memory fallback when Redis is unavailable.
- Security headers & CORS — applied globally via
src/proxy.ts(the Next.js 16 replacement formiddleware.ts). - Structured logging — JSON logs with per-request correlation ids.
- Validated configuration — env vars are schema-validated
(
src/lib/config/env.ts). - Health probes —
/api/healthand/api/health/readyfor Kubernetes. - Error boundaries —
error.tsx,not-found.tsx,loading.tsx. - Graceful shutdown —
src/instrumentation.node.tscloses DB/Redis on SIGTERM.
┌─────────────────────────────────────────────────────────────────┐
│ Frontend Layer │
│ Next.js 16 (App Router) + React 19 + TypeScript + Tailwind │
│ ├── Authentication (JWT) │
│ ├── Role-based Access Control │
│ └── Real-time Maps (Leaflet) │
└────────────────────────────┬────────────────────────────────────┘
│
┌────────────────────────────▼────────────────────────────────────┐
│ API Layer │
│ Next.js API Routes + Proxy (src/proxy.ts) + Route Guards │
│ ├── Authentication (/api/auth) │
│ ├── Services & Reviews (/api/services, /api/reviews) │
│ ├── Bookings (/api/bookings) │
│ ├── Payments & Webhooks (/api/payments, /api/webhooks/stripe) │
│ ├── Recommendations & Location (/api/recommendations, /api/location) │
│ ├── Administration (/api/admin) │
│ └── Health (/api/health) │
└────────────────────────────┬────────────────────────────────────┘
│
┌────────────────────────────▼────────────────────────────────────┐
│ Data Layer │
│ PostgreSQL (Prisma ORM) │
│ ├── Users, Services, Bookings, Reviews │
│ └── Redis (Location Caching) │
└─────────────────────────────────────────────────────────────────┘
src/
├── app/ # Next.js App Router pages
│ ├── api/ # Backend API routes
│ │ ├── admin/ # Admin management endpoints
│ │ ├── auth/ # Authentication endpoints
│ │ ├── bookings/ # Booking management
│ │ ├── dev/test-env/ # Dev-only diagnostics (disabled in prod)
│ │ ├── health/ # Liveness + readiness probes
│ │ ├── location/ # Real-time location tracking
│ │ ├── payments/ # Payment processing
│ │ ├── providers/ # Provider endpoints
│ │ ├── recommendations/ # AI recommendations
│ │ ├── reviews/ # Review management
│ │ ├── services/ # Service management
│ │ └── webhooks/stripe/ # Stripe webhook receiver
│ ├── admin/ # Admin dashboard page
│ ├── auth/ # Login/Register pages
│ ├── bookings/ # Bookings list + payment page
│ ├── payments/ # Payments overview page
│ ├── provider/ # Provider services dashboard
│ ├── profile/ # User profile page
│ ├── recommendations/ # AI recommendations page
│ ├── services/ # Service browsing
│ ├── error.tsx # Error boundary
│ ├── loading.tsx # Route loading UI
│ ├── not-found.tsx # 404 page
│ ├── layout.tsx # Root layout
│ └── page.tsx # Home page
├── components/ # React components
│ ├── admin/ # Admin-specific components
│ ├── features/ # Feature components (Map, Payment, Review…)
│ └── layout/ # Layout + route guards
├── context/ # React context providers
│ └── AuthContext.tsx # Authentication context
├── lib/ # Utility libraries (grouped by concern)
│ ├── api/errors.ts # ApiError + withApiHandler
│ ├── auth/index.ts # JWT + password helpers
│ ├── cache/ # redis.ts, rateLimit.ts
│ ├── config/ # env.ts (validation), logger.ts
│ ├── db/prisma.ts # Prisma client
│ └── integrations/ # stripe.ts, ai.ts
├── middleware/auth.ts # Route-level withAuth/withRole guards
├── proxy.ts # Global headers/CORS/rate limiting (Next 16)
├── instrumentation.ts # Edge-safe bootstrap entry point
└── instrumentation.node.ts # Node startup validation + graceful shutdown
tests/
├── api/ # Route-handler tests
├── lib/ # Unit tests for lib modules
└── utils/test-utils.ts # Shared fixtures & mock helpers
| Role | Description | Capabilities |
|---|---|---|
customer |
Customer | Browse services, create bookings, leave reviews |
provider |
Service Provider | Manage services, handle bookings, track locations |
admin |
Administrator | Full system access, user/service management |
The Prisma schema (prisma/schema.prisma) defines the following entities:
| Model | Table | Purpose |
|---|---|---|
User |
users |
Accounts with role-based access (customer / provider / admin) |
Service |
services |
Provided services (category, price, duration, tags, availability) |
Booking |
bookings |
Service appointments with payment + status tracking |
Review |
reviews |
Post-completion ratings (one per booking) |
WebhookEvent |
webhook_events |
Idempotency ledger for inbound payment webhooks (Stripe) |
Supporting enums: UserRole, BookingStatus (pending, confirmed,
in_progress, completed, cancelled), and PaymentStatus (pending,
paid, refunded).
Idempotent webhooks. The
WebhookEventmodel records every processed Stripe event bytype. Re-delivered webhooks are detected and skipped, so payment state is applied exactly once.
| Endpoint | Method | Description | Auth Required |
|---|---|---|---|
/api/auth/login |
POST | User login | No |
/api/auth/register |
POST | User registration | No |
/api/auth/profile |
GET/PUT | Profile management | Yes |
| Endpoint | Method | Description | Auth Required |
|---|---|---|---|
/api/services |
GET | List services (supports AI search) | No |
/api/services |
POST | Create service | Provider/Admin |
/api/services/[id] |
GET | Service details | No |
/api/services/[id] |
PUT | Update service | Owner/Admin |
/api/services/[id] |
DELETE | Delete service | Owner/Admin |
/api/services/[id]/reviews |
GET | Paginated service reviews | No |
| Endpoint | Method | Description | Auth Required |
|---|---|---|---|
/api/bookings |
GET | List user bookings | Yes |
/api/bookings |
POST | Create booking | Yes |
/api/bookings/[id] |
GET | Booking details | Yes |
/api/bookings/[id] |
PUT | Update booking | Yes |
/api/bookings/[id] |
DELETE | Cancel booking | Yes |
/api/bookings/[id]/refund |
POST | Refund to customer | Provider (owner) |
| Endpoint | Method | Description | Auth Required |
|---|---|---|---|
/api/payments/create-payment-intent |
POST | Initialize payment | Yes |
/api/payments/confirm |
POST | Confirm payment | Yes |
/api/webhooks/stripe |
POST | Stripe webhook receiver | Signature |
| Endpoint | Method | Description |
|---|---|---|
/api/health |
GET | Liveness / dependency status |
/api/health/ready |
GET | Readiness probe (DB reachable) |
| Endpoint | Method | Description | Auth Required |
|---|---|---|---|
/api/admin/users |
GET/PATCH/DELETE | User management | Admin |
/api/admin/services |
GET/PATCH/DELETE | Service management | Admin |
/api/admin/bookings |
GET/PATCH/DELETE | Booking management | Admin |
/api/admin/payments |
GET/POST/PUT/DELETE | Payment management | Admin |
- Node.js 18+
- PostgreSQL 14+
- Redis 7+
- Stripe account
- OpenAI API key — not required; recommendations use a local on-device model
Configuration is read from a single .env file at the project root. It is
git-ignored, so create it locally (the defaults below are set up for local
PostgreSQL + Redis via docker-compose.yml):
# Database (local PostgreSQL — matches docker-compose.yml)
DATABASE_URL="postgresql://postgres:mysecretpassword@localhost:5432/home_service_discovery?schema=public"
DIRECT_URL="postgresql://postgres:mysecretpassword@localhost:5432/home_service_discovery?schema=public"
# Redis (local)
REDIS_URL="redis://localhost:6379"
# Authentication (>= 16 chars)
JWT_SECRET="your-super-secret-jwt-key"
# Stripe
STRIPE_SECRET_KEY="sk_test_..."
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="pk_test_..."
STRIPE_WEBHOOK_SECRET="whsec_..."
# Security / CORS (optional, sensible defaults applied)
ALLOWED_ORIGINS="http://localhost:3000"
RATE_LIMIT_MAX=100
RATE_LIMIT_WINDOW_MS=900000 # 15 minutes
AUTH_RATE_LIMIT_MAX=10
# AI model cache location (optional — defaults to ./.cache)
# The embedding model is downloaded on first use and cached here.
TRANSFORMERS_CACHE="./.cache"| Variable | Required | Description |
|---|---|---|
DATABASE_URL / DIRECT_URL |
Production only | PostgreSQL connection strings |
REDIS_URL |
No (graceful fallback) | Redis location-cache / rate-limit store |
JWT_SECRET |
Production only | JWT signing secret (min. 16 chars) |
STRIPE_SECRET_KEY |
Production only | Stripe server key (sk_...) |
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY |
Production only | Stripe client key (pk_...) |
STRIPE_WEBHOOK_SECRET |
Production only | Webhook signature secret (whsec_...) |
ALLOWED_ORIGINS |
No (default *) |
Comma-separated CORS origins |
RATE_LIMIT_MAX |
No (default 100) |
Max requests per window |
RATE_LIMIT_WINDOW_MS |
No (default 15 min) | Rate-limit window in ms |
AUTH_RATE_LIMIT_MAX |
No (default 10) |
Stricter limit for auth routes |
TRANSFORMERS_CACHE |
No (default .cache) |
On-device model download cache directory |
In production, all variables marked "Production only" are validated on startup (
validateEnvinsrc/lib/config/env.ts) and the process fails fast if any is missing.
# Clone the repository
git clone <repository-url>
cd home-service-discovery
# Install dependencies (also runs `prisma generate`)
npm install
# Start local PostgreSQL + Redis
docker compose up -d db redis
# Sync the database schema
npx prisma db push
# Start development server
npm run devThe application will be available at http://localhost:3000.
Database workflow. The schema in
prisma/schema.prismais the single source of truth. Usenpm run db:push(prisma db push) to apply changes directly from the schema. If a change is destructive, Prisma will warn before touching data. A baseline migration also lives inprisma/migrations/, generated viaprisma migrate dev, but day-to-day schema changes are applied withdb pushin this workflow.
# Run the full test suite (Jest)
npm test
# Watch mode (re-run on change)
npm run test:watch
# Coverage report
npm run test:coverage
# CI mode (single run, coverage, no watch)
npm run test:ciTests live in tests/ and are grouped by concern:
tests/api/— route-handler tests (auth, admin, services, bookings, recommendations, payments …) using Supertest against mocked Prisma.tests/lib/— unit tests for library modules.tests/utils/test-utils.ts— shared fixtures and mock helpers.
npm run lintESLint is run via the CLI in CI — Next.js 16 removed the built-in
next lintcommand and theeslintconfig key fromnext.config.ts.
A Dockerfile and docker-compose.yml are provided. Compose defines a web
service that builds the app and depends on db (PostgreSQL) and redis:
# Everything (app + db + redis)
docker compose up -d
# Or just the backing services while you run the app locally
docker compose up -d db redisInside the container, database/Redis hosts are the Compose service names (
db,redis), notlocalhost— seedocker-compose.yml.
| Technology | Purpose |
|---|---|
| Next.js 16 | React framework with App Router |
| React 19 | UI library |
| TypeScript 5 | Type safety |
| Tailwind CSS 4 | Utility-first styling |
| React Context | State management |
| Leaflet / react-leaflet | Interactive maps |
| Stripe Elements | Payment UI components |
| Technology | Purpose |
|---|---|
| Next.js API Routes | Backend API endpoints |
| Prisma ORM | Database ORM |
| PostgreSQL | Primary database |
| Redis (ioredis) | Location caching & rate limits |
| JWT (jsonwebtoken) | Token-based authentication |
| bcryptjs | Password hashing |
| Stripe | Payment processing |
| Zod | Runtime config validation |
| @huggingface/transformers | On-device semantic search |
| Technology | Purpose |
|---|---|
| Docker | Containerization |
| Kubernetes | Orchestration (health probes) |
| Jest | Testing framework |
| ESLint | Linting |
| Supertest | HTTP route-handler assertions |
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under JUST License.