A complete IoT device management system with ESP32 devices, Node.js TypeScript backend, PostgreSQL database, and MQTT communication.
βββββββββββββββ MQTT βββββββββββββββ REST API βββββββββββββββ
β ESP32 β βββββββββββΊ β Backend β βββββββββββββββΊ β Clients β
β Devices β β (Node.js) β β (Web/Mobile)β
βββββββββββββββ βββββββββββββββ βββββββββββββββ
β β β
β β β
βΌ βΌ βΌ
βββββββββββββββ βββββββββββββββ βββββββββββββββ
β Sensors β β MQTT β β REST API β
β (DHT22, etc)β β Broker β β Endpoints β
βββββββββββββββ βββββββββββββββ βββββββββββββββ
β
βΌ
βββββββββββββββ
β PostgreSQL β
β Database β
βββββββββββββββ- Node.js >= 20.0.0
- npm >= 9.0.0
- Docker and Docker Compose (for PostgreSQL and MQTT broker)
- ESP32 development board
- DHT22 temperature/humidity sensor
- Soil moisture sensor
- Light sensor
- Relay modules (for water control)
Start PostgreSQL and Mosquitto MQTT broker using Docker Compose:
docker compose up -d
# or from repository root (npm workspaces helper)
npm run compose:upThis will start:
- PostgreSQL on port
5432 - Mosquitto MQTT broker on port
1883(MQTT)
- Navigate to the backend directory:
cd backend- Install dependencies:
npm install- Create a
.envfile in thebackenddirectory:
# Server Configuration
NODE_ENV=development
PORT=8000
# Database
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_DB=smart_pot
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
# MQTT Configuration
MQTT_URL=mqtt://localhost:1883
MQTT_USERNAME=
MQTT_PASSWORD=
# JWT Configuration
JWT_SECRET=your_jwt_secret_key_here_change_in_production
JWT_EXPIRY_HOURS=24
# Telemetry Retention (days)
TELEMETRY_RETENTION_DAYS=30
# Aggregation Worker (cron expression)
AGGREGATION_CRON=0 * * * *- Run database migrations:
npm run migration:run- Start the development server:
npm run devThe server will run on http://localhost:8000
- Navigate to the frontend directory:
cd frontend- Install dependencies:
npm install- Configure environment variables (optional but recommended). Create a
.envfile infrontend:
VITE_API_BASE_URL=http://localhost:8000/api/v1- Start the development server:
npm run devThe web dashboard will be available on http://localhost:5173 (default Vite port).
-
Update the ESP32 configuration in
esp32/config.h:#define WIFI_SSID "your_wifi_ssid" #define WIFI_PASSWORD "your_wifi_password" #define MQTT_BROKER "192.168.1.100" // Your server IP #define MQTT_PORT 1883
-
Upload the ESP32 code using Arduino IDE
-
Ensure the ESP32 device publishes telemetry to the correct MQTT topics (see MQTT Topics section below)
smart-pot/
βββ backend/ # Node.js TypeScript backend API
β βββ src/ # Source code
β β βββ config/ # Configuration (env, swagger, etc.)
β β βββ controllers/ # Request handlers
β β βββ db/ # Database config and entities
β β β βββ entities/ # TypeORM entities
β β β βββ migrations/ # Database migrations (generated)
β β βββ dto/ # Request/response validation schemas (Zod)
β β βββ middlewares/ # Express middlewares
β β βββ routes/ # API route definitions
β β βββ services/ # MQTT, logger, graceful shutdown, etc.
β β βββ utils/ # Helpers and shared utilities
β β βββ workers/ # Background workers (e.g. aggregation)
β β βββ app.ts # Express app setup
β β βββ server.ts # Server entry point
β βββ package.json # Backend dependencies & scripts
β βββ tsconfig.json # TypeScript configuration
β
βββ frontend/ # React + TypeScript + Vite web dashboard
β βββ src/
β β βββ features/ # Feature-based modules (auth, device, profile, home)
β β βββ components/ # Reusable UI components & layouts (shadcn/ui)
β β βββ router/ # React Router configuration
β β βββ lib/ # HTTP client, env helpers, utils
β β βββ providers/ # Global providers (React Query, theme, auth, etc.)
β βββ public/ # Static assets & icons
β βββ package.json # Frontend dependencies & scripts
β
βββ esp32/ # ESP32 Arduino firmware
β βββ esp32.ino # Main firmware entry
β βββ config.h # Board- and network-level configuration
β βββ wifi_manager.* # WiFi connection management
β βββ mqtt_manager.* # MQTT topics, connection and messaging
β βββ relay_manager.* # Channel modes, auto config and scheduling
β βββ time_manager.* # RTC/time utilities
β βββ sms_manager.* # SMS control interface (SIM800L)
β βββ utils.* # Shared helper functions
β βββ README.md # ESP32 documentation
β
βββ mosquitto/ # Mosquitto MQTT broker configuration
β βββ config/ # Mosquitto config files
βββ docker-compose.yml # Docker Compose for Postgres + Mosquitto
βββ package.json # Root monorepo config (npm workspaces)
βββ README.md # This fileCreate backend/.env with the following variables:
# Server Configuration
NODE_ENV=development
PORT=8000
# Database
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_DB=smart_pot
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
# MQTT Configuration
MQTT_URL=mqtt://localhost:1883
MQTT_USERNAME= # Optional
MQTT_PASSWORD= # Optional
# JWT Configuration
JWT_SECRET=your_jwt_secret_key_here_change_in_production
JWT_EXPIRY_HOURS=24
# Telemetry Retention (days)
TELEMETRY_RETENTION_DAYS=30
# Aggregation Worker (cron expression - runs every hour by default)
AGGREGATION_CRON=0 * * * *Update esp32/config.h:
// WiFi Configuration
#define WIFI_SSID "your_wifi_ssid"
#define WIFI_PASSWORD "your_wifi_password"
// MQTT Configuration
#define MQTT_BROKER "192.168.1.100" // Your server IP
#define MQTT_PORT 1883
#define MQTT_USERNAME "" // Optional
#define MQTT_PASSWORD "" // Optional-
esp/{deviceId}/telemetry- Telemetry data from device{ "temperature": 25.6, "humidity": 60.2, "soilMoisture": 450, "lightLevel": 1, "timestamp": 1640995200 } -
esp/{deviceId}/status- Device online/offline status{ "online": true } -
esp/{deviceId}/ack- Command acknowledgments{ "commandId": "uuid", "type": "delivery" | "execution", "success": true, "errorMessage": "optional error message" }
-
esp/{deviceId}/command- Commands sent to device{ "commandId": "uuid", "type": "water" | "stop-water" | "set-mode" | "set-profile" | "custom", "payload": { ... } }
All API endpoints are prefixed with /api/v1
-
POST /api/v1/auth/signup- Register a new user{ "email": "user@example.com", "password": "securepassword", "name": "User Name" } -
POST /api/v1/auth/login- Login and get JWT token{ "email": "user@example.com", "password": "securepassword" } -
GET /api/v1/auth/me- Get current user info (requires authentication)
-
GET /api/v1/devices- List all devices owned by authenticated user- Query params:
page,limit,search
- Query params:
-
POST /api/v1/devices/pair- Pair a device to authenticated user{ "deviceId": "esp32_001", "name": "My Device" } -
GET /api/v1/devices/:deviceId- Get device details with current status -
PATCH /api/v1/devices/:deviceId- Update device settings{ "name": "Updated Name", "mode": "auto" | "manual", "profile": { ... } } -
POST /api/v1/devices/:deviceId/unpair- Unpair device from user
-
GET /api/v1/devices/:deviceId/status- Get current device status -
GET /api/v1/devices/:deviceId/history- Get telemetry history- Query params:
from,to,type(raworhourly),page,limit
- Query params:
-
POST /api/v1/devices/:deviceId/telemetry- Ingest telemetry data (internal use)
-
POST /api/v1/devices/:deviceId/commands/water- Send water command{ "duration": 30, "zone": 1 } -
POST /api/v1/devices/:deviceId/commands/stop-water- Stop watering -
POST /api/v1/devices/:deviceId/commands/set-mode- Change device mode{ "mode": "auto" | "manual" } -
POST /api/v1/devices/:deviceId/channels/auto-config- Set auto mode configuration{ "threshold": 30, "durationSec": 300 } -
POST /api/v1/devices/:deviceId/commands/custom- Send custom command{ "action": "custom_action", "params": { ... } } -
GET /api/v1/devices/:deviceId/commands- Get command history- Query params:
page,limit,status
- Query params:
GET /api/v1/health- Health check endpoint
# Health check
curl http://localhost:8000/api/v1/health
# Sign up
curl -X POST http://localhost:8000/api/v1/auth/signup \
-H "Content-Type: application/json" \
-d '{
"email": "test@example.com",
"password": "password123",
"name": "Test User"
}'
# Login
curl -X POST http://localhost:8000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "test@example.com",
"password": "password123"
}'
# List devices (requires JWT token)
curl http://localhost:8000/api/v1/devices \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
# Send water command (requires JWT token)
curl -X POST http://localhost:8000/api/v1/devices/esp32_001/commands/water \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"duration": 30,
"zone": 1
}'# Start infrastructure
docker compose up -d
# Start backend
cd backend
npm run dev# Build backend
cd backend
npm run build
# Run migrations
npm run migration:run
# Start production server
npm startThe project includes Docker Compose configuration for PostgreSQL and Mosquitto:
# Start services
docker compose up -d
# Stop services
docker compose down
# Stop and remove volumes
docker compose down -v- JWT Authentication - Token-based authentication for API endpoints
- Password Hashing - Bcrypt password hashing
- CORS Protection - Cross-origin resource sharing configuration
- Input Validation - Zod schema validation for all requests
- Security Headers - Helmet security middleware
- MQTT Authentication - Optional username/password authentication
- β User authentication and authorization
- β Device pairing and management
- β Real-time telemetry ingestion via MQTT
- β Telemetry history (raw and hourly aggregated)
- β Device command system with acknowledgments
- β Background workers for data aggregation
- β PostgreSQL database for data persistence
- β RESTful API with comprehensive endpoints
- β TypeScript for type safety
- β Request validation with Zod
- β Structured logging with Winston
The frontend workspace provides a modern web dashboard built with React + TypeScript + Vite + shadcn/ui:
- Authentication: Sign up, sign in, and persistent sessions using JWT.
- Device overview: Home page with a list of paired devices and high-level status.
- Device details: Per-device pages showing live status, channel modes, and auto-config.
- Channels control: Manual channel activation/deactivation and per-channel scheduling UI.
- Telemetry views: History and report pages with charts for hourly telemetry.
- User profile: Basic profile and preferences section.
By default the dashboard talks to the backend via VITE_API_BASE_URL (see Frontend Setup).
The backend uses Winston for structured logging. Logs are output to the console in development and can be configured for file output in production.
GET /api/v1/health- Server health status
# Generate migration
npm run migration:generate
# Run migrations
npm run migration:run
# Revert last migration
npm run migration:revert
# Show migration status
npm run migration:show- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Run linting and type checking in relevant workspaces (e.g.
npm run lint/npm run typecheckinsidebackend,npm run lintinsidefrontend) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
MIT License - see LICENSE file for details.
For issues and questions:
- Check the documentation in each component
- Review the example code
- Check the ESP32 README for device-specific issues
- Create an issue with detailed information
- Database integration for sensor data storage
- User authentication and authorization
- Device management and pairing
- Telemetry ingestion and history
- Command system with acknowledgments
- Data aggregation workers
- Dashboard web interface (React-based web dashboard)
- Mobile app (React Native or Flutter)
- Cloud deployment options
- Advanced analytics and visualization