--- 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
:80/:443] FE[Next.js Frontend
standalone server
:8083] BE[Express Backend
+ Socket.IO
:5001] Mongo[(MongoDB 8)] Redis[(Redis 8)] SHK[SHKeeper
Crypto Gateway] SMTP[SMTP
Nodemailer] OAI[OpenAI API] BC[Blockchain RPC
Alchemy / WalletConnect] Browser -->|HTTPS + WSS| CDN Browser -->|HTTPS| Nginx Nginx --> FE Nginx --> BE FE -->|REST /api/*| BE FE -.->|Socket.IO| BE BE --> Mongo BE --> Redis BE -->|Pay-in / Pay-out| SHK SHK -.->|Webhook HMAC| 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 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 ``` 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 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 | 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 (SHKeeper) POST to `/api/payment/shkeeper/webhook`. The backend verifies HMAC signature, updates the `Payment` document, advances any linked `PurchaseRequest`/`SellerOffer` state, and emits Socket.IO events to both buyer and seller rooms. ```mermaid sequenceDiagram participant SHK as SHKeeper participant BE as Backend participant DB as MongoDB participant Buyer participant Seller SHK->>BE: POST /api/payment/shkeeper/webhook
X-Signature: HMAC-SHA256 BE->>BE: verifySignature(body, header, SHKEEPER_WEBHOOK_SECRET) BE->>DB: Payment.updateOne({providerPaymentId}, {status:"completed"}) BE->>DB: PurchaseRequest.updateOne(..., {status:"funded"}) BE-->>Buyer: socket emit "payment:status-updated" BE-->>Seller: socket emit "request:funded" BE-->>SHK: 200 OK ``` See [[Payment Flow - SHKeeper]] for the full 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` | | 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 - [[Payment Flow - SHKeeper]] — end-to-end crypto pay-in flow