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 configsTech Stack
| Layer | Technology |
|---|---|
| Frontend | Vue 3 (Composition API), PrimeVue 4 (Aura theme), Pinia, vue-router, vue-i18n |
| Backend | Fastify 5, Zod validation, bcryptjs |
| ORM | Drizzle ORM with postgres.js driver |
| Database | PostgreSQL 16 |
| Task Queue | BullMQ + Redis (for WebSocket notifications) |
| Build | Vite, TypeScript, Turborepo |
| Desktop | Tauri 2 (Rust + WebView) |
| Deploy | Docker 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:
| Mode | URL Prefix | Purpose |
|---|---|---|
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 |
all | all routes | Development 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.
// 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 usingfetch(). Used by the web client. - Tauri adapter (
createTauriAdapter): Maps command names to Tauri IPCinvoke()calls. Used by desktop apps.
// 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 PostgreSQLStore Mode
Browser -> nginx -> Store Server -> Store PostgreSQL
|
Store Server syncs with HQ ServerSync 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-serverEach 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
| Variable | Default | Description |
|---|---|---|
DATABASE_URL | postgresql://pos_admin:pos_dev_password@localhost:5434/pos_hq | HQ database connection |
REDIS_URL | redis://localhost:6379 | Redis for BullMQ |
JWT_SECRET | dev-secret-change-in-production | JWT signing key |
PORT | 3001 | Server listen port |
HOST | 0.0.0.0 | Server listen host |
Store Server
| Variable | Default | Description |
|---|---|---|
DATABASE_URL | postgresql://pos_store:pos_store_dev@localhost:5433/pos_store | Store database connection |
JWT_SECRET | dev-secret-change-in-production | JWT signing key (must match HQ) |
STORE_ID | (none) | UUID of this store in HQ database |
STORE_CODE | (none) | Short code (e.g., ST01) |
HQ_URL | http://localhost:3001 | HQ server URL for sync |
SYNC_TOKEN | (none) | JWT token for sync authentication |
SYNC_INTERVAL_MS | 30000 | How often to run sync cycle (ms) |
PORT | 3002 | Server listen port |