A simplified energy monitoring backend and dashboard for enterprise customers. Organizations have users and energy meters. Meters send index (reading) data; the system computes consumption from successive readings.
- Backend: Express + routing-controllers + Prisma + PostgreSQL + TypeScript
- Frontend: TanStack React Start + shadcn/ui + Tailwind CSS
- Validation: Zod schemas on all endpoints
- Auth: JWT-based with role-based access (ADMIN / USER)
- API Docs: Swagger UI auto-generated from controllers
- Infra: Docker Compose (Postgres + Backend + Frontend)
chmod +x run.sh
./run.shrun.sh will:
- Auto-create
.envfiles from.env.examplefor both backend and frontend (if they don't exist) - Build and start all services via
docker compose up --build - Postgres initializes with the configured database
- Backend runs Prisma migrations, seeds the default admin user, then starts the Express server
- Frontend builds the TanStack Start app and serves it with Vite preview
| Service | URL |
|---|---|
| Frontend | http://localhost:3000 |
| Swagger UI | http://localhost:4000/docs |
| OpenAPI JSON | http://localhost:4000/docs.json |
Default admin: admin@example.com / admin123
Tip: After logging in, click Admin Panel → 🌱 Seed Test Data to populate sample organizations, users, meters, and 24h of readings.
# Backend
cd backend
cp .env.example .env # set DATABASE_URL
npm install
npx prisma migrate deploy
npm run prisma:seed
npm run dev
# Frontend (separate terminal)
cd frontend
npm install
npm run dev| Path | Access | Description |
|---|---|---|
/login |
Public | Login page |
/dashboard |
Authenticated | Consumption report with org summary cards, meter filter, readings table |
/admin |
Admin only | Full CRUD for organizations, users, meters, and meter assignments |
Interactive Swagger UI is available at http://localhost:4000/docs.
All endpoints return JSON. Auth endpoints require Authorization: Bearer <token> header.
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /auth/login |
— | Login, returns JWT token |
Body: { "email": "string", "password": "string" }
| Method | Path | Description |
|---|---|---|
| GET | /organizations |
List all organizations |
| POST | /organizations |
Create organization |
| DELETE | /organizations/:id |
Delete organization |
| Method | Path | Description |
|---|---|---|
| GET | /users |
List all users |
| POST | /users |
Create user |
| DELETE | /users/:id |
Delete user |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /meters |
Authenticated | List meters (scoped by role) |
| POST | /meters |
Admin | Create meter |
| DELETE | /meters/:id |
Admin | Delete meter |
| Method | Path | Description |
|---|---|---|
| POST | /meter-readings |
Submit a meter reading |
Body: { "meterId": "uuid", "timestamp": "ISO 8601", "index_kwh": number }
- Computes consumption from the difference between current and previous index
- Rejects readings where
index_kwh< previous reading - Rate-limited: 100 requests/minute
| Method | Path | Description |
|---|---|---|
| GET | /report?meterIds=id1,id2 |
Consumption per meter/org (last 24h) |
- Users see only their organization's data
- Admins see all organizations
- Filter by meter IDs via query param
| Method | Path | Description |
|---|---|---|
| GET | /meter-assignments |
List all assignments |
| POST | /meter-assignments |
Assign meter to user |
| DELETE | /meter-assignments/:meterId/:userId |
Remove assignment |
When a user has meter assignments, they can only see/interact with those specific meters.
| Method | Path | Description |
|---|---|---|
| POST | /seed-test-data |
Seed sample orgs, users, meters, and 24h of readings |
All errors return structured responses:
{
"error": "Human-readable message",
"code": "ERROR_CODE"
}Error codes are defined in backend/src/errors.ts.
- ✅ Rate limiting on
/meter-readings(100 req/min) - ✅ Meter-level access control: Admins assign specific meters to users via
/meter-assignments; users with assignments only see those meters - ✅ Unit tests:
cd backend && npm test(10 tests) - ✅ Swagger / OpenAPI: Auto-generated at
/docs - ✅ Admin Panel UI: Full CRUD for all resources + seed test data button
- UUIDs used for all primary keys
- Passwords hashed with bcrypt (10 rounds)
- Tanstack starter with Shadcn for components
- JWT tokens expire after 24 hours
- Cascade deletes: deleting an org removes its users, meters, and readings
- The frontend is a simple SPA dashboard — no fancy design, focused on structure and clarity
vite previewis used to serve the frontend in Docker (suitable for demo; I would use a proper server in production)- Zod schemas validate all request bodies; structured error codes for every failure case
- If no user is assigned to a meter, all users in the organization have access to that meter
├── backend/
│ ├── src/
│ │ ├── controllers/ # REST endpoints (7 controllers)
│ │ ├── middlewares/ # Auth checkers
│ │ ├── types/ # TypeScript interfaces
│ │ ├── __tests__/ # Unit tests
│ │ ├── errors.ts # Error codes & AppException
│ │ ├── schemas.ts # Zod validation schemas
│ │ ├── prisma.ts # Prisma client singleton
│ │ └── index.ts # Express bootstrap + Swagger
│ ├── prisma/
│ │ ├── schema.prisma # Data model
│ │ ├── migrations/ # SQL migrations
│ │ └── seed.ts # Default admin seed
│ └── Dockerfile
├── frontend/
│ ├── src/
│ │ ├── routes/ # TanStack file-based routes (login, dashboard, admin)
│ │ ├── components/ui/ # shadcn components
│ │ └── utils/ # API client & auth helpers
│ └── Dockerfile
├── docker-compose.yml
├── run.sh
└── README.md