The API that powers Matchids: authentication, users, books, categories,
artwork, orders, payments, donations, donation campaigns, user library,
notifications and admin — one Express + TypeScript service, backed by
the schema from matchids-database.
matchids-web ──HTTP──> matchids-backend ──Prisma──> Postgres
│
├── @matchids/payments (card payments)
└── @matchids/celoht (CELO / USDm)
The frontend (matchids-web) never talks to the database or a payment
provider directly — everything sensitive lives here.
src/
app.ts / server.ts Express app + entrypoint
routes/ One file per resource, thin — just wiring
controllers/ Actual request handling + business logic
middleware/ auth (JWT), admin gating, rate limiting, errors
lib/
prisma.ts Prisma client (schema copied from matchids-database)
auth.ts Password hashing + session tokens
paymentService.ts Registers @matchids/celoht with @matchids/payments
validate.ts Zod request validation helper
prisma/schema.prisma Kept in sync with matchids-database (see below)
tests/ Vitest + supertest
matchids-database is the canonical schema. This repo's prisma/schema.prisma
is a working copy. Pick one and document it for your team:
- Git submodule:
git submodule add <matchids-database-url> prisma/database - Published package: publish
matchids-database's schema as an npm package and generate the Prisma client from it - CI sync job: a scheduled action that copies the schema and opens a PR
here whenever
matchids-databasechanges
Whichever approach, never let this copy drift silently — a schema mismatch between backend and database is a production bug waiting to happen.
@matchids/payments and @matchids/celoht are separate repos. For local
development, link them (npm workspaces, npm link, or a file:../matchids-payments
dependency) rather than publishing throwaway versions while iterating.
- Every route that touches user data requires
requireAuth; admin routes additionally requirerequireAdmin. - Auth endpoints are rate-limited separately and more strictly than the
rest of the API (
middleware/rateLimit.ts). - No payment is ever confirmed by a client request. Only
POST /api/payments/webhook, afterpaymentService.confirmPayment()succeeds, moves an Order toPAIDor a Donation toCONFIRMED— seecontrollers/payments.controller.ts. Verifying the webhook's signature per-provider before trusting its body is flagged as an integration point there, not skipped silently. errorHandlernever leaks a stack trace to the client in production.
npm install
cp .env.example .env
npm run db:generate
npm run db:push # requires a running Postgres instance
npm run dev # http://localhost:4000npm test| Route | Auth | Purpose |
|---|---|---|
POST /api/auth/register, /login |
— | Account creation and sign-in |
GET /api/books, /api/books/:id |
— | Browse and search the library |
GET /api/categories |
— | List book categories |
GET /api/artwork, POST /api/artwork/:id/like |
partial | Kids Art |
POST /api/orders, GET /api/orders |
required | Purchase a premium book |
POST /api/payments/webhook |
provider | Confirm a payment (server-to-server) |
POST /api/donations, GET /api/donations/campaigns |
optional | Give Back |
GET /api/library |
required | A user's purchased/free books |
GET /api/notifications |
required | Placeholder — no delivery mechanism yet |
GET /api/admin/overview |
admin | Real counts from the database, nothing hand-written |