Kei — Architecture of a Motorcycle GPS Tracker

Feb 20, 2026 · 11 min read

ArchitectureMQTTElectricSQLElysiaReal-TimeGPS

I built a motorcycle GPS tracking SaaS platform called Kei. The core requirement: receive location data from existing GPS devices in real-time, display historical routes on a map, and let users send commands back to their hardware. Here's the full architecture and every decision behind it.

The Problem

Existing GPS tracking platforms are either expensive, ugly, or locked to specific hardware vendors. Users already own GPS devices — they just need a dashboard that works with any device. Kei is the SaaS layer that sits on top of any GPS hardware, providing real-time tracking, history, and device control.

Data Ingestion: Flespi + MQTT

The hardest part of any GPS platform is device compatibility. There are hundreds of GPS device manufacturers, each with their own protocol. Flespi solves this by normalizing signals from all major manufacturers into a single format.

The architecture splits the data flow into two paths:

Real-time location updates use MQTT, a pub/sub protocol designed for lightweight telemetry. Each device publishes its position to a MQTT topic on Flespi's broker. Our backend subscribes to relevant topics and receives position data as it arrives.

Device commands (like "stop engine" or "set geofence") use Flespi's REST API. The backend sends HTTP requests to Flespi, which forwards the command to the device over its native protocol.

The MQTT message format looks like this:

I subscribe to the device-specific topic pattern using the MQTT.js library:

Every incoming message triggers an upsert: the latest position updates the device's current state, and the raw data point is appended to the history table.

Backend: Elysia.js

The API layer runs on Elysia.js, a Bun-native web framework with built-in TypeScript support and excellent performance characteristics.

The command endpoint is the most interesting. Different devices expect different command formats. Flespi abstracts this, but you still need to specify the command type and parameters:

Database: Local-First with ElectricSQL

This is the most unconventional choice. Instead of a direct Postgres connection from the frontend, I used ElectricSQL — a local-first sync engine that gives every client its own SQLite database and syncs changes via Postgres' logical replication.

The schema has three main tables:

ElectricSQL syncs these tables to the frontend's local SQLite via the Electric sync service. The frontend reads and writes to SQLite, and Electric propagates changes to Postgres (and vice versa).

The key benefit: the map works offline. If the user opens the app in an area with spotty connectivity, they can still browse recent routes from the local cache. When connectivity returns, Electric syncs any pending changes.

Docker Compose

The entire infrastructure runs locally via Docker:

Postgres runs with wal_level=logical which enables Change Data Capture — this is what ElectricSQL uses to detect changes and sync them to clients. The tmpfs data directory means the database resets on restart, which is fine for development.

Map Rendering: MapLibre GL

I chose MapLibre GL JS over Google Maps for two reasons: cost and styling. Google Maps charges per tile load, which adds up fast for a real-time tracking app. MapLibre renders vector tiles client-side and lets you customize every visual aspect.

The real-time view subscribes to a WebSocket endpoint that broadcasts position updates:

The device markers rotate to match the heading, and speed is encoded as marker size and color — green for stopped, yellow for moving, red for highway speed.

For historical routes, I query the positions table with a date range and render the result as a GeoJSON LineString:

Auth

Authentication uses JWT stored in HTTP-only cookies. The flow is standard — register, login, receive a signed token. The token includes the user ID and device list, so every API call can verify ownership before returning data.

Lessons

MQTT is vastly underrated for this use case. HTTP polling would miss updates between intervals. WebSockets require maintaining persistent connections from the server to each client. MQTT's pub/sub model with broker-level persistence handles disconnections, reconnections, and fan-out naturally.

Local-first changes the user experience. Most GPS apps show a loading spinner when connectivity is poor. With ElectricSQL, the map renders instantly from the local cache and syncs in the background. Users notice the difference immediately.

Flespi saved months of work. Writing custom parsers for even 5 GPS device protocols would have taken weeks. Flespi supports hundreds out of the box, including obscure Chinese brands. The tradeoff is vendor lock-in, but for an early-stage SaaS, it's the right bet.

Elysia.js is fast enough for real-time. Bun's runtime combined with Elysia's lightweight router handles hundreds of concurrent MQTT messages and WebSocket connections on a single $10 VPS. No Kubernetes, no auto-scaling, no complexity.