active

Multi-Tenant POS Backend

Scalable point-of-sale backend supporting multiple organizations, 100+ store locations, product catalogs, barcode systems, tax groups, and real-time sales sync.

Node.js TypeScript Prisma PostgreSQL Redis Azure DevOps Docker

Overview

A headless POS backend built for a multi-tenant retail deployment, serving multiple client organizations from a single deployment with strict data isolation. Each tenant manages their own products, locations, tax groups, and transaction history.

Architecture

The system is split into domain-focused services:

  • Product Service — catalog, attributes, barcodes, pricing rules
  • Sales Service — transaction ingestion, receipt generation, refund logic
  • Sync Service — bidirectional data sync with offline-capable POS terminals
  • Reporting Service — aggregated sales analytics per location, period, and product

Data Sync for Offline POS Terminals

Retail POS terminals need to operate offline and sync when connectivity is restored. I implemented an event-sourcing light approach:

// Each terminal action is logged as an event
interface SalesEvent {
  terminalId: string;
  tenantId: string;
  sequence: number; // monotonic, per terminal
  type: "SALE" | "REFUND" | "VOID";
  payload: SalePayload;
  createdAt: Date;
}

The sync service ingests events in order, deduplicates by (terminalId, sequence), and applies them to the central ledger. Out-of-order events are queued rather than rejected.

Multi-Tenancy Strategy

Every table uses row-level tenancy with a tenantId column and corresponding index. All queries go through repository functions that always inject the tenant context from the authenticated session — client-supplied tenant IDs are ignored.

CI/CD Pipeline

Deployments run through Azure DevOps YAML pipelines with three environments:

stages:
  - stage: Build
    jobs:
      - job: Test
        steps:
          - script: npm test
      - job: BuildImage
        steps:
          - task: Docker@2

  - stage: DeployStaging
    condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop'))

  - stage: DeployProduction
    condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))

Key Metrics

  • Handles 100+ POS terminals across multiple locations
  • Sub-100ms response time for product lookups (Redis cache)
  • Zero data leakage incidents across tenants (verified by penetration test)
  • 99.9% uptime across a 12-month period