20 KiB
MotoVaultPro Architecture Overview
Architecture Summary
MotoVaultPro is a single-tenant vehicle management application built with a 5-container Docker-first architecture. All development and deployment occurs in production-configured containers with no local installation dependencies.
Core Containers
- Traefik - Reverse proxy and service discovery
- Frontend - React SPA with Vite (Node.js 20 + nginx)
- Backend - Node.js API with Fastify (Node.js 20) - includes platform feature module
- PostgreSQL - Primary database (PostgreSQL 18)
- Redis - Caching layer (Redis 8)
Key Architectural Principles
- Production-Only: All services use production builds and configuration
- Docker-First: Zero local installations, all work happens in containers
- Single-Tenant: Application serves a single user/tenant
- User-Scoped Data: All application data isolated by
user_id - Feature Capsule Organization: Self-contained feature modules in
backend/src/features/
Container Interaction Diagram
Internet/Users
|
v
+----------------+------------------+
| Traefik |
| (Reverse Proxy & TLS) |
| Ports: 80, 443, 8080 |
+----------------+------------------+
|
+---------------------+---------------------+
| |
v v
+------------------+ +--------------------+
| Frontend | | Backend |
| React + Vite | | Node.js + Fastify |
| Port: 3000 | | Port: 3001 |
+------------------+ +--------------------+
(frontend network) (backend network)
|
|
+---------------------------------+
| |
v v
+---------------+ +-------------+
| PostgreSQL | | Redis |
| Port: 5432 | | Port: 6379 |
+---------------+ +-------------+
(database network - internal)
Network Topology
Frontend Network
- Purpose: Public traffic routing
- Type: Bridge (non-internal)
- Connected Services: Traefik, Frontend
- Access: External (for Traefik public endpoints)
Backend Network
- Purpose: API service communication
- Type: Bridge (non-internal)
- Connected Services: Traefik, Backend
- Access: External (requires Auth0 JWT validation)
Database Network
- Purpose: Data layer isolation
- Type: Bridge (internal)
- Connected Services: Backend, PostgreSQL, Redis
- Access: Internal only, no external exposure
Request Flow
User Request Flow
1. User → HTTPS Request
2. Traefik → TLS Termination + Route Selection
- Domain: motovaultpro.com or www.motovaultpro.com
- Path: / → Frontend (priority 10)
- Path: /api → Backend (priority 20)
- Path: /api/platform/* → Backend (platform feature module)
3. Frontend/Backend → Process Request
4. Response → Traefik → User
Authentication Flow
1. User → Frontend → Auth0 (Login)
2. Auth0 → JWT Token → Frontend
3. Frontend → Backend (with Authorization: Bearer <JWT>)
4. Backend → Validate JWT via Auth0 JWKS
5. Backend → Extract user_id from JWT (request.user.sub)
6. Backend → Execute user-scoped queries
7. Backend → Response → Frontend
Backend Platform Flow
1. Backend Feature → Calls platform domain services (TypeScript in-process)
2. Platform module → Query PostgreSQL (vehicles schema)
3. Platform module → Check/Update Redis cache
4. Platform response → Returned to caller (vehicles, frontend proxies, etc.)
Data Access Flow
1. Backend/Platform → PostgreSQL Query
- Database: motovaultpro
- Connection: Internal via database network
2. Backend/Platform → Redis Cache Check
- Connection: Internal via database network
3. Response → Backend → Frontend
Service Details
Traefik (Reverse Proxy)
- Image: traefik:v3.0
- Purpose: Service discovery, TLS termination, load balancing
- Ports:
- 80 (HTTP)
- 443 (HTTPS)
- 8080 (Dashboard)
- Networks: frontend, backend
- Health Check:
traefik healthcheck(30s interval) - Configuration:
/etc/traefik/traefik.yml- Main config/etc/traefik/middleware.yml- Middleware rules- Dynamic routing via Docker labels
Frontend (React SPA)
- Technology: React 18 + Vite + TypeScript
- Build: Multi-stage Dockerfile (node:lts-alpine → nginx:alpine)
- Port: 3000 (internal)
- Networks: frontend
- Dependencies: Backend (API calls)
- Health Check:
curl https://motovaultpro.com(30s interval) - Environment Variables:
VITE_AUTH0_DOMAIN- Auth0 tenantVITE_AUTH0_CLIENT_ID- Auth0 application IDVITE_AUTH0_AUDIENCE- API identifierVITE_API_BASE_URL- Backend API path (/api)
- Routing: Traefik routes non-/api paths to frontend
Backend (Node.js API)
- Technology: Node.js 20 + Fastify + TypeScript
- Build: Production build in node:lts-alpine
- Port: 3001 (internal)
- Networks: backend, database
- Dependencies: PostgreSQL, Redis, Platform
- Health Check:
https://motovaultpro.com/api/health(30s interval) - Configuration:
/app/config/production.yml- Main config/app/config/shared.yml- Shared config/run/secrets/*- Secret files (K8s pattern)
- Secrets:
postgres-password- Database passwordplatform-vehicles-api-key- Platform service authauth0-client-secret- Auth0 M2M secretgoogle-maps-api-key- Maps API key
- Storage:
/app/data/documents- Document uploads
PostgreSQL (Primary Database)
- Image: postgres:15-alpine
- Database: motovaultpro
- Port: 5432 (exposed for development)
- Networks: database (internal)
- User: postgres
- Password: File-based secret (
/run/secrets/postgres-password) - Health Check:
pg_isready -U postgres(10s interval) - Storage: Named volume
mvp_postgres_data - Schemas:
public- Application data (user-scoped)vehicles- Platform vehicle data (shared)
Redis (Cache Layer)
- Image: redis:7-alpine
- Port: 6379 (exposed for development)
- Networks: database (internal)
- Persistence: AOF enabled (
--appendonly yes) - Health Check:
redis-cli ping(10s interval) - Storage: Named volume
mvp_redis_data - Usage:
- Vehicle data caching (year-based hierarchy)
- Session storage (future)
- Rate limiting (future)
Platform Module (Vehicle Data)
- Technology: TypeScript feature capsule inside
backend/src/features/platform/ - Runtime: Shares the
mvp-backendFastify container - Dependencies: PostgreSQL, Redis (accessed via backend connection pool)
- Deployment: Bundled with backend build; no separate container or port
- Health: Covered by backend
/healthendpoint and feature-specific logs - Configuration:
backend/src/features/platform/domain/*.ts- Business logicbackend/src/features/platform/data/*.ts- Database queries
- Secrets:
- Reuses backend secrets (PostgreSQL, Auth0, etc.)
- Purpose:
- Vehicle hierarchical data (years, makes, models, trims, engines)
- VIN decoding (Planned/Future - not yet implemented)
- Standardized vehicle information
Platform Module Integration
Current Architecture
Platform capabilities now run in-process within the backend as a feature capsule. This delivers:
- Shared Language & Tooling: TypeScript across the stack
- Centralized Caching: Reuses backend Redis helpers for year-based lookups
- Simplified Operations: No extra container to build, scale, or monitor
Shared Infrastructure
- Database: Uses the backend connection pool against
mvp-postgres - Cache: Shares the backend Redis client (
mvp-redis) - Runtime: Packaged with the
mvp-backendcontainer and Fastify plugins - Secrets: Relies on the same secret files as the backend (PostgreSQL, Auth0, etc.)
Communication Pattern
// Vehicles feature pulling platform data via domain services
const platformService = new PlatformVehicleDataService({
db: postgresPool,
cache: redisClient
});
const makes = await platformService.getMakes({ year: 2024 });
Ongoing Work
- Continue migrating remaining FastAPI utilities into TypeScript where needed
- Expand
/api/platform/*endpoints as new lookup types are added - Monitor backend logs for
platformnamespace entries to verify health
Feature Capsule Architecture
Organization Pattern
Features are self-contained modules within the backend service at backend/src/features/[name]/:
backend/src/features/
├── admin/ # Admin role management
├── auth/ # User authentication
├── backup/ # Database backup/restore
├── documents/ # Document storage
├── fuel-logs/ # Fuel tracking
├── maintenance/ # Service records and schedules
├── notifications/ # Email and toast notifications
├── onboarding/ # User onboarding flow
├── platform/ # Vehicle data + VIN decoding
├── stations/ # Gas station locations
├── terms-agreement/ # T&C acceptance audit
├── user-export/ # GDPR data export
├── user-preferences/ # User settings
├── user-profile/ # User profile management
└── vehicles/ # Vehicle management
Feature Structure
Each feature follows a standardized structure:
feature-name/
├── README.md # Feature documentation
├── api/ # Route handlers
├── domain/ # Business logic
├── data/ # Data access layer
├── migrations/ # Database migrations
├── tests/ # Unit and integration tests
├── events/ # Event handlers (optional)
└── external/ # External service integrations (optional)
Current Features (15)
Admin
- Purpose: Admin role management and oversight
- Tables:
admin_users,admin_audit_logs - Endpoints: Admin CRUD, catalog management, station oversight
Auth
- Purpose: User authentication and signup
- Tables: None (uses Auth0)
- Integration: Auth0 for identity management
- Endpoints: Signup, verify email, resend verification
Backup
- Purpose: Database and document backup/restore
- Tables:
backup_schedules,backup_history,backup_settings - Storage:
/app/data/backups/ - Endpoints: Admin backup CRUD, restore, schedules
Documents
- Purpose: Document storage and retrieval
- Tables:
documents(user-scoped) - Storage: Filesystem (
/app/data/documents/) - Endpoints: Upload + download + delete
Fuel Logs
- Purpose: Fuel purchase and efficiency tracking
- Tables:
fuel_logs(user-scoped) - Integration: Stations feature for location data
- Endpoints: CRUD + analytics
Maintenance
- Purpose: Service and maintenance record tracking
- Tables:
maintenance_logs,maintenance_schedules(user-scoped) - Integration: Vehicles feature, notifications
- Endpoints: CRUD + schedules + reminders
Notifications
- Purpose: Email and toast notifications
- Tables:
email_templates,notification_logs - Integration: Resend API for email delivery
- Endpoints: User summary, admin template management
Onboarding
- Purpose: User onboarding after signup
- Tables: Uses
user_profiles,user_preferences - Integration: User profile and preferences features
- Endpoints: Preferences, complete, status
Platform
- Purpose: Vehicle data and VIN decoding
- Tables:
engines,transmissions,vehicle_options - Integration: Backend dropdown cascade queries
- Endpoints: Years, makes, models, trims, engines
Stations
- Purpose: Gas station location and pricing
- Tables:
stations,community_stations - Integration: Google Maps API
- Endpoints: Search + favorites + community reports
Terms Agreement
- Purpose: Legal audit trail for T&C acceptance
- Tables:
terms_agreements - Integration: Created during signup flow
- Endpoints: None (internal use)
User Export
- Purpose: GDPR-compliant data export
- Tables: None (reads from all user data)
- Storage: Temporary TAR.GZ archives
- Endpoints: Export download
User Preferences
- Purpose: User preference management
- Tables:
user_preferences(user-scoped) - Endpoints: GET/PUT preferences
User Profile
- Purpose: User profile management
- Tables:
user_profiles(user-scoped) - Endpoints: GET/PUT profile
Vehicles
- Purpose: Vehicle inventory management
- Tables:
vehicles(user-scoped) - Integration: Platform service for make/model/trim data
- Endpoints: CRUD + dropdown data + images
Data Flow
Authentication via Auth0 JWT
1. Frontend → Auth0 Login
2. Auth0 → JWT Token (contains user_id in 'sub' claim)
3. Frontend → Backend (Authorization: Bearer <JWT>)
4. Backend → Validate JWT:
- Verify signature via Auth0 JWKS endpoint
- Check issuer: https://{AUTH0_DOMAIN}/
- Check audience: {AUTH0_AUDIENCE}
5. Backend → Extract user_id from token.sub
6. Backend → Execute user-scoped queries
User ID Extraction
// From auth.plugin.ts
await request.jwtVerify(); // Validates and populates request.user
const userId = request.user.sub; // Auth0 user ID (e.g., "auth0|123456")
User-Scoped Data Access
All application data is isolated by user_id:
-- Example: Get user's vehicles
SELECT * FROM vehicles WHERE user_id = $1;
-- Example: Create fuel log (user_id inserted)
INSERT INTO fuel_logs (user_id, vehicle_id, gallons, cost, ...)
VALUES ($1, $2, $3, $4, ...);
-- Example: Soft delete (preserves audit trail)
UPDATE vehicles
SET deleted_at = NOW()
WHERE id = $1 AND user_id = $2;
Data Isolation Strategy
- Foreign Keys: All user data tables have
user_idforeign key - Query Scoping: All queries filtered by authenticated user's ID
- Soft Deletes:
deleted_attimestamp for audit trail - No Cascade Deletes: Prevents accidental data loss
- Index Strategy: Composite indexes on (
user_id, primary key)
Cross-Feature Data Flow
1. User creates vehicle (Vehicles feature)
2. User logs fuel purchase (Fuel Logs feature)
- References vehicle_id
- References station_id (Stations feature)
3. User adds maintenance record (Maintenance feature)
- References vehicle_id
- Can attach documents (Documents feature)
Configuration Management
Kubernetes-Style Patterns
The application uses Kubernetes-inspired configuration patterns:
ConfigMaps (YAML files):
/app/config/production.yml- Service-specific config/app/config/shared.yml- Cross-service config
Secrets (File-based):
/run/secrets/postgres-password- Database credentials/run/secrets/auth0-client-secret- OAuth credentials/run/secrets/google-maps-api-key- External API keys
Environment Variables
Used for service references and runtime configuration:
NODE_ENV: production
CONFIG_PATH: /app/config/production.yml
SECRETS_DIR: /run/secrets
DATABASE_HOST: mvp-postgres
REDIS_HOST: mvp-redis
Development Workflow
Container Commands (via Makefile)
make start # Start all containers
make stop # Stop all containers
make restart # Restart all containers
make rebuild # Rebuild and restart containers
make logs # View all container logs
make logs-backend # View backend logs only
make clean # Remove containers and volumes
Health Monitoring
# Check container health
docker ps
# View specific service logs
docker logs mvp-backend -f
docker logs mvp-postgres -f
# Test health endpoints (via Traefik)
curl https://motovaultpro.com/api/health # Backend (includes platform module)
# Or from within backend container
docker compose exec mvp-backend curl https://motovaultpro.com/api/health
Database Access
# PostgreSQL shell
make db-shell-app
# Execute SQL
docker exec -it mvp-postgres psql -U postgres -d motovaultpro
# View Redis data
docker exec -it mvp-redis redis-cli
Security Architecture
Authentication & Authorization
- Provider: Auth0 (OAuth 2.0 / OpenID Connect)
- Token Type: JWT (JSON Web Token)
- Validation: JWKS-based signature verification
- Token Cache: 1 hour TTL for Auth0 public keys
- Issuer Validation: Prevents token spoofing
- Audience Validation: API-specific tokens only
Protected Resources
All API endpoints require valid JWT except:
/health- Health check (priority routing)/api/health- API health check (bypass auth)
Data Security
- VIN Handling: Check digit validation, no VIN in logs
- User Isolation: Queries filtered by authenticated
user_id - Soft Deletes: Audit trail preservation
- No Cascading Deletes: Manual data cleanup only
- Encrypted Connections: PostgreSQL SSL/TLS
Infrastructure Security
- Non-root Containers: All services run as non-root users
- Network Isolation: Internal database network
- Secret Management: File-based secrets (K8s pattern)
- No Hardcoded Credentials: Environment and file injection only
Deployment Strategy
Production Environment
- Container Orchestration: Docker Compose (current) → Kubernetes (future)
- Service Discovery: Traefik with Docker provider
- TLS: Automatic certificate management via Traefik
- Scaling: Single instance per service (MVP phase)
- Monitoring: Container health checks + log aggregation
Zero-Downtime Deployment
# Build new images
make rebuild
# Traefik handles traffic routing during restart
# Health checks ensure services are ready before traffic
Observability
Logging Strategy
- Format: Structured JSON logging
- Levels: ERROR, WARN, INFO, DEBUG
- Context: Request ID, User ID (truncated), Service name
- Aggregation: Docker logs → Future: ELK/Loki stack
Health Checks
Every service exposes health endpoints:
- Backend:
GET /health- Database + Redis connectivity - Platform:
GET /health- Database + Redis connectivity - Frontend: Nginx status check
- PostgreSQL:
pg_isready - Redis:
redis-cli ping
Metrics (Future)
- Request rate and latency
- Database query performance
- Cache hit rates
- Error rates by endpoint
Performance Considerations
Caching Strategy
- Vehicle Data: Year-based hierarchical caching in Redis
- JWT Keys: 1-hour cache for Auth0 public keys
- Database Queries: Indexed by (
user_id, primary key)
Database Optimization
- Connection Pooling: Fastify PostgreSQL plugin
- Prepared Statements: SQL injection prevention + performance
- Indexes: Composite indexes on frequently queried columns
- Query Scoping: All queries filtered by
user_idfor data locality
Horizontal Scaling (Future)
- Stateless Services: Backend and Platform can scale horizontally
- Shared State: PostgreSQL and Redis as centralized state
- Load Balancing: Traefik distributes traffic across instances
Related Documentation
- Feature Documentation:
backend/src/features/{feature}/README.md - Platform Architecture:
docs/PLATFORM-SERVICES.md - Security Details:
docs/SECURITY.md - Database Schema:
docs/DATABASE-SCHEMA.md - Testing Guide:
docs/TESTING.md - Vehicles API:
docs/VEHICLES-API.md - Development Commands:
Makefile