A full-stack, real-time IoT sensor monitoring dashboard that ingests telemetry over MQTT, stores time-series readings in PostgreSQL, evaluates threshold-based alerts with email notifications, and visualizes live and historical data through a glassmorphism UI with dark/light mode support.
Live Demo • Features • Quick Start • API Docs • Screenshots
- Real-time Monitoring — Live sensor cards with animated values, sparklines, and expandable Recharts via Socket.io push
- Multi-floor Dashboard — Filter sensors by floor with tabbed navigation and auto-updating counts
- Alert Engine — Automatic WARNING/CRITICAL detection based on configurable thresholds per sensor type
- Email Notifications — Nodemailer + Gmail SMTP sends emails on critical alerts
- Historical Analytics — Aggregated charts with minute/hour windows, date range picker, brush zoom, and stats summary
- Alert Management — Paginated alert list with severity/type/status filters, single/bulk acknowledge, and cleanup
- Settings Panel — Threshold configuration with live range visualizer, toggle monitoring, and system status display
- Device Management — Admin panel to register, edit, toggle, and delete IoT devices with MQTT whitelist enforcement
- Dark/Light Mode — CSS custom property-based theming with smooth transitions across all pages
- Responsive Design — Mobile-first with collapsible sidebar, bottom navigation, and adaptive layouts
- Glassmorphism UI — Backdrop blur, subtle borders, glow effects, and Framer Motion layout animations
- Role-based Access — JWT authentication with Admin/Viewer roles and route guards (Protected, Admin, Guest)
- Loading Skeletons — Page-specific skeleton patterns with shimmer animations
- Swagger API Docs — Interactive OpenAPI 3.0 documentation at
/api-docs
Default admin credentials (demo only):
Email: admin@iot-dashboard.com
Password: admin123
These credentials are for the public demo only. Production deployments must set
SEED_ADMIN_EMAILandSEED_ADMIN_PASSWORDbefore running seed.
The backend runs on Render free tier and may need ~30 seconds to cold-start on the first request. The app automatically pings
/api/healthand shows a Waking up status instead of going offline immediately.
Optional: set the repository variable
BACKEND_HEALTH_URL(e.g.https://your-api.onrender.com/api/health) to enable the scheduled keep-alive workflow and reduce cold starts.
All screenshots are captured from the live deployment running against the seeded demo dataset.
A high-level visual map of the system. Both diagrams render natively on GitHub thanks to Mermaid support.
How the core entities relate to each other and how real-time delivery fans out.
graph LR
User(("User"))
Device(["Device"])
ThresholdConfig(["ThresholdConfig"])
Alert(["Alert"])
SensorReading(["SensorReading"])
User -- "registers" --> Device
User -- "configures" --> ThresholdConfig
User -- "acknowledges" --> Alert
Device -- "generates" --> SensorReading
SensorReading -- "evaluated by" --> ThresholdConfig
ThresholdConfig -. "triggers" .-> Alert
Alert -. "emits via Socket.io" .-> User
How a single sensor reading travels through the stack.
flowchart LR
SIM["Sensor Simulator<br/>(mqtt.js)"]
BROKER["MQTT Broker<br/>(HiveMQ Cloud)"]
CONSUMER["MQTT Consumer<br/>(Node.js)"]
DB[("PostgreSQL<br/>(Neon)")]
ALERT["Alert Engine"]
EMAIL["Nodemailer<br/>(Gmail SMTP)"]
WS["Socket.io"]
CLIENT["React 19 SPA<br/>(Vite + Tailwind v4)"]
API["Express 5 API<br/>(Prisma 7 + JWT)"]
SIM -- "MQTT publish" --> BROKER
BROKER -- "MQTT subscribe" --> CONSUMER
CONSUMER --> DB
CONSUMER --> ALERT
ALERT --> DB
ALERT -- "critical" --> EMAIL
ALERT -. "alert:new" .-> WS
CONSUMER -. "sensor:data" .-> WS
WS -. "WebSocket" .-> CLIENT
CLIENT -- "Axios + JWT" --> API
API --> DB
- React 19 — Modern UI library with hooks and context for state management
- Vite 8 — Lightning-fast build tool and dev server with HMR
- TypeScript 6 — Type-safe development across all components
- Tailwind CSS v4 — Utility-first CSS framework with custom dark/light theme system
- Framer Motion — Smooth layout animations, page transitions, and micro-interactions
- Recharts 3 — Responsive charting for sparklines, sensor charts, and historical data
- Socket.io Client — Real-time sensor data and alert event streaming
- React Router v7 — Client-side routing with protected/admin/guest route guards
- Axios — HTTP client with JWT interceptors and error handling
- Lucide React — Modern icon library
- Node.js 20+ — Server-side JavaScript runtime
- Express 5 — Next-gen web framework with async error handling
- TypeScript 6 — End-to-end type safety
- Prisma 7 — Modern ORM with PostgreSQL adapter and type-safe queries
- PostgreSQL (Neon) — Serverless database for application and time-series data
- MQTT (mqtt.js) — IoT telemetry ingestion from MQTT broker (Mosquitto / HiveMQ)
- Socket.io 4 — Real-time bidirectional event streaming to React clients
- JWT + bcryptjs — Stateless authentication with password hashing
- Nodemailer — SMTP email notifications on critical alerts
- Swagger UI — Interactive OpenAPI 3.0 API documentation
- Helmet + CORS + Rate Limiting — Security middleware stack
- Node.js v20+ and npm
- Docker — for local Mosquitto MQTT broker
- PostgreSQL 16+ or a Neon account (free tier)
1. Clone the repository:
git clone https://github.com/Serkanbyx/s5.3_Iot-Dashboard.git
cd s5.3_Iot-Dashboard2. Start the MQTT broker:
cd docker
docker compose up -d
cd ..3. Set up environment variables:
cp server/.env.example server/.env
cp client/.env.example client/.envserver/.env
PORT=5000
NODE_ENV=development
DATABASE_URL=postgresql://user:password@localhost:5432/iot_dashboard
MQTT_BROKER_URL=mqtt://localhost:1883
MQTT_TOPIC_ROOT=factory
JWT_SECRET=your_secret_key_min_32_chars
JWT_EXPIRES_IN=7d
CLIENT_URL=http://localhost:5173
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your_email@gmail.com
SMTP_PASS=your_app_password
ALERT_EMAIL_FROM=IoT Dashboard <your_email@gmail.com>
ALERT_EMAIL_TO=recipient@example.comclient/.env
VITE_API_URL=http://localhost:50004. Install dependencies and set up database:
# Terminal 1 — Backend
cd server
npm install
npx prisma generate
npx prisma migrate dev --name init
npm run seed
npm run dev
# Terminal 2 — Simulator
cd server
npm run simulate
# Terminal 3 — Frontend
cd client
npm install
npm run devOpen http://localhost:5173 and log in with the development seed credentials:
Email: admin@iot-dashboard.com
Password: admin123
Local development uses these defaults automatically. Do not use them in production.
- Register / Login — Create a new account or log in with existing credentials
- Dashboard — View real-time sensor readings organized by floor with animated cards and sparklines
- Expand Charts — Click any sensor card to view a full interactive Recharts visualization
- Historical Data — Navigate to Historical page, select sensor, type, time window, and date range
- Alerts — Monitor threshold violations with severity filters, acknowledge or bulk-clear alerts
- Devices — (Admin) Register new IoT sensors, toggle active state, edit floor/type assignments
- Settings — (Admin) Configure threshold ranges per sensor type with live range visualizer
- Theme Toggle — Switch between dark, light, and system themes from the navbar
Sensor readings flow through a structured MQTT topic hierarchy:
{root}/{floor}/{sensorId}/{type}
factory/floor1/sensor01/temperature
The MQTT consumer validates incoming data against the registered device whitelist, writes to PostgreSQL, evaluates threshold rules, and fans out to connected clients via Socket.io.
Every incoming reading is evaluated against configurable thresholds:
criticalMin ← WARNING → minValue ← NORMAL → maxValue ← WARNING → criticalMax
- WARNING — value between
criticalMin–minValueormaxValue–criticalMax - CRITICAL — value below
criticalMinor abovecriticalMax
When triggered: alert is persisted → alert:new emitted via Socket.io → email sent for CRITICAL severity.
JWT-based stateless auth with role-based access:
- User logs in → server returns signed JWT token
- Axios interceptor attaches
Authorization: Bearer <token>to every request protectmiddleware verifies token and attaches user to requestadminOnlymiddleware checksrole === ADMINfor privileged routes- Client route guards (
ProtectedRoute,AdminRoute,GuestOnlyRoute) enforce navigation
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | / |
— | Welcome page with API links |
| GET | /api-docs |
— | Swagger UI documentation |
| GET | /api/health |
— | Health check and uptime |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /api/auth/register |
— | Register a new user |
| POST | /api/auth/login |
— | Login and receive JWT token |
| GET | /api/auth/me |
JWT | Get current user profile |
| PATCH | /api/auth/profile |
JWT | Update user profile |
| PATCH | /api/auth/password |
JWT | Change password |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /api/sensors/latest |
JWT | Latest reading per sensor+type |
| GET | /api/sensors/history |
JWT | Raw history for a sensor |
| GET | /api/sensors/aggregated |
JWT | Aggregated data (minute/hour) |
| GET | /api/sensors/list |
JWT | List of all known sensors |
| GET | /api/sensors/floors |
JWT | Floor overview with readings |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /api/alerts |
JWT | Paginated, filterable alert list |
| GET | /api/alerts/stats |
JWT | Alert statistics |
| PATCH | /api/alerts/:id/acknowledge |
Admin | Acknowledge a single alert |
| PATCH | /api/alerts/acknowledge-all |
Admin | Acknowledge all alerts |
| DELETE | /api/alerts/cleanup |
Admin | Delete old acknowledged alerts |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /api/thresholds |
JWT | Get all threshold configurations |
| PATCH | /api/thresholds/:sensorType |
Admin | Update threshold for a sensor type |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /api/devices |
JWT | List all registered devices |
| GET | /api/devices/:id |
JWT | Get a single device |
| POST | /api/devices |
Admin | Register a new device |
| PATCH | /api/devices/:id |
Admin | Update a device |
| DELETE | /api/devices/:id |
Admin | Delete a device |
| Event | Direction | Description |
|---|---|---|
sensor:data |
Server → Client | New sensor reading received |
alert:new |
Server → Client | New alert created |
alert:acknowledged |
Server → Client | Alert was acknowledged |
Auth endpoints require
Authorization: Bearer <token>header. Admin endpoints require theADMINrole.
A clean monorepo layout with an explicit backend / frontend split. Each panel below is collapsible — expand the one you care about.
Server — Express 5 API
server/
├── prisma/
│ ├── schema.prisma # 4 models: User, ThresholdConfig, Alert, Device
│ └── seed.ts # admin user, thresholds, 6 default devices
├── src/
│ ├── config/ # env, database, mqtt, swagger
│ ├── controllers/ # auth, sensor, alert, threshold, device
│ ├── middlewares/ # auth, errorHandler, rateLimiter, sanitize, validate
│ ├── routes/ # one file per resource group
│ ├── services/ # mqttConsumer, alertEngine, emailService, socketService
│ ├── simulator/ # sensorSimulator.ts (mqtt.js publisher)
│ ├── types/ # TypeScript interfaces
│ ├── utils/ # generateToken
│ ├── validators/ # express-validator rules per resource
│ └── index.ts # Express app + Swagger + graceful shutdown
├── tsconfig.json
├── nodemon.json
└── package.json
Client — React 19 + Vite SPA
client/
├── src/
│ ├── api/ # axios instance + service wrappers
│ ├── components/
│ │ ├── alerts/ # AlertList, AlertItem, AlertToast, FilterBar, Stats
│ │ ├── dashboard/ # SensorCard, SensorChart, SensorGrid, FloorTabs
│ │ ├── guards/ # ProtectedRoute, AdminRoute, GuestOnlyRoute
│ │ ├── historical/ # HistoricalChart, DateRangePicker, StatsSummary
│ │ ├── layout/ # MainLayout, Navbar, Sidebar, MobileNav
│ │ ├── settings/ # ThresholdCard, RangeVisualizer, SystemStatus
│ │ ├── skeletons/ # page-specific loading skeletons
│ │ └── ui/ # GlassCard, Spinner, Badge, Toggle, Pagination...
│ ├── contexts/ # AuthContext, SocketContext, ThemeContext
│ ├── hooks/ # useSocket, useDebounce, useAnimatedValue...
│ ├── pages/ # Login, Dashboard, Historical, Alerts, Devices, Settings, 404
│ ├── types/ # TypeScript interfaces
│ └── utils/ # cn (clsx helper)
├── index.html
├── vite.config.ts
├── vercel.json
└── package.json
Repository root — docs, governance & shared assets
s5.3_Iot-Dashboard/
├── client/ # → see Client panel above
├── server/ # → see Server panel above
├── docker/ # docker-compose.yml + mosquitto/mosquitto.conf
├── docs/ # build-guide.md (archived build playbook)
├── assets/ # screenshots/
├── .github/ # CONTRIBUTING, CODE_OF_CONDUCT, SECURITY, issue/PR templates
├── .gitignore
├── LICENSE # PolyForm Noncommercial 1.0.0
└── README.md
- Helmet — Security HTTP headers on every response
- CORS — Strict origin whitelist (only
CLIENT_URLallowed) - Rate Limiting — Global limiter + stricter auth limiter + API limiter
- Input Sanitization — express-mongo-sanitize strips
$and.operators - Input Validation — express-validator rules on every mutation endpoint
- Password Hashing — bcryptjs with salt rounds
- Production Admin —
SEED_ADMIN_EMAIL/SEED_ADMIN_PASSWORDrequired for production seed; server refuses to start if the default demo password is still in use - JWT Authentication — Stateless tokens with configurable expiry
- Role-based Authorization — Admin-only routes for settings, devices, and alert management
- Device Whitelist — MQTT consumer only processes data from registered active devices
- No Stack Traces — Error handler strips internals in production mode
Every service uses permanent free tiers — no credit card required, no expiration.
| Service | Platform | Cost |
|---|---|---|
| Backend API | Render | $0/month |
| Frontend | Vercel | $0/month |
| Database | Neon | $0/month |
| MQTT Broker | HiveMQ Cloud | $0/month |
| Gmail SMTP (App Password) | $0/month | |
| Total | $0/month |
- Connect your GitHub repository
- Set root directory to
server - Build command:
npm install && npx prisma generate && npm run db:deploy && npm run build && npm run seed - Start command:
npm start - Add all environment variables from
.env.example - Required in production: set
NODE_ENV=production,ALLOW_REGISTRATION=false, and strongSEED_ADMIN_EMAIL/SEED_ADMIN_PASSWORDvalues before seed runs
- Import the repository
- Set root directory to
client - Framework preset: Vite
- Add
VITE_API_URLpointing to your Render backend URL
- Create a free project at neon.tech
- Copy the pooled connection string to
DATABASE_URL - Run
npm run db:deployfrom the server directory (ornpx prisma migrate devfor local development)
- Create a free cluster at hivemq.com
- Set
MQTT_BROKER_URLtomqtts://<cluster>.hivemq.cloud:8883 - Create credentials and set
MQTT_USERNAME/MQTT_PASSWORD
After deploying both services, set
CLIENT_URLon Render to your Vercel URL and redeploy.
| Variable | Description | Default |
|---|---|---|
PORT |
Server port | 5000 |
NODE_ENV |
Environment | development |
DATABASE_URL |
PostgreSQL connection string | — |
MQTT_BROKER_URL |
MQTT broker URL | mqtt://localhost:1883 |
MQTT_USERNAME |
MQTT username (optional) | — |
MQTT_PASSWORD |
MQTT password (optional) | — |
MQTT_TOPIC_ROOT |
Base MQTT topic | factory |
JWT_SECRET |
JWT signing secret (min 32 chars) | — |
JWT_EXPIRES_IN |
Token expiry duration | 7d |
CLIENT_URL |
CORS allowed origin | http://localhost:5173 |
ALLOW_REGISTRATION |
Allow public sign-up | true (defaults to false in production) |
SEED_ADMIN_EMAIL |
Admin email for npm run seed |
dev: admin@iot-dashboard.com |
SEED_ADMIN_PASSWORD |
Admin password for seed | dev: admin123; required in production |
SEED_ADMIN_NAME |
Admin display name | Admin |
SMTP_HOST |
SMTP server host | smtp.gmail.com |
SMTP_PORT |
SMTP server port | 587 |
SMTP_USER |
SMTP email address | — |
SMTP_PASS |
SMTP password / app password | — |
ALERT_EMAIL_FROM |
Sender display name | — |
ALERT_EMAIL_TO |
Alert recipient email | — |
| Variable | Description | Default |
|---|---|---|
VITE_API_URL |
Backend API base URL | (empty = same origin) |
Contributions are welcome! Please read our Contributing Guidelines and Code of Conduct first.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes following semantic format:
| Prefix | Description |
|---|---|
feat: |
New feature |
fix: |
Bug fix |
refactor: |
Code refactoring |
docs: |
Documentation changes |
chore: |
Maintenance and dependency updates |
- Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the PolyForm Noncommercial License 1.0.0 — see the LICENSE file for details.
Serkanby
- 🌐 Website: serkanbayraktar.com
- 🐙 GitHub: @Serkanbyx
- 📧 Email: serkanbyx1@gmail.com
- React — UI library
- Express — Web framework
- Prisma — Database ORM
- Recharts — Charting library
- Framer Motion — Animation library
- Socket.io — Real-time engine
- Tailwind CSS — Utility-first CSS
- MQTT.js — MQTT client
- Neon — Serverless PostgreSQL
- HiveMQ — Cloud MQTT broker
- Open an Issue
- Email: serkanbyx1@gmail.com
- Website: serkanbayraktar.com
⭐ If you like this project, don't forget to give it a star!





