Skip to content

System Architecture

Overview

POS is a multi-store retail point-of-sale system built as a monorepo. It supports a central headquarters (HQ) that manages products, users, and reporting, while individual stores run independently with periodic synchronization back to HQ.

Monorepo Structure

The project uses pnpm workspaces with Turborepo for task orchestration.

pos/
  apps/
    hq-server/          # HQ API server (Fastify, port 3000)
    store-server/       # Store API server (Fastify, port 3002)
    web-client/         # Shared browser PWA (Vue 3)
    hq-manager/         # Tauri desktop app for HQ (unused in prod)
    pos-terminal/       # Tauri desktop app for POS terminal (unused in prod)
    store-manager/      # Tauri desktop app for store manager (unused in prod)
  packages/
    shared-types/       # TypeScript interfaces shared across all apps
    i18n/               # vue-i18n messages (en + es)
    ui-kit/             # PrimeVue theme and shared components
    tax-engine/         # Tax calculation logic (basis points)
    sync-protocol/      # Sync protocol types
    event-bus/          # Cross-component event bus
    backend-adapter/    # Abstraction layer: Tauri invoke() or HTTP fetch()
  docs/                 # VitePress documentation site
  tools/
    scripts/            # deploy.sh, setup-dev.sh, validate-i18n.js
    docker-init/        # Docker initialization helpers
  deploy/               # Per-deployment config.json and nginx configs

Tech Stack

LayerTechnology
FrontendVue 3 (Composition API), PrimeVue 4 (Aura theme), Pinia, vue-router, vue-i18n
BackendFastify 5, Zod validation, bcryptjs
ORMDrizzle ORM with postgres.js driver
DatabasePostgreSQL 16
Task QueueBullMQ + Redis (for WebSocket notifications)
BuildVite, TypeScript, Turborepo
DesktopTauri 2 (Rust + WebView)
DeployDocker Compose, nginx, Traefik (TLS)

Application Architecture

Three-App Model

                  +-------------------+
                  |   HQ Server       |
                  |   (port 3000)     |
                  |   PostgreSQL HQ   |
                  +--------+----------+
                           |
              WebSocket + HTTP Sync
                           |
         +-----------------+-----------------+
         |                                   |
+--------+----------+            +----------+---------+
|  Store 1 Server   |            |  Store 2 Server    |
|  (port 3002)      |            |  (port 3002)       |
|  PostgreSQL Store |            |  PostgreSQL Store   |
+---------+---------+            +----------+----------+
          |                                 |
   nginx (store mode)              nginx (store mode)
          |                                 |
   web-client (SPA)                web-client (SPA)

HQ Server (apps/hq-server)

The central API. Manages the master product catalog, users, roles, permissions, stores, and aggregated reporting. Receives pushed data (sales, inventory adjustments, register sessions) from stores via the sync protocol. Runs a WebSocket server that stores connect to for real-time sync notifications.

Key responsibilities:

  • Master data management (products, departments, taxes, suppliers, tenders, customers, sales reps)
  • User and role-based access control
  • Sync pull endpoint (stores fetch updates)
  • Sync push endpoint (stores send local data)
  • WebSocket server for store connectivity and notifications
  • Aggregated cross-store reporting
  • Worksheets (bulk product changes with approval workflow)
  • Purchase order management

Store Server (apps/store-server)

Runs at each physical store location. Has its own PostgreSQL database that mirrors HQ data through sync. Handles local POS operations (sales, register sessions, inventory) and pushes changes back to HQ.

Key responsibilities:

  • Local copy of products, users, taxes, tenders (pulled from HQ)
  • Sales processing and register management
  • Inventory tracking and adjustments
  • Sync client (pulls from HQ, pushes local changes)
  • WebSocket client for real-time notifications from HQ
  • Transfer management between stores

Web Client (apps/web-client)

A single Vue 3 SPA that serves three different UI modes depending on deployment configuration:

ModeURL PrefixPurpose
hq/hq/*HQ Manager: products, users, stores, reports, sync dashboard
store/store/*Store Manager: inventory, transfers, local reports
store/pos/*POS Terminal: sales, checkout, register open/close
allall routesDevelopment mode with all routes enabled

The mode is determined by a config.json file served by nginx at /config.json. Each deployment (HQ, Store 1, Store 2) has its own config.json specifying mode, apiUrl, storeId, and storeCode.

json
// deploy/store1/config.json
{
  "mode": "store",
  "apiUrl": "",
  "storeId": "da92c128-...",
  "storeCode": "ST01",
  "storeName": "Downtown Store"
}

Backend Adapter Pattern

The @pos/backend-adapter package provides a unified interface for frontend code to call backend operations. It abstracts the difference between:

  • Web adapter (createWebAdapter): Maps command names to REST endpoints using fetch(). Used by the web client.
  • Tauri adapter (createTauriAdapter): Maps command names to Tauri IPC invoke() calls. Used by desktop apps.
typescript
// Frontend code is the same regardless of platform:
const result = await adapter.execute('get_products', { search: 'coffee' });

The web adapter contains a full mapping of ~100 command names to HTTP methods, endpoints, and URL suffixes.

Data Flow

HQ Mode

Browser -> nginx -> HQ Server -> HQ PostgreSQL

Store Mode

Browser -> nginx -> Store Server -> Store PostgreSQL
                                      |
                                Store Server syncs with HQ Server

Sync Flow

Store Server <-- pull (HTTP GET) -- HQ Server    (master data: products, users, taxes, ...)
Store Server -- push (HTTP POST) --> HQ Server   (local data: sales, adjustments, registers, ...)
Store Server <-- WebSocket notify -- HQ Server   (real-time: "products updated, pull now")

Deployment Architecture

Production uses Docker Compose with Traefik for TLS termination and routing:

Traefik (HTTPS)
  |
  +-- hq.pos.example.com      -> pos-hq-web (nginx) -> static SPA + proxy /api/ to pos-hq-server
  +-- tienda1.pos.example.com  -> pos-store1-web (nginx) -> static SPA + proxy /api/ to pos-store1-server
  +-- tienda2.pos.example.com  -> pos-store2-web (nginx) -> static SPA + proxy /api/ to pos-store2-server

Each store has its own:

  • PostgreSQL instance (isolated data)
  • Store server container
  • nginx container (serves the same SPA build with a different config.json)

Shared infrastructure:

  • One HQ PostgreSQL instance
  • One Redis instance (for BullMQ notification queue)
  • One HQ server container

Environment Variables

HQ Server

VariableDefaultDescription
DATABASE_URLpostgresql://pos_admin:pos_dev_password@localhost:5434/pos_hqHQ database connection
REDIS_URLredis://localhost:6379Redis for BullMQ
JWT_SECRETdev-secret-change-in-productionJWT signing key
PORT3001Server listen port
HOST0.0.0.0Server listen host

Store Server

VariableDefaultDescription
DATABASE_URLpostgresql://pos_store:pos_store_dev@localhost:5433/pos_storeStore database connection
JWT_SECRETdev-secret-change-in-productionJWT signing key (must match HQ)
STORE_ID(none)UUID of this store in HQ database
STORE_CODE(none)Short code (e.g., ST01)
HQ_URLhttp://localhost:3001HQ server URL for sync
SYNC_TOKEN(none)JWT token for sync authentication
SYNC_INTERVAL_MS30000How often to run sync cycle (ms)
PORT3002Server listen port