---
title: System Architecture
tags: [architecture, system, overview]
created: 2026-05-23
---

# System Architecture

End-to-end architecture for the **Amn** escrow marketplace platform — a two-repo system (frontend + backend) that brokers crypto-escrowed transactions between buyers and sellers.

> [!info]
> Read [[Backend Architecture]] and [[Frontend Architecture]] for component-level detail. Read [[Infrastructure]] for deployment topology.

---

## 1. High-level topology

```mermaid
flowchart LR
    Browser[Browser / PWA]
    CDN[Static CDN]
    Nginx[Nginx Reverse Proxy<br/>:80/:443]
    FE[Next.js Frontend<br/>standalone server<br/>:8083]
    BE[Express Backend<br/>+ Socket.IO<br/>:5001]
    Mongo[(MongoDB 8<br/>primary runtime store)]
    PG[(PostgreSQL 18<br/>migration target / quote table)]
    Redis[(Redis 8)]
    RN[Request Network<br/>Pay-in + webhooks]
    CFWorker[Durable webhook ingress<br/>roadmap]
    SMTP[SMTP<br/>Nodemailer]
    OAI[OpenAI API]
    BC[Blockchain RPC<br/>Alchemy / WalletConnect]

    Browser -->|HTTPS + WSS| CDN
    Browser -->|HTTPS| Nginx
    Nginx --> FE
    Nginx --> BE
    FE -->|REST /api/*| BE
    FE -.->|Socket.IO| BE
    BE --> Mongo
    BE -.->|PG_URL + migration/quote paths| PG
    BE --> Redis
    BE -->|Pay-in intent / status| RN
    RN -.->|Signed webhook| CFWorker
    CFWorker -.->|Forward / replay| BE
    BE --> SMTP
    BE --> OAI
    FE -->|Wallet Connect| BC
    BE -->|Tx verify| BC
```

---

## 2. Request lifecycle (typical authenticated REST call)

```mermaid
sequenceDiagram
    actor U as User
    participant FE as Frontend (Next.js)
    participant AX as Axios Client
    participant BE as Backend (Express)
    participant MW as Middleware Chain
    participant SVC as Service Layer
    participant DB as MongoDB
    participant SK as Socket.IO

    U->>FE: Interact with UI
    FE->>AX: useMutation(...).mutate(payload)
    AX->>AX: Attach Authorization: Bearer <jwt>
    AX->>BE: POST /api/marketplace/requests
    BE->>MW: helmet, cors, body-parser
    MW->>MW: authMiddleware → verify JWT
    MW->>MW: validation middleware
    MW->>SVC: controller invokes service
    SVC->>DB: Mongoose create / update
    DB-->>SVC: document
    SVC->>SK: emitToRoom("user-{id}", "request:created", payload)
    SVC-->>BE: response payload
    BE->>AX: 200 { success, data }
    AX->>FE: React Query cache update
    FE-->>U: UI re-render
```

> [!note] Postgres status on `integrate-main-into-development`
> Backend `2.6.79` includes Drizzle schemas, migrations, repository implementations, backfill/verify tooling, and conditional oracle quote persistence to Postgres. It is not a full runtime cutover: ordinary services still call Mongoose models directly and MongoDB remains the primary store. See [[Postgres Runtime Cutover Status]] for the current boundary.

Concurrent realtime path:

```mermaid
sequenceDiagram
    participant FE as Frontend
    participant BE as Backend
    FE->>BE: socket.connect() w/ auth token
    BE-->>FE: socket connected
    FE->>BE: join-user-room <userId>
    BE-->>FE: joined user-{userId}
    Note over BE,FE: long-lived connection
    BE-->>FE: emit "notification:received"
    FE->>FE: update notification badge
```

---

## 3. Deployment topology

Production runs as a single Docker Compose stack (`backend/docker-compose.production.yml`) behind an internal Nginx that proxies to both the frontend and backend containers. Watchtower watches the container registry (`git.manko.yoga/manawenuz/escrow-backend`) and auto-pulls new images tagged `latest`.

| Layer | Service | Image | Port | Purpose |
|---|---|---|---|---|
| Edge | Nginx | `nginx:alpine` | 8083 | Reverse proxy + static assets |
| App | Frontend | `nickapp-frontend:latest` | 8083 (internal) | Next.js standalone |
| App | Backend | `nickapp-backend:latest` | 5001 (internal) | Express + Socket.IO |
| Data | MongoDB | `mongo:8.0` | 27017 (internal) | Primary store |
| Data | PostgreSQL | `postgres:18` / `postgres:18-alpine` | 5432 (internal) | Migration target; required for PG backfill/verify and oracle `payment_quotes` when enabled |
| Data | Redis | `redis:8-alpine` | 6379 (internal) | Cache + sessions + rate-limit counters |

External SSL termination, DNS, and CDN are assumed to live in front of Nginx (CloudFlare / nginx-proxy / similar).

> [!note]
> Dev uses `backend/docker-compose.dev.yml` (no Nginx, no frontend container — developer runs FE locally with `yarn dev`). See [[Docker Setup]] for full compose breakdown.

