Backend for a fitness/IoT tracker. Wearable/GPS devices (e.g. a Neo-6M GPS module) stream location and pulse-rate data; the backend stores it, computes distance covered and pulse-rate alerts, and manages users, groups, and events (group runs, hikes, etc.).
It is a gRPC service fronted by a gRPC-Gateway that exposes a REST/JSON API. Relational data (users/groups/events) lives in Postgres; time-series data (location/pulse) lives in InfluxDB; a small in-memory cache sits in front of Postgres reads.
GPS module reference: https://dzone.com/articles/interacting-with-a-neo-6m-gps-module-using-golang-
HTTP/JSON ──► gRPC-Gateway (start-gw) ──► gRPC server (start-server) ──► service ──► db
:8980 :8978 │
┌──────────────────┼───────────────┐
Postgres InfluxDB in-memory cache
(users/groups/events) (location/pulse) (read cache)
Each domain (user, group, event, tracker) is a vertical slice:
internal/handler → internal/service → internal/db. Proto definitions are
in proto/iot/*, generated Go in protogen/golang/iot/*.
- Go 1.23+
- Docker + Docker Compose (for the local Postgres/InfluxDB datastores)
protoc+protoc-gen-go,protoc-gen-go-grpc,protoc-gen-grpc-gateway(only needed if you regenerate protos)
# 1. Start the backing datastores (Postgres + InfluxDB).
make up
# 2. Run the gRPC server (creates tables on first run).
make server # ./path-pulse-iot-backend start-server --db-config-path internal/config/database-config.yaml
# 3. In another terminal, run the REST gateway.
make gateway # ./path-pulse-iot-backend start-gw
# 4. Hit the REST API.
curl -X POST http://127.0.0.1:8980/v1/user \
-H 'Content-Type: application/json' \
-d '{"id":{"id":1},"name":"Ada","age":36,"email":"ada@example.com","phone_no":"12345","gender":"female"}'
curl http://127.0.0.1:8980/v1/user/1
# 5. Tear down.
make downConfiguration lives in internal/config/database-config.yaml and matches
docker-compose.yaml out of the box. Postgres fields can also be overridden via
the standard PG* environment variables.
| Method | Path | Description |
|---|---|---|
| POST | /v1/user |
Create user |
| GET | /v1/user/{id} |
Get user |
| PUT | /v1/user/{id.id} |
Update user |
| DELETE | /v1/user/{id} |
Delete user |
| POST | /v1/group |
Create group |
| GET | /v1/group/{g_id} |
Get group |
| POST | /v1/group/{groupId}/user/{userId} |
Add user to group |
| POST | /v1/event |
Create event |
| GET | /v1/event/{e_id} |
Get event |
| GET | /v1/user/{id}/events |
List a user's events |
| GET | /v1/group/{g_id}/events |
List a group's events |
| POST | /v1/tracker/checkpoint |
Record a checkpoint |
| GET | /v1/tracker/location/{id} |
Get a user's latest location |
| GET | /v1/tracker/distance/checkpoint |
Distance between two checkpoints |
Streaming RPCs (UpdateLocation, UpdatePulseRate, GetRealTimeDistanceCovered)
are best consumed over gRPC directly.
make test # unit tests (no external dependencies)
make up # start datastores first...
make integration # integration tests (Postgres + InfluxDB), build tag `integration`
make smoke # end-to-end REST smoke test (server + gateway must be running)Integration tests read connection settings from PG* / INFLUX_* env vars and
default to the docker-compose datastores; they skip (rather than fail) if the
datastores aren't reachable.
See tests/documentation.md for the full testing
procedure (unit, integration, and end-to-end).
make protoc # regenerates protogen/golang/iot/* from proto/iot/*Only the project's own iot/* protos are generated. The imported google/*
protos are provided by google.golang.org/genproto and must not be emitted
locally.