- Updated ESLint configuration to enforce stricter import rules for the @customer-portal/domain package, promoting better import hygiene and preventing deep imports. - Refactored various files across the BFF and portal applications to comply with the new import rules, ensuring that only the appropriate modules are imported from the domain. - Cleaned up unused imports and optimized code structure for improved maintainability and clarity. - Updated documentation to reflect changes in import practices and domain structure.
8.3 KiB
Customer Portal Architecture
🏗️ System Overview
The Customer Portal is a modern monorepo with clean separation between frontend (Next.js) and backend (NestJS), designed for maintainability and scalability.
High-Level Structure
apps/
portal/ # Next.js frontend
bff/ # NestJS Backend-for-Frontend
packages/
domain/ # Pure domain types, validation schemas, and utilities (isomorphic)
🎯 Architecture Principles
1. Separation of Concerns
- Dev vs Prod: Clear separation with appropriate tooling
- Services vs Apps: Development runs apps locally, production containerizes everything
- Configuration vs Code: Environment variables for configuration, code for logic
2. Single Source of Truth
- One environment template:
.env.example - One Docker Compose per environment
- One script per operation type
3. Clean Dependencies
- Portal: Uses
@/lib/*for shared utilities and services - BFF: Feature-aligned modules with shared concerns in
src/common/ - Domain: Framework-agnostic types and utilities
🚀 Portal (Next.js) Architecture
src/
app/ # App Router routes
components/ # Design system (atomic design)
atoms/ # Basic UI elements
molecules/ # Component combinations
organisms/ # Complex UI sections
templates/ # Page layouts
features/ # Feature modules (auth, billing, etc.)
lib/ # Core utilities and services
api/ # Zod-aware fetch client + helpers
hooks/ # Shared React hooks
utils/ # Utility functions
providers/ # Context providers
styles/ # Global styles
Conventions
- Use
@/lib/*for shared frontend utilities and services - Feature modules own their
components/,hooks/,services/, andtypes/ - Cross-feature UI belongs in
components/(atomic design) - Avoid duplicate layers - no
core/orshared/inside apps
🔧 BFF (NestJS) Architecture
src/
modules/ # Feature-aligned modules
auth/ # Authentication and authorization
users/ # User management
me-status/ # Aggregated customer status (dashboard + gating signals)
id-mappings/ # Portal-WHMCS-Salesforce ID mappings
services/ # Services catalog (browsing/purchasing)
orders/ # Order creation and fulfillment
invoices/ # Invoice management
subscriptions/ # Service and subscription management
currency/ # Currency handling
support/ # Support case management
realtime/ # Server-Sent Events API
verification/ # ID verification
notifications/ # User notifications
health/ # Health check endpoints
core/ # Core services and utilities
infra/ # Infrastructure (database, cache, queue, email)
integrations/ # External service integrations
salesforce/ # Salesforce CRM integration
whmcs/ # WHMCS billing integration
freebit/ # Freebit SIM provider integration
sftp/ # SFTP file transfer
main.ts # Application entry point
Conventions
- Prefer
modules/*over flat directories per domain - Keep DTOs and validators in-module
- Reuse
packages/domainfor domain types - External integrations in dedicated modules
API Boundary: Public vs Account
- Public APIs (
/api/public/*): strictly non-personalized endpoints intended for marketing pages and unauthenticated browsing. - Account APIs (
/api/account/*): authenticated endpoints that may return personalized responses (e.g. eligibility-gated catalogs, SIM family discount availability).
📦 Shared Packages
Domain Package (packages/domain/)
The domain package is the single source of truth for shared types, validation schemas, and utilities across both the BFF and Portal applications.
packages/domain/
├── auth/ # Authentication types and validation
├── billing/ # Invoice and payment types
├── services/ # Services catalog types
├── checkout/ # Checkout flow types
├── common/ # Shared utilities and base types
├── customer/ # Customer profile types
├── dashboard/ # Dashboard data types
├── mappings/ # ID mapping types (Portal-WHMCS-SF)
├── notifications/ # Notification types
├── opportunity/ # Salesforce opportunity types
├── orders/ # Order types and Salesforce mappings
├── payments/ # Payment method types
├── providers/ # Provider-specific type definitions
├── realtime/ # SSE event types
├── salesforce/ # Salesforce API types
├── sim/ # SIM lifecycle and Freebit types
├── subscriptions/ # Subscription types
├── support/ # Support case types
├── toolkit/ # Utility functions
└── index.ts # Public exports
Key Principles
- Framework-agnostic: No NestJS or React dependencies
- Isomorphic: Works in both Node.js and browser environments
- Zod-first validation: Schemas defined with Zod for runtime validation
- Provider mappers: Transform external API responses to domain types
Usage
Import via module entrypoints:
import { invoiceSchema, type Invoice } from "@customer-portal/domain/billing";
import { orderSummarySchema, type OrderSummary } from "@customer-portal/domain/orders";
import { SIM_STATUS } from "@customer-portal/domain/sim";
Integration with BFF
The BFF integration layer (apps/bff/src/integrations/) uses domain mappers to transform raw provider data:
External API → Raw Response → Domain Mapper → Domain Type → Use Everywhere
This ensures a single transformation point and consistent types across the application.
Logging
Centralized logging is implemented in the BFF using nestjs-pino:
- Structured JSON logging for production
- Correlation IDs for request tracing
- Automatic PII redaction for security
🔗 Integration Architecture
API Client
- Implementation: Fetch wrapper using shared Zod schemas from
@customer-portal/domain - Features: CSRF protection, auth handling, consistent
ApiResponsehelpers - Location:
apps/portal/src/lib/api/
External Services
- WHMCS: Billing system integration
- Salesforce: CRM and order management
- Redis: Caching and session storage
- PostgreSQL: Primary data store
🔒 Security Architecture
Authentication Flow
- Portal-native authentication with JWT tokens
- Optional MFA support
- Secure token rotation with Redis backing
Error Handling
- Never leak sensitive details to end users memory:6689308
- Centralized error mapping to user-friendly messages
- Comprehensive audit trails
Data Protection
- PII minimization with encryption at rest/in transit
- Row-level security (users can only access their data)
- Idempotency keys on all mutating operations
🚀 Development Workflow
Path Aliases
- Portal:
@/*,@/lib/*,@/features/*,@/components/* - BFF:
@/*mapped toapps/bff/src - Domain: Import via
@customer-portal/domain
Code Quality
- Strict TypeScript rules enforced repository-wide
- ESLint and Prettier for consistent formatting
- Pre-commit hooks for quality gates
Domain Build Hygiene
The domain package (packages/domain) is consumed via committed dist/ outputs.
- Build:
pnpm domain:build - Verify dist drift (CI-friendly):
pnpm domain:check-dist
📈 Performance & Scalability
Caching Strategy
- Invoices: 60-120s per page; bust on WHMCS webhook
- Cases: 30-60s; bust after create/update
- Catalog: 5-15m; manual bust on changes
- Keys include user_id to prevent cross-user leakage
Database Optimization
- Connection pooling with Prisma
- Proper indexing on frequently queried fields
- Optional mirrors for external system data
This architecture supports clean, maintainable code with clear separation of concerns and production-ready security.