Afiel Marketplace Backend

v1 · 62 endpoints · NestJS + Postgres

Everything the frontend needs: how to run the API locally, the business rules that shape what it returns, and what every endpoint takes and gives back.

Interactive docs run at /docs. Once the API is up, open http://localhost:3000/docs for Swagger UI with every endpoint, every request shape and a working Try it out. The raw OpenAPI JSON is at /docs-json if you want to generate a client.

What you need

ToolVersionWhy
Node.js20+22+ preferred; the AWS SDK requires it from Jan 2027
npm10+Ships with Node
Docker DesktopanyRuns Postgres — must be started manually

No global Postgres install needed. The database runs in Docker on port 5433, deliberately not 5432, so it never collides with a Postgres you already have.

First run

# 1. install
npm install

# 2. start Postgres (Docker Desktop must already be running)
npm run db:up

# 3. copy the env template and fill it in
cp .env.example .env

# 4. create the schema
npx prisma migrate deploy

# 5. reference data: plans, categories, system tags
npm run seed:plans
npm run seed:categories
npm run seed:admin        # needs ADMIN_EMAIL + ADMIN_PASSWORD

# 6. go
npm run start:dev         # http://localhost:3000/v1

The app refuses to boot if required env vars are missing, so a misconfigured setup fails immediately naming the variable, rather than as a confusing 500 on your first request.

Environment

VariableRequiredNotes
DATABASE_URLyesPort 5433 for the Docker instance
JWT_ACCESS_SECRETyesMust differ from the refresh secret — boot fails if they match
JWT_REFRESH_SECRETyes
JWT_ACCESS_EXPIRES_INnoDefault 15m
JWT_REFRESH_EXPIRES_INnoDefault 30d
BCRYPT_ROUNDSnoDefault 12; must be 10–15
PORTnoDefault 3000
STORAGE_DRIVERnolocal (default) or s3
S3_*if s3Endpoint, keys, two bucket names, public base URL

File storage

The API never receives file bytes. Uploading is always three steps, identical in every environment:

  1. POST /v1/uploads with a purpose, content type and byte size → you get an objectKey and a short-lived uploadUrl.
  2. PUT the raw bytes to that uploadUrl, sending the Content-Type header it gave you.
  3. Submit the objectKey on whatever payload the file belongs to — KYC, product images, a message.

With STORAGE_DRIVER=local the upload URL points back at this API and files land in a directory, so you can build against the real flow with no cloud account. Switching to s3 changes only where that URL points.

Never send a URL where a key is expected. Fields like idDocumentFrontKey and objectKeys take the key the upload grant returned. Anything else is a 422 — the backend has to be able to prove a file was uploaded by the person claiming it.

Everyday commands

CommandDoes
npm run start:devWatch mode on port 3000
npm run buildCompile to dist/
npm testUnit tests (40)
npm run test:e2eEnd-to-end against a real database (176)
npm run lintESLint with --fix
npm run db:upStart Postgres in Docker
npm run db:studioPrisma Studio, a GUI over the data
npx prisma migrate deployApply migrations

The e2e suite uses its own database (afielmarketplace_test) and wipes it before each run, so it never touches your development data.

Conventions you can rely on

  • Base URL is /v1. Every path in the reference is relative to it.
  • Auth is Authorization: Bearer <accessToken>.
  • Lists take ?page=&limit= (limit max 100) and return { data, pagination: { page, limit, total } }.
  • Money is a decimal string in RWF, never a float. Don't parse it into a JS number for arithmetic you care about.
  • Dates are ISO 8601 UTC.
  • Unknown body fields are rejected with a 400, so a typo'd key tells you immediately instead of being silently dropped.
Not built yet. Reports and moderation. There is also no CI or Dockerfile for the app itself, and storage currently points at a Cloudflare R2 development URL that has to be swapped for a CDN domain before production.