A complete rewrite of the OneBusAway (OBA) REST API server in Golang.
- Install Go 1.24.2 or later.
- Copy
config.example.jsontoconfig.jsonand fill in the required values. - Run
make runto build and start the server. - Open your browser and navigate to
http://localhost:4000/healthzto verify the server works.
Docker provides a consistent development environment across all platforms.
Quick Start:
# Create docker config from template
cp config.docker.example.json config.docker.json
# Edit config.docker.json with your settings
# Build and run with Docker Compose (recommended)
# Uses config.docker.json which stores data in /app/data/ for persistence
docker-compose up
# Or build and run manually
docker build -t maglev .
docker run -p 4000:4000 -v $(pwd)/config.docker.json:/app/config.json:ro -v maglev-data:/app/data maglev
Verify it works:
curl http://localhost:4000/healthz
Development with live reload:
docker-compose -f docker-compose.dev.yml up
See the Docker section below for more details.
Maglev supports two ways to configure the server: command-line flags or a JSON configuration file.
Run the server with command-line flags:
./bin/maglev -port 8080 -env production -api-keys "key1,key2" -rate-limit 50
Alternatively, use a JSON configuration file with the -f flag:
./bin/maglev -f config.json
An example configuration file is provided as config.example.json. You can copy and modify it:
cp config.example.json config.json
# Edit config.json with your settings
./bin/maglev -f config.json
Example config.json:
{
"port": 8080,
"env": "production",
"api-keys": ["key1", "key2", "key3"],
"rate-limit": 50,
"log-level": "info",
"log-format": "json",
"gtfs-static-feed": {
"url": "https://example.com/gtfs.zip",
"auth-header-name": "Authorization",
"auth-header-value": "Bearer token456",
"enable-gtfs-tidy": true
},
"gtfs-rt-feeds": [
{
"id": "agency-a",
"agency-ids": ["40"],
"trip-updates-url": "https://api.example.com/agency-a/trip-updates.pb",
"vehicle-positions-url": "https://api.example.com/agency-a/vehicle-positions.pb",
"service-alerts-url": "https://api.example.com/agency-a/service-alerts.pb",
"headers": { "Authorization": "Bearer token123" },
"refresh-interval": 30,
"enabled": true
},
{
"id": "agency-b",
"agency-ids": ["1"],
"trip-updates-url": "https://api.example.com/agency-b/trip-updates.pb",
"vehicle-positions-url": "https://api.example.com/agency-b/vehicle-positions.pb",
"refresh-interval": 60
}
],
"data-path": "/data/gtfs.db"
}Note: The -f flag is mutually exclusive with other command-line flags. If you use -f, all other configuration flags will be ignored. The system will error if you try to use both.
Dump Current Configuration:
./bin/maglev --dump-config > my-config.json
# or with other flags
./bin/maglev -port 8080 -env production --dump-config > config.json
JSON Schema & IDE Integration:
A JSON schema file is provided at config.schema.json for IDE autocomplete and validation. To enable IDE validation, add $schema to your config file:
{
"$schema": "./config.schema.json",
"port": 4000,
"env": "development",
...
}
| Option | Type | Default | Description |
|---|---|---|---|
port |
integer | 4000 | API server port |
env |
string | "development" | Environment (development, test, production) |
api-keys |
array | ["test"] | API keys for authentication |
log-level |
string | "info" | Log level (debug, info, warn, error) |
log-format |
string | "text" | Log format (text, json) |
rate-limit |
integer | 100 | Requests per second per API key |
gtfs-static-feed |
object | (Sound Transit) | Static GTFS feed configuration |
gtfs-rt-feeds |
array | (Sound Transit) | GTFS-RT feed configurations (see below) |
data-path |
string | "./gtfs.db" | Path to SQLite database |
Each entry in the gtfs-rt-feeds array supports:
| Field | Type | Default | Description |
|---|---|---|---|
id |
string | auto ("feed-0", "feed-1", …) |
Unique identifier for the feed, used in logs and internal data partitioning |
agency-ids |
array | [] |
When set, only realtime data (trips, vehicles, alerts) belonging to the listed agency IDs is included. Data for other agencies in the same feed is filtered out. Agencies are resolved via route→agency mapping from the static GTFS data. |
trip-updates-url |
string | "" |
URL for GTFS-RT trip updates protobuf |
vehicle-positions-url |
string | "" |
URL for GTFS-RT vehicle positions protobuf |
service-alerts-url |
string | "" |
URL for GTFS-RT service alerts protobuf |
headers |
object | {} |
HTTP headers sent with every request to this feed |
refresh-interval |
integer | 30 |
Polling interval in seconds |
enabled |
boolean | true |
Set to false to disable polling without removing the entry |
A feed must have at least one URL (trip-updates-url, vehicle-positions-url, or service-alerts-url) to be activated. Each feed runs its own independent polling goroutine. Data from all enabled feeds is merged into a single unified view for the API.
All basic commands are managed by our Makefile:
make run- Build and run the app with a fake API key:test.make build- Build the app.make clean- Delete all build and coverage artifacts.make coverage- Test and generate HTML coverage artifacts.make test- Run tests.make models- Generate Go code from SQL queries using sqlc.make watch- Build and run the app with Air for live reloading.
The server uses github.com/mattn/go-sqlite3 and SQLite FTS5 for route search. Build and test with the FTS5 tag enabled:
CGO_ENABLED=1 go test -tags "sqlite_fts5" ./...
# or
CGO_ENABLED=1 go build -tags "sqlite_fts5" ./...
Ensure you have a working C toolchain when CGO is enabled.
Maglev uses SQLite and supports two different drivers via Go build tags to balance production performance with developer experience:
-
Fast Mode (Default): Uses
github.com/mattn/go-sqlite3(CGo). This is the default for production because of its high performance and support for advanced SQLite features like FTS5 (Full-Text Search). It requires a C compiler (GCC/Clang) installed on your system.- Run tests:
make test - Build:
make build
- Run tests:
-
Compatible Mode: Uses
modernc.org/sqlite(Pure Go). This mode is intended for local development and CI on platforms where CGo is difficult to configure (like Windows). It does not require a C compiler.- Run tests:
make test-pure - Build:
make build-pure
- Run tests:
bin: Compiled application binaries.cmd/api: Application-specific code (server, HTTP handling, auth).internal: Ancillary packages (database, validation, etc.). Code here is reusable and imported bycmd/api.migrations: SQL migration files.remote: Production server configuration and setup scripts.go.mod: Project dependencies and module path.Makefile: Automation for building, testing, and migrations.
# Install Delve
go install github.com/go-delve/delve/cmd/dlv@latest
# Build the app
make build
# Start the debugger
dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec ./bin/maglevThis allows debugging in the GoLand IDE.
Maglev includes built-in Go pprof endpoints for debugging memory leaks and CPU bottlenecks. For security reasons, these are completely disabled by default and are never exposed on the public API port.
To enable the profiling server, set the following environment variable:
MAGLEV_ENABLE_PPROF=1When enabled, the debug server will start strictly on the local loopback interface at 127.0.0.1:6060.
Accessing in Production:
To securely access the profiles on a remote production server, do not open the port to the internet. Instead, use an SSH tunnel:
ssh -L 6060:localhost:6060 your-user@production-serverYou can then view the profiles locally in your browser at http://localhost:6060/debug/pprof/.
We use sqlc with SQLite to generate a data access layer. Use make models to regenerate files.
gtfsdb/models.go: Autogenerated by sqlc.gtfsdb/query.sql: All SQL queries.gtfsdb/query.sql.go: SQL turned into Go code.gtfsdb/schema.sql: Database schema.gtfsdb/sqlc.yml: sqlc configuration.
Docker support provides a consistent environment and simplified deployment.
- Docker 20.10 or later.
- Docker Compose v2.0 or later.
# Build the production image
docker build -t maglev .
# Or use make
make docker-build
Note: Ensure you have created config.docker.json from the template.
Using Docker directly:
# Run the container (mount your config file)
docker run -p 4000:4000 -v $(pwd)/config.docker.json:/app/config.json:ro maglev
# Or use make
make docker-run
Using Docker Compose (recommended for production):
# Start the service
docker-compose up -d
# View logs
docker-compose logs -f
# Stop the service
docker-compose down
For development with live reload:
# Start development environment with Air live reload
docker-compose -f docker-compose.dev.yml up
# Or use make
make docker-compose-dev
| Command | Description |
|---|---|
make docker-build |
Build the Docker image |
make docker-run |
Build and run the container |
make docker-stop |
Stop and remove the container |
make docker-compose-up |
Start with Docker Compose |
make docker-compose-down |
Stop Docker Compose services |
make docker-compose-dev |
Start development environment |
make docker-clean |
Remove all Docker artifacts |
The SQLite database is persisted using Docker volumes:
- Production:
maglev-datavolume mounted at/app/data. - Development:
maglev-dev-datavolume.
The GTFS database is stored in /app/data/gtfs.db within the container.
# Note: 'maglev' is the default container name when using docker-compose
docker cp maglev:/app/data/gtfs.db ./gtfs-backup.db
Once copied, you can inspect it with any SQLite client:
sqlite3 gtfs-backup.db "SELECT name FROM sqlite_master WHERE type='table';"
docker-compose exec maglev ls -lh /app/data/
docker-compose exec maglev sqlite3 /app/data/gtfs.db
SQLite CLI commands:
.tables
.schema stops
.quit
SQL queries:
-- Count records in a table
SELECT COUNT(*) FROM stops;
-- View sample data
SELECT * FROM stops LIMIT 5;
Verify database integrity:
docker-compose exec maglev sqlite3 /app/data/gtfs.db "PRAGMA integrity_check;"
Check database size:
docker-compose exec maglev du -h /app/data/gtfs.db
View recent database modifications:
docker-compose exec maglev stat /app/data/gtfs.db
The container includes a health check that verifies the API is responding:
# Check container health status
docker inspect --format='{{.State.Health.Status}}' maglev
Important: The health checks use the HEALTH_CHECK_KEY environment variable (defaults to test). If you change your API keys in the configuration, update this environment variable to match:
# In docker-compose.yml or docker-compose.dev.yml
environment:
- HEALTH_CHECK_KEY=your-api-key| Variable | Description | Default |
|---|---|---|
TZ |
Timezone for the container | UTC |
HEALTH_CHECK_KEY |
API key used for health check endpoint | test |
Container fails to start:
# Check logs
docker-compose logs maglev
# Verify config file exists
ls -la config.docker.json
Health check failing:
# Test the endpoint manually
curl http://localhost:4000/healthz
Permission issues:
- The container runs as non-root user (maglev:1000).
- Ensure mounted volumes are accessible.
