Architecture
System architecture overview including MQTT pipeline, database design, authentication flow, and real-time data path.
System Overview
CPI is a monorepo built with Turborepo and pnpm workspaces containing:
| Component | Technology | Role |
|---|---|---|
| API | NestJS 11, TypeORM 0.3 | REST API, business logic, MQTT handling |
| Admin | React 19, React Router v7, ShadCN UI | Web dashboard (SPA mode) |
| Mobile | Expo SDK 54, React Native | iOS/Android monitoring app |
| Shared | Zod schemas, TypeScript enums | Cross-app type safety |
| API Types | Orval-generated React Query hooks | Type-safe API calls |
| UI | ShadCN component library | Shared UI components |
| Config | ESLint 9 + TypeScript configs | Shared tooling configs |
MQTT Telemetry Pipeline
The real-time data flow from device to dashboard:
Device Mosquitto NestJS API Admin/Mobile
| | | |
|-- MQTT publish ----> | | |
| (telemetry) |-- forward ------> | |
| | |-- MqttService |
| | | (receives msg) |
| | |-- EventEmitter |
| | | emit 'mqtt.telemetry'|
| | | |
| | |-- TelemetryListener |
| | | 1. Verify HMAC |
| | | 2. Check nonce |
| | | 3. Insert TimescaleDB|
| | | 4. Check alert rules |
| | | |
| | |-- WebsocketsGateway |
| | | emit to room |
| | | 'device:{id}' ----> |
| | | Socket.IOKey Components
MqttModule (global) — connects to Mosquitto on :1883, subscribes to cpi/+/telemetry, cpi/+/status, cpi/+/ack. Forwards messages via @nestjs/event-emitter.
TelemetryListener — handles mqtt.telemetry and mqtt.device-status events. Verifies HMAC signatures, checks nonce uniqueness, inserts raw telemetry into TimescaleDB hypertable, and evaluates alert rules.
WebsocketsGateway — Socket.IO gateway with JWT auth (supports both cookie and Bearer token). Clients join room device:{id} and receive telemetry:update and device:status events in real-time.
Database Architecture
PostgreSQL + TimescaleDB
The database uses PostgreSQL 16 with the TimescaleDB extension for time-series telemetry data.
Entities (TypeORM, auto-loaded):
| Entity | Description |
|---|---|
User | Admin users with role-based access (ADMIN, OPERATOR, VIEWER) |
Device | IoT devices with lifecycle status, auth method, metadata |
Alert | Configurable alert rules with thresholds |
CommandLog | Audit trail of commands sent to devices |
NotificationLog | Delivery tracking for push/email/SMS notifications |
PushToken | Expo push notification tokens per user |
Telemetry hypertable — raw SQL with TimescaleDB time_bucket() for aggregation queries. Created in the initial TypeORM migration.
Data Flow
TypeORM Entities → @InjectRepository(Entity) → Repository<Entity>
Telemetry (raw SQL) → DataSource.query() → time_bucket() aggregationMigrations auto-run on startup via migrationsRun: true in the TypeORM config.
Authentication Flow
Web (Admin Dashboard)
Login POST /auth/login
→ Validate credentials
→ Issue JWT
→ Set httpOnly cookie (access_token)
→ Subsequent requests: cookie extracted by JwtAuthGuardMobile App
Login POST /auth/login
→ Response includes JWT in body
→ Stored in Expo SecureStore
→ Sent as Bearer token via axios interceptor
→ Socket.IO: auth.token in handshakeGlobal Guards
Registered in AuthModule via APP_GUARD:
- JwtAuthGuard — extracts JWT from cookie or Bearer header, validates, attaches user to request
- RolesGuard — checks
@Roles()decorator against user role
The @Public() decorator skips JWT auth for specific endpoints.
Real-Time Architecture (Socket.IO)
Admin Dashboard
useSocket() // Shared Socket.IO connection (cookie auth)
→ Joins room 'device:{id}'
→ Receives 'telemetry:update' events
→ useDeviceTelemetry() merges historical REST + live Socket.IO dataMobile App
useSocket() // Bearer token via socket.handshake.auth.token
→ Same room-based pattern
→ Auto-disconnects on app background
→ useDeviceTelemetry() identical merge patternWsAuthAdapter
Custom Socket.IO adapter that overrides createIOServer from IoAdapter:
- Sets CORS origins
- JWT auth middleware validates token on connection
- Supports both cookie (
access_token) and Bearer token (auth.token)
Type Generation Pipeline
NestJS DTOs → Swagger decorators → OpenAPI JSON → Orval → React Query hooks
↓
@cpi/api-types package
↓
Admin + Mobile import hooksRun pnpm orval to regenerate types after API changes.