---

## 4. Network ports

| Port | Process | Visibility | Notes |
|---|---|---|---|
| 8083 | Frontend (Docker) | Public via Nginx | `next start` standalone |
| 3000 | Frontend (local dev) | localhost | `yarn dev` — hot reload |
| 5001 | Backend (Express + Socket.IO) | Public via Nginx `/api` & `/socket.io` | One process, two protocols |
| 27017 | MongoDB | Internal | Mapped to host only in dev |
| 6379 | Redis | Internal | Mapped to host only in dev |
| 222 | Gitea SSH | Public | Used for `git clone` |

---

## 5. Data flow patterns

### 5.1 Read path

REST `GET` requests are cache-able. React Query on the frontend de-duplicates concurrent requests, retries on failure (exponential backoff), and treats data as stale after `staleTime` (default 60s, varies per query). Backend reads use Mongoose lean queries where possible for performance.

### 5.2 Write path

Mutations follow optimistic-then-confirm:
1. Frontend `useMutation` issues the request, often with `onMutate` updating cache optimistically.
2. Backend validates → writes to MongoDB → emits affected socket rooms → returns canonical response.
3. Frontend invalidates related query keys; React Query refetches.
4. Other connected clients receive the socket event and refetch on their side.

### 5.3 Webhook path (inbound)

External services POST payment callbacks to provider-specific webhook routes. The current primary path is Request Network at `/api/payment/request-network/webhook`; the target architecture puts a durable ingress worker in front of the backend so raw delivery evidence can be replayed after outages. The backend remains the trust oracle: it verifies signatures, deduplicates deliveries, applies Transaction Safety Provider checks, updates ledger/payment state, and emits Socket.IO events to both buyer and seller rooms.

```mermaid
sequenceDiagram
    participant RN as Request Network
    participant WK as Durable ingress worker
    participant BE as Backend
    participant DB as MongoDB
    participant Buyer
    participant Seller
    RN->>WK: POST signed webhook<br/>delivery id + raw body
    WK->>WK: Store immutable delivery evidence
    WK->>BE: Forward / replay webhook
    BE->>BE: Verify RN signature + idempotency
    BE->>BE: Transaction Safety Provider checks tx hash, recipient, token, amount, confirmations
    BE->>DB: Append ledger entry + Payment escrowState="funded"
    BE->>DB: PurchaseRequest.updateOne(..., {status:"payment"})
    BE-->>Buyer: socket emit "payment:status-updated"
    BE-->>Seller: socket emit "request:funded"
    BE-->>WK: 200 OK
```

See [[PRD - Request Network In-House Checkout]] and [[Request Network Integration Constraints]] for the full Request Network sequence.

---

## 6. Scaling considerations

| Concern | Current state | Scale-out path |
|---|---|---|
| Backend stateless? | Yes — JWT-only auth, no in-memory session | Run N replicas behind LB; use Redis pub/sub adapter for Socket.IO |
| MongoDB | Single-node | Replica set → sharding by `buyerId` |
| PostgreSQL | Dev/staging service for migration work | Managed Postgres or hardened self-hosted PG with backups/PITR before cutover |
| Redis | Single-node | Cluster mode; separate cache vs session DBs |
| Socket.IO | Single process | `@socket.io/redis-adapter` for multi-node fan-out |
| File uploads | Local `uploads/` mount | S3 / R2; multer-s3 adapter |
| Logs | Container stdout | Loki / ELK |
| Errors | Sentry (`@sentry/nextjs` on FE) | Add Sentry backend SDK |

> [!warning]
> Before horizontal-scaling backend, switch Socket.IO to the Redis adapter — otherwise users on different nodes will not receive each other's events.

---

## 7. Cross-cutting concerns

- **Auth** — Bearer JWT on REST, same token also passed via Socket.IO `auth` payload on connect. See [[Security Architecture]] & [[Authentication Flow]].
- **i18n** — Frontend supports en/fa/ar/fr/cn/vi via `i18next`. RTL handled by `stylis-plugin-rtl` per direction. See [[Internationalization & RTL]].
- **Realtime** — All notification/chat/payment state changes broadcast over Socket.IO. See [[Real-time Layer]].
- **Errors** — Standardized envelope `{ success: false, error: { code, message, details } }` via `backend/src/shared/utils/response-handler.ts`. Sentry on frontend.
- **Idempotency** — Payment webhooks idempotent by `providerPaymentId` + status. Pay-out uses a client-supplied `reference` string.

---

## 8. Related documents

- [[Backend Architecture]] — module-level walk-through of the Express app
- [[Frontend Architecture]] — Next.js App Router & section organization
- [[Infrastructure]] — Docker, compose, registry, Watchtower
- [[Real-time Layer]] — Socket.IO setup, rooms, events
- [[Security Architecture]] — auth, hashing, rate-limit, webhook HMAC
- [[Tech Stack]] — exact versions & purpose of every dependency
- [[Escrow Flow]] — current Request Network pay-in, ledger, and custody release flow
