02 — Databases & Multi-Tenancy

Master DB, tenant DBs, provisioning, schemas

CEO Summary

Data isolation is critical for a multi-tenant SaaS. Each business gets its own set of databases (main, analytics, HR) — no tenant ever sees another tenant's data. Business value: Compliance, trust, enterprise sales. Key metrics: Tenant count, DB size per tenant, provisioning success rate. If this breaks: New signups fail; existing tenants may lose access if connection resolution fails.

Multi-Tenancy Model

flowchart TB subgraph master [Master DB: ppos-cloudfresh-dev] Tenants[Tenants] Users[Users] Orgs[Organizations] TenantEmployees[tenant_employees] end subgraph tenant1 [Tenant: mariospizza] Main1[dev-mariospizza] Analytics1[dev-analytics-mariospizza] HR1[dev-hr-mariospizza] end subgraph tenant2 [Tenant: acme-coffee] Main2[dev-acme-coffee] Analytics2[dev-analytics-acme-coffee] HR2[dev-hr-acme-coffee] end Tenants --> Main1 Tenants --> Main2 TenantEmployees --> Main1 TenantEmployees --> Main2

Overview

Google Cloud SQL MySQL 8.0. All databases on same instance. Data isolated by tenant — each business gets its own set of databases.

Database Types

Master Database

Default name: ppos-cloudfresh-dev — override with environment variable MYSQL_MASTER_DATABASE (or MYSQL_PLATFORM_DATABASE) so staging clones or separate instances can use a different database name without code changes. Resolved in backend/config/tenantDatabase.js when getTenantConnection('master', 'main') is used.

Core tables: Tenants, Users, Organizations, tenant_employees, Sessions.

Platform barcode catalog: platform_product_catalog — platform-owned GTIN/barcode → title, brand, image (image_url: HTTPS URL or data:image/jpeg;base64,… stored as MEDIUMTEXT), JSON attributes (enrichment for inventory and POS; not tenant stock). Migrations: backend/migrations/create-platform-product-catalog.sql, alter-platform-catalog-image-url-mediumtext.sql (legacy VARCHAR→MEDIUMTEXT). The service can also CREATE TABLE IF NOT EXISTS and auto-widen image_url on first use.

Per-Tenant Databases

Prefixes (env): MYSQL_TENANT_DB_PREFIX (default dev-), MYSQL_ANALYTICS_DB_PREFIX (default dev-analytics-). Example: if prefix is staging-, main DB is staging-{slug}.

TypeName PatternPurpose
Main{MYSQL_TENANT_DB_PREFIX}{slug}Invoices, Customers, Staff, POS, Inventory
Analytics{MYSQL_ANALYTICS_DB_PREFIX}{slug}Business/sales analytics
HRdev-hr-{slug}Employees, payroll, PTO (separate resolver; pattern may differ)

Template Databases

dev-template, dev-analytics-template, dev-hr-template — Schema only, no data.

Tenant Resolution Order

  1. Already resolved
  2. Master admin
  3. Session tenantSlug
  4. Headers (X-Tenant-Slug, X-Org-Id)
  5. Session tenantId
  6. API key
  7. Subdomain
  8. Dev fallback
  9. Otherwise 401

Connection Managers

tenantDatabase.js: getTenantConnection(tenantName, dbType) — main | analytics. No HR.

multiDatabaseManager.js: getConnection(tenantIdOrSlug, type) — MAIN | ANALYTICS | HR.

Use CaseUse
POS, devices, catalog, inventorytenantDatabase.getTenantConnection(slug, 'main')
HR routesmultiDatabaseManager.getConnection(slug, 'HR')
Auth, provisioningmultiDatabaseManager

Tenant Provisioning

Trigger: POST /api/public/signup

Service: comprehensiveTenantProvisioning.js

  1. Validate signup data
  2. Create Organization, Tenant, User in master
  3. Generate slug
  4. Create DBs: dev-{slug}, dev-analytics-{slug}, dev-hr-{slug}
  5. Copy schema from templates
  6. Create owner user in tenant main
  7. Add owner to Staff
  8. Register in tenant_employees
  9. Create default catalog, locations
  10. On failure: rollback

Key Schema (Main Tenant)

Invoices, InvoiceLineItems, Products, CatalogItems, Customers, Staff, Devices, Locations, InventoryItems, inventory_ledger, pos_carts, Transactions, GiftCards, Tickets, Recipes, Preparables, SignageFolders, SignageAds, SignageDisplays (digital signage — created on first use; see 26 — Signage)

Database Naming

ContextPatternExample
MasterMYSQL_DATABASEppos-cloudfresh-dev
Tenant maindev-{slug}dev-mariospizza
Tenant analyticsdev-analytics-{slug}dev-analytics-mariospizza
Tenant HRdev-hr-{slug}dev-hr-mariospizza

Tenant Configs (tenants/)

Per-tenant folders for local/dev: MasterTenant-Tenant001, MarioRossi-Tenant004, etc.

PathPurpose
config/settings.jsTenant-specific settings
config/database.jsDB connection config
.env.localEnv vars for local run
Dockerfile, docker-compose.ymlContainerized run

Used when running tenant-specific dev or Docker. Relationship: multiDatabaseManager reads from master DB; tenant configs are for local overrides.

Seeding the platform barcode catalog (optional)

Third-party dumps (e.g. Kaggle Universal Product Code Database) can jump-start enrichment: mostly UPC-A + product description, no images. Always read the dataset license on Kaggle before loading into production; many community datasets are for research — confirm redistribution/commercial use fits your policy.

Alternatives: Commercial barcode APIs (GS1, aggregators) for higher accuracy and support; category-specific open data (e.g. Open Food Facts) for groceries with richer attributes.

Repo script: backend/scripts/seed-platform-catalog-from-file.js — stream CSV/TSV into platform_product_catalog with source='import', INSERT IGNORE so existing GTINs (e.g. manual) are not overwritten. Example: cd backend && node scripts/seed-platform-catalog-from-file.js --file /path/to/file.csv --dry-run --limit 5000 then run without --dry-run. Copy the unzipped file to the server or run from a machine with DB access.

Migrations

Path: backend/migrations/

FilePurpose
create-platform-product-catalog.sqlMaster DB: platform_product_catalog (barcode/GTIN enrichment)
create-recipe-system-tables.sqlRecipes, preparables
update-pos-cart-schema.jsPOS cart schema updates
create-ticket-settings-table.jsTicket settings
create-ticket-categories-table.jsTicket categories
add-parentId-to-ticket-categories.jsCategory hierarchy

Key Provisioning & Schema Scripts

Path: backend/scripts/

ScriptPurpose
create-template-database.jsCreate main template
create-hr-template-database.jsCreate HR template
create-hr-database-for-tenant.jsProvision HR DB for tenant
migrate-tenant-schemas.jsApply migrations to tenants
update-template-database.jsUpdate template schema
add-seating-layouts-table.jsSeating layouts
initialize-tenant-databases.jsInitialize tenant DBs
setup-tenant-databases.jsSetup tenant DBs

Other scripts: seeders (seed.js, seed-admin.js), fixes (fix-*.js), tests (test-*.js). See backend/scripts/ for full list.

Browser storage (not MySQL)

The POS offline mutation queue uses each user’s IndexedDB database pawspos_offline (object store queue). It is not a MySQL database and is not provisioned on the server. See 14 — Frontend and 06 — POS & Invoicing.