This is the backend for my final year project: Design and Implementation of a Smart Remote Inventory Management System Using Computer Vision and Machine Learning. The project targets small retail businesses in Nigeria (the case study is an SME outlet in Abuja) where stock counts are still done by hand on paper or in spreadsheets, and a manager who isn't physically in the store has no way to know what's on the shelf.
Instead of barcodes or RFID tags which both need machine-readable labels on every item the system uses a phone camera. The mobile app snaps a shelf, sends the photo to OpenAI's vision API to recognise products from how they look, and posts the detected counts here. This API is everything server-side: accounts and roles, stores/warehouses, the product catalogue, inventory state and history, scan ingestion, low-stock alerts, and a simple demand model that forecasts when an item will run out.
The mobile app (including the detection step) lives in its own repo; this one is just the Go API. The backend doesn't care how detections are produced it just takes a class id, a count, and a confidence per product so the detector could be swapped for an on-device model without touching this code.
- Staff point the app at a shelf and take a photo. The app sends it to OpenAI's vision API, which returns a list of detections: a class id, a count, and a confidence score per product.
- The app POSTs that list to
/scans. Eachml_class_idmaps to an item in the catalogue, and the inventory row for that (warehouse, item) pair is updated inside a Mongo transaction, with a history record per change. - After the transaction commits, alerts are re-evaluated (low stock / out of stock, transition-based so you don't get spammed on every scan) and events go out over WebSocket and FCM push so dashboards update live.
- The predictions endpoint uses a 14-day moving average over consumption to estimate daily demand and days-to-stockout per item, which is what turns restocking from reactive into "reorder before it runs out".
- Go 1.25+
- Docker (for local MongoDB)
git clone <repo-url> srims-backend
cd srims-backend
cp .env.example .env
make up # start MongoDB (single-node replica set)
make tidy # fetch Go deps + populate go.sum
make run # start the API on :8080Collections and indexes are created automatically on startup (idempotent).
curl localhost:8080/healthz # {"status":"ok","db":"ok"}.
├── Dockerfile # multi-stage, ships the api binary
├── docker-compose.yml # local MongoDB (replica set)
├── render.yaml # Render deploy manifest (uses external Mongo Atlas)
├── Makefile # up | down | run | test | tidy | docker-build
├── .github/workflows/ci.yml # lint + test + docker-build on push/PR
├── cmd/api/ # API entrypoint (thin shell over internal/app)
├── internal/config/ # env-driven Config + Load()
└── internal/
├── app/ # Build() — wires everything; reused by main and tests
├── auth/ # JWT + bcrypt + register/login/refresh/me
├── warehouses/ # CRUD + RBAC scoping
├── items/ # CRUD + uniqueness on sku/ml_class_id
├── inventory/ # rows, history, manual adjust (txn-safe)
├── scans/ # the scan→inventory pipeline (txn-safe + alerts + ws)
├── alerts/ # transition-based alert evaluator + dismiss
├── predictions/ # 14-day moving-average demand model
├── analytics/ # KPI overview + per-item trend
├── ws/ # gorilla/websocket hub + client + auth
├── httpx/ # error envelope + sentinel errors
└── db/ # Mongo connect + collection/index bootstrap
| Group | Endpoints | Auth |
|---|---|---|
/auth |
POST register/login/refresh/logout, GET me, PUT users/:id/role (admin) | public + bearer |
/warehouses |
GET list, POST create, GET :id | bearer (RBAC) |
/items |
GET list, POST create, PUT :id, DELETE :id | bearer (RBAC) |
/inventory |
GET ?warehouse_id=, GET :id/history, PUT :id (manual adjust) | bearer |
/scans |
POST submit, GET ?warehouse_id=, GET :id | bearer |
/alerts |
GET ?active=&warehouse_id=, PUT :id/dismiss | bearer (admin/mgr) |
/predictions |
GET dashboard?warehouse_id= | bearer |
/analytics |
GET overview?warehouse_id=, GET items/:id/trend?warehouse_id=&days= | bearer |
/ws?token= |
WebSocket: subscribe/unsubscribe to warehouse events | JWT in query |
A scan submission looks like this — it's exactly what the detector on the phone produces:
POST /scans
{
"warehouse_id": "…",
"detected_items": [
{ "ml_class_id": "coke_500ml", "count": 12, "confidence": 0.91 },
{ "ml_class_id": "indomie_pack", "count": 8, "confidence": 0.85 }
]
}- Set up a free MongoDB Atlas cluster, copy the connection string.
- New Blueprint in Render, point at this repo's
render.yaml. - Set
MONGODB_URIin the dashboard (it's markedsync: false). - Deploy. Health check:
/healthz.
docker build -t srims-backend .
docker run -p 8080:8080 \
-e MONGODB_URI='mongodb+srv://...' \
-e JWT_SECRET='supersecret' \
-e ENV=production \
srims-backend| Var | Required | Default | Notes |
|---|---|---|---|
PORT |
8080 |
||
MONGODB_URI |
✓ | — | Replica-set URI (Atlas or local rs0) |
MONGO_DATABASE |
inventory |
||
JWT_SECRET |
✓ | — | ≥32 bytes outside development |
JWT_ACCESS_TTL |
15m |
||
JWT_REFRESH_TTL |
168h |
(= 7 days); refresh tokens rotate on use | |
LOG_LEVEL |
info |
trace/debug/info/warn/error | |
ENV |
development |
||
ALLOWED_ORIGINS |
— (allow all) | Comma-separated Origins for websocket upgrades | |
TRUSTED_PROXIES |
— (use RemoteAddr) | Comma-separated proxy CIDRs whose X-Forwarded-For is trusted | |
FIREBASE_CREDENTIALS_JSON |
— (pushes no-op) | FCM service-account: file path or raw JSON |
make test| Target | What it does |
|---|---|
make up |
Start MongoDB in Docker |
make down |
Stop MongoDB |
make logs |
Tail MongoDB container logs |
make run |
Run the API locally |
make test |
Run the Go test suite |
make tidy |
go mod tidy |
make mongo-shell |
Open mongosh against the local instance |
make docker-build |
Build the production Docker image |
No SQL migrations — Mongo is schemaless. On startup, internal/db.Init ensures
these collections exist with the right indexes:
| Collection | Notable indexes |
|---|---|
users |
unique email |
warehouses |
manager_id |
items |
unique sku, unique ml_class_id |
inventory |
unique compound (warehouse_id, item_id) |
inventory_transactions |
(inventory_id, created_at desc), sparse scan_id |
scans |
(warehouse_id, scanned_at desc), (user_id, scanned_at desc) |
predictions |
(item_id, predicted_at desc) |
alerts |
(status, created_at desc) |
refresh_tokens |
TTL on expires_at, user_id |
_id on every document is a UUID v4 string (set by the application, not Mongo).
Built as part of my final year project on inventory management for Nigerian SMEs, where record accuracy without automated tracking typically sits in the 60–70% range and restocking is reactive rather than planned. The full write-up covers the motivation, the computer-vision approach, and the evaluation against manual counting at the case-study outlet.