Production-grade Axum + SQLx backend for fuel-station operations, wallet-style card balances, commission workflows, and loyalty-aware transaction processing.
storm-api is a modular Rust REST API built on Axum, SQLx, PostgreSQL, and Redis. It is designed for operational reliability: structured logs, explicit health checks, graceful shutdown, OpenAPI-driven documentation, and a test layout that spans unit, integration, and end-to-end scenarios.
- Dual JWT auth flows with Argon2id password hashing.
- JWT revocation via Redis blocklist.
- Idempotency-key support for authenticated mutating endpoints via
X-Idempotency-Key.
- NFC card registry with PIN-protected balance checks.
- Customer profiles with card linkage and category assignment.
- Agent account management with transaction history.
- Agent-led customer registration with NFC card assignment.
- Cash withdrawal with PIN verification, commission split, and house account tracking.
- Configurable commission tier history with L-1 and L-2 MLM bonuses.
- Fuel consumption logging with pricing per type.
- 2-level MLM bonus via PostgreSQL trigger on consumption.
- Request counter, tracing, gzip, 30s timeout, CORS, JWT auth, idempotency replay.
- Health probes:
/health(liveness),/ready(readiness),/metrics(counter). - Graceful shutdown on
SIGTERMwith 5s drain window. - Structured JSON logging via
tracing.
- OpenAPI 3.0 with Swagger UI at
/api/v1/docs. - Three-tier tests (unit, integration, e2e) with ≥80% coverage enforced.
- GitHub Actions CI pipeline.
Prerequisites: Rust toolchain, Docker, sqlx-cli.
# Install sqlx-cli for database migrations
cargo install sqlx-cli --no-default-features --features rustls,postgres
# Clone the repository and set up environment
git clone https://github.com/ardinbig/storm-api.git && cd storm-api
cp .env.example .env
# Start PostgreSQL and Redis services
docker compose up -d database redis
# Run database migrations
sqlx migrate run --source migrations
# Build and run the API server
cargo run- Local API:
http://127.0.0.1:3000 - Swagger UI:
http://127.0.0.1:3000/api/v1/docs - OpenAPI JSON:
http://127.0.0.1:3000/api-doc/openapi.json
storm-api/
├── src/
│ ├── app.rs # Router + middleware assembly
│ ├── main.rs # Entry point, graceful shutdown
│ ├── routes/ # HTTP topology per domain
│ ├── handlers/ # Request extraction → service call → response
│ ├── services/ # Business logic + SQLx queries
│ ├── middleware/ # Auth, request counter, idempotency
│ ├── models/ # DTOs and database row types
│ ├── state/ # AppState (pool, Redis, JWT config, counters)
│ ├── errors/ # Unified AppError → JSON response
│ └── utils/ # Password hashing, Redis cache helpers
├── migrations/
├── tests/ # unit / integration / e2e
├── compose.yml
└── Dockerfile
┌────────────────────────────────────────────┐
CLIENT
└────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────┐
AXUM ROUTER
└────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────┐
MIDDLEWARE STACK
1. Request Counter — feeds /metrics
2. Tracing — structured JSON logs
3. Compression — gzip
4. Timeout — 408 after 30s
5. CORS — cross-origin policy
6. Auth — JWT → CurrentUser
(protected routes)
7. Idempotency — Redis-backed replay/cache
(protected mutating routes)
└────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────┐
HANDLER
extract → delegate → shape response
└────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────┐
SERVICE
Business logic · SQLx queries · &PgPool
└────────────────────────────────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌────────────────────┐
PostgreSQL Redis
(SQLx + PgPool) JWT blocklist +
idempotency
└─────────────────┘ └────────────────────┘
| Variable | Description | Default |
|---|---|---|
DATABASE_URL |
PostgreSQL connection string. | postgres://postgres:postgres@localhost/stormdb |
REDIS_URL |
Redis URL. Optional - app degrades gracefully if absent. | redis://127.0.0.1:6379 |
JWT_SECRET |
HMAC secret. Change in production. | dev-secret-change-in-production |
JWT_EXPIRY_HOURS |
Token validity period in hours. | 24 |
APP_ADDR |
Bind address. | 127.0.0.1:3000 |
RUST_LOG |
Log filter. | storm_api=debug,tower_http=debug |
MAX_DB_CONNECTIONS |
SQLx pool size. | 10 |
Authenticated mutating routes (POST, PUT, PATCH, DELETE under the protected router, including POST /api/v1/auth/logout) accept an optional X-Idempotency-Key header.
- Missing header: request proceeds normally.
- Malformed header, repeated header, or comma-joined value:
400 Bad Request. - Duplicate in-flight request for the same authenticated user + key:
409 Conflict, body{ "error": "processing", "code": 409 }, headerRetry-After: 1. - Successful cacheable
2xxresponse: replay-cached in Redis for 24 hours. 4xx/5xxdownstream response: not cached; the lock is released immediately.- Streamed responses or oversized bodies: not cached.
- Redis unavailable while an idempotency key is supplied:
503 Service Unavailable.
Idempotency scope is per authenticated user and key, not global across all callers.
# Format code according to Rust conventions
cargo fmt --all
# Lint check for code quality and best practices
cargo clippy --all-targets --all-features -- -D warnings
# Run all tests (unit, integration, e2e)
cargo test --locked
# Build and start all services with Docker
docker compose up --build | Endpoint | Description | Response |
|---|---|---|
GET /health |
Liveness | 200 OK |
GET /ready |
Readiness - 503 after shutdown signal |
200 ready / 503 not ready |
GET /metrics |
Request counter | { "requests": N } |
Graceful shutdown listens for SIGTERM / Ctrl+C, flips the readiness flag to false, then waits 5s for in-flight requests to drain.
| Layer | Technology (crates) |
|---|---|
| Language | Rust 1.97+ |
| Web framework | Axum 0.8 + Tower + tower-http |
| Async runtime | Tokio |
| Database | PostgreSQL via SQLx |
| Cache / JWT blocklist | Redis via redis-rs (optional - graceful degradation) |
| Authentication | JWT via jsonwebtoken, Argon2id via argon2 |
| Serialization | serde + serde_json + uuid + chrono |
| Error handling | thiserror |
| Observability | tracing + tracing-subscriber (JSON, env-filter) |
| API docs | utoipa + utoipa-swagger-ui |
| Testing | sqlx::test, mockall, testcontainers, reqwest |
| Containerization | Docker (multi-stage), Docker Compose |
| CI | GitHub Actions |