Technical Architecture
Tech Stack & Pinned Versions
| Layer | Technology | Version | Lý do / Notes |
|---|---|---|---|
| Node.js | LTS | 22.22.2 | .nvmrc tại root, nvm use để switch |
| Backend | NestJS (TypeScript) | 11.0.1 | Modular architecture khớp với industry plugin pattern, built-in DI |
| Language | TypeScript | 5.7.x | Theo NestJS 11 |
| Database | PostgreSQL | 16 (Alpine) | JSONB cho metadata, RLS cho multi-tenant (deferred Phase 8+), ACID. Docker postgres:16-alpine |
| ORM | Prisma | 7.6.0 | Type-safe; v7 dùng prisma-client-js generator + @prisma/adapter-pg bắt buộc, datasource URL config trong prisma.config.ts |
| Cache / Realtime | Redis | 7 (Alpine) | Session, real-time state, pub/sub. Docker redis:7-alpine |
| Queue | BullMQ | latest | Notification async, scheduled jobs (reminders, payment-expiry sweep, outbox publisher) |
| Customer Portal | Next.js | 16.x | SSR cho SEO (subdomain booking page), fast load |
| Admin Portal | Next.js | 16.x | Web-only theo brief, share codebase với customer portal |
| Owner App | React Native (Expo) | SDK 54 | Cross-platform; React Native 0.81.5, React 19.1 |
| Staff App | React Native (Expo) | SDK 54 | Cùng codebase với Owner App, phân quyền bằng role |
| Realtime | Socket.io (NestJS Gateway) | — | Live booking updates, staff notifications |
| File Storage | S3-compatible (MinIO) | latest | Avatar, salon branding assets, self-hosted |
| Payment Provider | Bambora Classic | — | MVP. Worldline Direct giữ trong providers/worldline-direct/ cho enterprise migration. Stripe / Vipps MobilePay / Nets / Adyen — roadmap post-MVP, drop-in qua PaymentProviderPort |
| Package Manager | Yarn | 1.22.x (Classic) | KHÔNG dùng npm/npx |
Prisma v7 — Breaking changes
- Generator provider:
prisma-client-js(CJS compatible) - Import:
from '@prisma/client'(cách truyền thống) - Adapter bắt buộc:
@prisma/adapter-pg— truyền vàoPrismaClient({ adapter }) urlkhông nằm trongdatasourceblock — config trongprisma.config.tsquadefineConfig+env()- Generator
prisma-client(mới) output ESM.tsfiles — KHÔNG compatible NestJS CJS, tránh dùng
Docker Compose ports
| Service | Host Port | Container Port |
|---|---|---|
| PostgreSQL | 5442 | 5432 |
| Redis | 6389 | 6379 |
| MinIO API | 9010 | 9000 |
| MinIO Console | 9011 | 9001 |
| NestJS API | 3010 | 3010 |
System Architecture
flowchart TB
subgraph Edge [Edge layer]
LB[Nginx / Caddy<br/>SSL + subdomain routing]
end
subgraph App [Application tier — PM2 process manager]
Customer[Next.js Customer Portal<br/>'slug'.app.no]
Admin[Next.js Admin Portal<br/>admin.app.no]
API[NestJS API<br/>api.app.no — REST + WS]
Docs[Docs static site<br/>documents.app.no]
end
subgraph Data [Data tier — internal only]
PG[(PostgreSQL<br/>Tenants / Resources / Bookings / Customers / Payments)]
Redis[(Redis<br/>Session / Cache / Pub-Sub / BullMQ)]
MinIO[(MinIO<br/>Assets / Uploads)]
end
subgraph Mobile [React Native — Expo]
Owner[Owner App<br/>iOS + Android]
Staff[Staff App<br/>iOS + Android]
end
subgraph External [External]
Bambora{{Bambora Classic<br/>checkout + webhook}}
end
LB --> Customer
LB --> Admin
LB --> API
LB --> Docs
API --> PG
API --> Redis
API --> MinIO
Owner -->|REST + WebSocket| API
Staff -->|REST + WebSocket| API
API <--> Bambora
Repository Structure
Multi-repo với shared AI context. Mỗi app là git repo riêng, nằm trong 1 workspace folder.
booking/ # Workspace folder (không phải git repo)
├── booking-api/ # Git repo — NestJS backend
│ ├── src/
│ │ ├── core/ # Core booking engine (Resource-agnostic)
│ │ │ ├── booking/
│ │ │ ├── resource/
│ │ │ ├── service/
│ │ │ ├── customer/
│ │ │ ├── notification/
│ │ │ ├── payment/
│ │ │ └── tenant/
│ │ ├── industries/ # Industry plugins
│ │ │ ├── beauty/ # MVP: Staff, beauty-specific logic
│ │ │ └── restaurant/ # Future: table booking
│ │ ├── admin/ # Admin-only endpoints
│ │ ├── auth/ # Authentication & authorization
│ │ └── common/ # Guards, interceptors, filters, pipes
│ ├── prisma/
│ │ ├── schema.prisma
│ │ └── migrations/
│ ├── .env
│ └── package.json
│
├── booking-web/ # Git repo — Next.js
│ ├── src/
│ │ ├── app/
│ │ │ ├── (customer)/ # Customer-facing booking pages
│ │ │ └── (admin)/ # Admin portal pages
│ │ ├── components/
│ │ └── generated/ # Auto-generated API client
│ ├── .env
│ └── package.json
│
├── booking-mobile/ # Git repo — React Native (Expo)
│ ├── src/
│ │ ├── features/
│ │ │ ├── booking/
│ │ │ ├── schedule/
│ │ │ ├── pos/
│ │ │ └── staff/
│ │ ├── navigation/
│ │ │ ├── owner/ # Owner-specific screens
│ │ │ └── staff/ # Staff-specific screens
│ │ ├── components/
│ │ └── generated/ # Auto-generated API client
│ ├── .env
│ └── package.json
│
├── docs/ # Shared documentation
│ └── architecture.md
├── docker-compose.yml # Shared: PostgreSQL + Redis + MinIO
├── product-brief-salon-booking-2026-03-10.md
└── CLAUDE.md # AI context cho toàn bộ workspace
Type Sharing Strategy
OpenAPI codegen — API là single source of truth cho types.
booking-api (NestJS + Swagger decorators)
│
├── npm run generate:openapi → openapi.json
│
▼
openapi.json ──► openapi-typescript-codegen (hoặc orval)
│
┌─────┴──────┐
▼ ▼
booking-web/ booking-mobile/
generated/ generated/
api-client.ts api-client.ts
- NestJS Swagger plugin tự generate
openapi.jsontừ decorators - Web/Mobile chạy codegen script → import typed API client
- Không cần npm package, không cần monorepo tooling
NestJS Module Architecture
AppModule
├── CoreModule (forRoot)
│ ├── TenantModule # Multi-tenant resolution, tenant context
│ ├── ResourceModule # CRUD Resource (abstract)
│ ├── ServiceModule # CRUD Service catalog
│ ├── BookingModule # Booking engine, scheduling logic
│ ├── CustomerModule # Customer management
│ ├── NotificationModule # SMS, push, email templates
│ ├── PaymentModule # Payment tracking (MVP: manual)
│ └── AuditModule # Mandatory audit trail
│
├── IndustryModule
│ ├── BeautyModule # MVP
│ │ ├── StaffResource # Staff extends Resource
│ │ ├── BeautyService # Nail, haircut, etc.
│ │ ├── WalkInHandler # Walk-in specific logic
│ │ ├── SelfPickService # Staff self-pick bookings
│ │ └── BeautyPosService # POS checkout flow
│ └── (future modules...)
│
├── AuthModule
│ ├── JWT strategy
│ ├── Role-based access (owner, staff, customer, admin)
│ └── Tenant-scoped guards
│
└── AdminModule # Super-admin: create tenants, support access
Multi-tenancy Strategy
Shared database, shared schema, tenant_id discriminator.
- Mọi query tự động filter by
tenant_id(NestJS middleware/interceptor) - Prisma middleware hoặc custom decorator inject tenant context
- Row-level security (RLS) ở PostgreSQL level làm safety net
// Mọi entity có tenant_id
interface TenantScoped {
tenantId: string;
}
// Request context mang tenant info
interface RequestContext {
tenant: { id: string; industryType: string; settings: TenantSettings };
user: { id: string; role: Role };
}
Subdomain routing:
nailsbyanna.app.no→ resolve tenant → load branding + servicesapi.app.no+X-Tenant-IDheader (hoặc từ JWT)
Offline-First Strategy (Mobile)
Brief yêu cầu offline mode cho staff app.
- WatermelonDB (SQLite-based, React Native) cho local data
- Sync protocol: last-write-wins với conflict detection
- Critical offline operations: view schedule, check-in customer, mark payment
- Sync khi online: queue changes → bulk push → pull latest state
Authentication Flow
Customer: Email/phone → OTP verification → JWT
Staff: Invite link from owner → set password → JWT
Owner: Email + password → JWT (+ optional 2FA)
Admin: Email + password + 2FA mandatory → JWT
All JWTs contain: userId, tenantId, role, permissions
Notification Architecture
Template-based, per industry:
Template: "booking_confirmed"
├── beauty: "Hei {customer}! Din time hos {staff} er bekreftet for {date} kl {time}"
├── restaurant: "Hei {customer}! Bord for {partySize} er bekreftet for {date} kl {time}"
Channels: SMS (primary for Norway), Push notification, Email (secondary) Provider: Configurable (Twilio, MessageBird, or local Norwegian SMS provider)