Skip to content
Back to case files
IndependentIn progress

Service Marketplace — A Two-Sided Matching Platform

One unified model carrying both local and skill services, from listing to order, fulfilment and review — in a multi-package monorepo with a single contract source

Role
Architecture and full-stack implementation (solo)
Scale
3 clients · 12 core tables · one contract source
Period
Oct 2026 — Present
TypeScriptNestJSPrismaPostgreSQLRedisBullMQVue 3Element PlusVantpnpmTurborepoZodDocker Compose

What I did

  • One model, two service shapes: local and skill services share service items and orders, with differences confined to category attributes and fulfilment fields — no dual-model maintenance cost
  • Role, not account: a provider is just a role on a user, so one person can be both customer and provider, and auth, addresses and files are reused rather than duplicated
  • Monorepo with a single contract source: pnpm + Turborepo organise api / admin / web, while packages/shared collapses types and interface definitions so types run end to end
  • Documents first: AGENTS.md requires design, API and schema documents before code, and an API list out of sync with Apifox counts as unfinished
  • Explicit non-goals: payment splitting, mini programs, real-time IM and smart dispatch are each written down with a trigger condition, which keeps scope from creeping

Starting point: two service shapes, not two models

The platform carries both local services (cleaning, repairs, moving) and skill services (design, development, copywriting). Their fulfilment looks nothing alike: the first depends on address, service area and booking slots; the second on deadlines, deliverables, acceptance and revision counts.

The easy route is a table set per shape, paid for later: every new feature written twice, every requirement change reconciled in two places. So modelling came first — one service-item and order model, with the differences collected into category attributes and fulfilment fields, leaving a single main flow.

Role model: a provider is a role, not an account

PROVIDER is not a separate account system; it is a role on User.roles. That buys three things:

  • One person can be both customer and provider without registering twice;
  • Auth, addresses, files and reviews are reused, so there is never a second permission system to keep aligned;
  • Operations keep one user view, and certification is just an approval state attached to a role.

Engineering shape: built for an AI to keep iterating

The repository was designed from the start for an AI coding agent to work in over the long term:

Design What it solves
pnpm + Turborepo monorepo api / admin / web / shared in one repository; a contract change is visible to all three at once
Single contract source (packages/shared) Interface types written once, flowing frontend to backend, so a renamed field cannot silently break a client
Prisma + migrations The schema is the only source of truth; database structure and code move together
AGENTS.md as a hard rule Design, API and schema documents precede code, and code changes write back to them
A written Non-Goals list Payments, mini programs, IM and smart dispatch are explicitly out, each with a condition for revisiting

The last row matters most: stating what you will not build prevents scope creep far better than a long to-do list.

Current state

Core modelling is done — users, providers, categories, service items, orders, order events, disputes, reviews and files — with the API and admin skeleton running. Payment splitting, the mini-program client and smart dispatch sit at P3 per the non-goals, to be decided once the core loop has data.