Reflex is a delivery coordination app for small Kenyan retailers—electronics shops, pharmacies, and hardware stores—that currently manage dispatch through calls and WhatsApp. It creates a single record of each delivery, assigns a rider, and keeps retailers, dispatchers, and riders in sync as the order moves from request to delivery.
┌───────────────────────────┐
│ React Frontend │
│ │
│ Retailer | Dispatcher │
│ Rider │
└─────────────┬─────────────┘
│ REST API
▼
┌───────────────────────────┐
│ Node + Express │
│ Delivery / assignment │
│ status validation / auth │
└─────────────┬─────────────┘
│
▼
┌───────────────────────────┐
│ PostgreSQL │
│ Users / deliveries / │
│ status history │
└───────────────────────────┘
▲
│ Socket.IO
▼
Real-time updates
- React + Vite, React Router, Axios, and Socket.IO client
- Node.js + Express REST API with centralized error handling
- PostgreSQL accessed through
pg, using foreign keys and transactional changes - JWT authentication and bcrypt password hashing
- Socket.IO broadcasts after database commits; a 15-second browser fallback refresh is used if a socket is unavailable
The backend, not the UI, enforces the lifecycle:
REQUESTED → ASSIGNED → PICKED_UP → DELIVERED
└──────────────→ CANCELLED
Only a retailer can create their own delivery. Only a dispatcher can assign an unassigned REQUESTED delivery to a user whose server-side role is RIDER. Only the assigned rider can progress it from ASSIGNED to PICKED_UP, then DELIVERED. Every legitimate state change creates a row in delivery_status_history. Duplicate or invalid transitions return 409 Conflict; access violations return 403 Forbidden.
The schema is in database/migrations/001_initial.sql. It creates users, deliveries, and delivery_status_history; delivery foreign keys link retailers and riders to users.
| Method | Endpoint | Access | Purpose |
|---|---|---|---|
| POST | /api/auth/register |
Public | Register a role-based account |
| POST | /api/auth/login |
Public | Receive JWT and public user data |
| GET | /api/auth/me |
Authenticated | Load session user |
| POST | /api/deliveries |
Retailer | Create a delivery |
| GET | /api/deliveries |
Authenticated | Role-scoped delivery list |
| GET | /api/deliveries/:id |
Authorized viewer | Detail and status history |
| PATCH | /api/deliveries/:id/assign |
Dispatcher | Assign a rider |
| PATCH | /api/deliveries/:id/status |
Assigned rider | Set PICKED_UP or DELIVERED |
| GET | /api/users/riders |
Dispatcher | Selectable riders |
All protected endpoints require Authorization: Bearer <JWT> and use JSON request/response payloads.
Create a database named reflex, then copy .env.example to .env and set a real DATABASE_URL and long JWT_SECRET.
createdb reflex
cp .env.example .envOn Windows PowerShell, use Copy-Item .env.example .env instead.
npm run install:all
npm run migrate
npm run seednpm run devOpen http://localhost:5173. The API is served at http://localhost:4000.
To start the services separately, use these commands from the project root:
# Backend API
npm run dev --prefix server
# Frontend
npm run dev --prefix clientIf Vite reports that port 5173 is occupied, it will choose the next available port; use the URL printed in the terminal. Ensure CLIENT_URL in .env matches that frontend origin, or use one of the supported local Vite ports (5173/5174).
Every seeded account uses password ReflexDemo123!.
| Role | |
|---|---|
| Retailer — Jane Wanjiku | jane@reflex.demo |
| Dispatcher — David Mwangi | david@reflex.demo |
| Rider — Brian Kamau | brian@reflex.demo |
| Rider — Kevin Otieno | kevin@reflex.demo |
| Rider — Mary Njeri | mary@reflex.demo |
The seed creates deliveries covering REQUESTED, ASSIGNED, PICKED_UP, and DELIVERED. Rerunning it is safe; it does not duplicate demo deliveries.
npm testThe included tests verify the delivery state machine, including every normal transition and rejected delivered/duplicate transitions. The server also enforces rider ownership, role restrictions, rider-role assignment validation, duplicate assignment prevention, and missing-record handling at the API boundary.
Each delivery has a cryptographically generated qr_token at the database layer, ready for a scanner endpoint that verifies both the token and assigned rider before confirmation. The visual scanner is intentionally deferred to keep the core create → assign → pick up → deliver workflow stable.
Future additions: rider QR scan UI, proof-of-delivery photos/signatures, retailer team accounts, dispatch reassignment rules, delivery notifications, analytics, and production deployment configuration.
This is a single-organization MVP: dispatchers can view all deliveries and there is no retailer organization table yet. Socket rooms are role/user scoped, while PostgreSQL remains the source of truth. Public self-registration supports all three demo roles; a production version should restrict dispatcher and rider invitation/registration.