Backend REST API for a home-services marketplace connecting customers, technicians, and admins. Customers browse services, book technicians, and pay via Stripe; technicians list services and manage bookings; admins manage users and categories.
Built with Express + TypeScript, PostgreSQL via Prisma, JWT auth, and Stripe payments.
| Concern | Choice |
|---|---|
| Runtime / framework | Node.js, Express (TypeScript) |
| Database | PostgreSQL |
| ORM | Prisma |
| Auth | JWT (access + refresh), bcrypt |
| Payments | Stripe Checkout Sessions + webhooks |
| Validation | Zod (per-route middleware) |
| Error handling | AppError + httpStatus constants, catchAsync wrapper |
src/
app.ts # Express app: middleware + route mounting
server.ts # Bootstraps DB connection and server
config/ # Env config
lib/ # prisma client, stripe client
middlewares/ # auth, validateRequest, globalErrorHandler, notFound
utils/ # catchAsync, jwt, successResponse
modules/
auth/ # register, login, current-user
user/ # customer profile + password
technician/ # technician profile, availability, bookings
category/ # service categories (admin)
service/ # service listings
booking/ # booking lifecycle
payment/ # Stripe checkout + webhook
review/ # customer reviews after a completed booking
admin/ # user & category management
prisma/
schema.prisma # User, TechnicianProfile, Category, Services, Booking, Payment, Review
migrations/ # SQL migrations
Each module follows a route → controller → service → interface split.
Node.js 18+, a PostgreSQL database, and a Stripe account (for payments).
npm installCopy .env.example to .env and fill in:
DATABASE_URL=postgresql://user:pass@host:5432/dbname
PORT=5000
APP_URL=http://localhost:5000
SOLT_OR_ROUNDS=10
JWT_ACCESS_SECRET=your_access_secret
JWT_REFRESH_SECRET=your_refresh_secret
JWT_ACCESS_EXPIRES_IN=1h
JWT_REFRESH_EXPIRES_IN=7d
STRIPE_SECRET_KEY=sk_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
npx prisma migrate deploy # apply migrations
npx prisma generate # generate the clientnpm run dev # ts-node-dev / nodemon (development)
npm run build # tsc -> dist/
npm start # node dist/server.js (production)For local Stripe webhooks:
npm run stripe:webhook # stripe listen --forward-to http://localhost:5000/api/payments/webhookNote:
express.raw()must be registered for the webhook route before the globalexpress.json()middleware, or Stripe's signature verification will fail.
Every success JSON response follows this shape (via successResponse):
{
"success": true,
"statusCode": 200,
"message": "…",
"data": { },
"meta": { "page": 1, "limit": 10, "total": 42 }
}meta is only present on paginated endpoints.
Every not found JSON response follows this shape (via notFoundResponse):
{
"success": false,
"statusCode": 400,
"message": "Not Found",
"data": null,
}Every JSON unauthorized response follows this shape (via unauthorizedResponse):
{
"success": false,
"statusCode": 401,
"message": "Unauthorized access",
}Every JSON error response follows this shape (via errorResponse):
{
"success": false,
"statusCode": 500,
"message": "…",
"data": {}
}Send the access token as either an httpOnly cookie (accessToken, set automatically on login) or an Authorization: Bearer <token> header. Protected routes also enforce roles — a request with a valid token but the wrong role gets rejected. Blocked users (status: BLOCKED) are denied.
Roles: CUSTOMER (default), TECHNICIAN, ADMIN.
Errors return the standard envelope with success: false and an appropriate status code, thrown via AppError and caught by the global error handler. Validation failures (Zod) return 400 with a per-field errors array. Prisma errors are mapped (e.g. P2002 → 409 Conflict, P2025 → 404 Not Found).
Booking status: REQUESTED → ACCEPTED → PAID → IN_PROGRESS → COMPLETED (or DECLINED / CANCELLED).
- Customer submits a booking →
REQUESTED. - Technician accepts or declines →
ACCEPTED/DECLINED. - Customer pays via Stripe → webhook marks payment
PAID, booking moves toPAID. - Technician starts the job →
IN_PROGRESS, then marks itCOMPLETED. - Once
COMPLETED, the customer may leave one review.
Customers can cancel a booking at any point before it reaches
IN_PROGRESS.
Payment status: PENDING → PAID / FAILED / REFUNDED.
Webhook handling is idempotent — replayed Stripe events for an already-processed paymentIntentId/transactionId are safely ignored rather than double-applied.
Base URL: http://localhost:<PORT>
All routes are prefixed as shown. Auth column = required role(s); "Public" = no token needed.
| Method | Path | Auth | Body | Description |
|---|---|---|---|---|
| POST | /register |
Public | { firstName, lastName?, email, password, phone, role, bio?, skills?, experience?, hourlyRate?, availability? } |
Register a CUSTOMER or TECHNICIAN. Technicians must include role: "TECHNICIAN", hourlyRate, and availability. |
| POST | /login |
Public | { email, password } |
Log in; sets accessToken and refreshToken cookies and returns tokens. |
| POST | /access-token |
Public (uses refreshToken cookie) |
— | Issue a new access token using the refresh token cookie. |
| GET | /me |
CUSTOMER, TECHNICIAN, ADMIN | — | Get current authenticated user profile. |
| Method | Path | Auth | Query | Description |
|---|---|---|---|---|
| GET | /api/services |
Public | search, type, location, minPrice, maxPrice, page, limit |
List services with optional filters. |
| GET | /api/technicians |
Public | skills, minExperience, maxExperience, page, limit |
List technicians with optional filters. |
| GET | /api/technicians/:technicianId |
Public | — | Get a technician's public profile and reviews. |
| GET | /api/categories |
Public | page, limit |
List service categories. |
| Method | Path | Auth | Body | Description |
|---|---|---|---|---|
| GET | /profile |
CUSTOMER | — | Get own profile. |
| PUT | /profile |
CUSTOMER | { firstName?, lastName?, phone?, address?, city?, profileImage?, bio?, skills?, experience?, hourlyRate?, availability? } |
Update own profile. Technicians may also update technician fields. |
| PATCH | /change-password |
CUSTOMER | { oldPassword, newPassword } |
Change password. |
| DELETE | /delete |
CUSTOMER | — | Delete own account. |
| Method | Path | Auth | Body | Description |
|---|---|---|---|---|
| POST | / |
CUSTOMER | { serviceId, scheduledDate, address, note? } |
Create a new booking (status REQUESTED). |
| GET | / |
CUSTOMER | — | List the customer's own bookings. |
| GET | /status |
CUSTOMER | — | Get the customer's bookings (same handler as GET /). |
| GET | /:bookingId |
CUSTOMER | — | Get details for a single booking. |
| PATCH | /cancel/:bookingId |
CUSTOMER | { cancelReason? } |
Cancel a booking. Current implementation allows cancellation only when booking status is IN_PROGRESS. |
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /create |
CUSTOMER | Create a Stripe Checkout session for an accepted booking; returns the payment URL. |
| POST | /webhook |
Stripe webhook | Stripe webhook endpoint. Expects raw body plus stripe-signature header. On checkout.session.completed, payment is fulfilled and booking status updates to IN_PROGRESS. |
| GET | /history |
CUSTOMER | Get the customer's payment history. |
| GET | /:paymentId |
CUSTOMER | Get a single payment's details. |
| Method | Path | Auth | Body | Description |
|---|---|---|---|---|
| POST | / |
CUSTOMER | { bookingId, rating, comment? } |
Create a review for a completed booking. |
| Method | Path | Auth | Body | Description |
|---|---|---|---|---|
| GET | /profile |
TECHNICIAN | — | Get own technician profile. |
| PUT | /profile |
TECHNICIAN | { bio?, skills?, experience?, hourlyRate?, availability? } |
Update own technician profile. |
| PATCH | /change-password |
TECHNICIAN | { oldPassword, newPassword } |
Change password. |
| PATCH | /availability |
TECHNICIAN | { availability: string[] } |
Update availability slots. |
| GET | /bookings |
TECHNICIAN | — | List bookings assigned to the technician. |
| GET | /bookings/incomming |
TECHNICIAN | — | List incoming REQUESTED bookings. |
| GET | /bookings/:bookingId |
TECHNICIAN | — | Get a booking's details. |
| PATCH | /bookings/:bookingId |
TECHNICIAN | { status: "ACCEPTED" | "DECLINED" | "COMPLETED" } |
Accept, decline, or complete a booking. |
| GET | /:technicianId |
Public | — | Get a technician's public profile. |
| Method | Path | Auth | Body | Description |
|---|---|---|---|---|
| POST | / |
TECHNICIAN | { categoryId, title, description, price, duration, location[], availableAt[] } |
Create a service listing. type is inferred from the category. |
| GET | / |
Public | search, type, location, minPrice, maxPrice, page, limit |
List services. |
| PUT | /:serviceId |
TECHNICIAN | Any subset of the create body | Update own service listing (ownership enforced). |
| Method | Path | Auth | Body | Description |
|---|---|---|---|---|
| GET | /profile |
ADMIN | — | Get admin profile. |
| PUT | /profile |
ADMIN | { firstName?, lastName?, phone? } |
Update admin profile. |
| PATCH | /change-password |
ADMIN | { oldPassword, newPassword } |
Change password. |
| GET | /users |
ADMIN | — | List customers and technicians. |
| PATCH | /users/update-status/:userId |
ADMIN | { status: "ACTIVE" | "BLOCKED" } |
Update user status. |
| GET | /bookings |
ADMIN | — | List all bookings. |
| GET | /bookings/:bookingId |
ADMIN | — | Get a booking's details. |
| GET | /categories |
Public | — | List all categories. |
| POST | /categories |
ADMIN | { name, icon?, description? } |
Create a category. |
| PATCH | /categories/:categoryId |
ADMIN | { name?, icon?, description? } |
Update a category. |
| GET | /payment-history |
ADMIN | — | View payment history platform-wide. |
Prisma models on PostgreSQL:
| Model | Purpose |
|---|---|
User |
Account details, role (CUSTOMER/TECHNICIAN/ADMIN), and status |
TechnicianProfile |
Bio, skills, experience, hourly rate, availability, completed jobs |
Services |
Individual services offered by a technician, linked to a category |
Category |
Service categories (plumbing, electrical, cleaning, etc.) |
Booking |
A job request between a customer and technician, with lifecycle status |
Payment |
Stripe transaction records linked one-to-one with a booking |
Review |
Customer ratings and feedback tied to a service, technician, and customer |
MIT
Md. Shahdat Hossain