axum-conf 0.3.16

A library to simplify the use of Axum, Tokio and Postgres together using configuration. It comes with batteries included and many features can be feature activated.
Documentation

axum-conf

codecov Crates.io Documentation License: MIT

Production-ready web services with Axum — batteries included.

Build Kubernetes-native Rust services without the boilerplate. axum-conf gives you health probes, metrics, security headers, rate limiting, and more — all configured through simple TOML.

                              axum-conf
    ┌─────────────────────────────────────────────────────────┐
    │                                                         │
    │  ┌─────────────┐   ┌──────────────┐   ┌─────────────┐   │
    │  │   Config    │──▶│ FluentRouter │──▶│  Middleware │   │
    │  │   (TOML)    │   │   Builder    │   │    Stack    │   │
    │  └─────────────┘   └──────────────┘   └─────────────┘   │
    │                           │                             │
    │                           ▼                             │
    │  ┌─────────────────────────────────────────────────┐    │
    │  │              Production-Ready Server            │    │
    │  │  • Health probes  • Metrics  • Security headers │    │
    │  │  • Rate limiting  • CORS     • Graceful shutdown│    │
    │  └─────────────────────────────────────────────────┘    │
    │                                                         │
    └─────────────────────────────────────────────────────────┘

Why axum-conf?

  • Zero boilerplate: Get liveness probes, Prometheus metrics, and security headers without writing middleware
  • Kubernetes-native: Built for container deployments with proper health checks and graceful shutdown
  • Configuration-driven: Change behavior through TOML files, not code changes

Quick Start

1. Add to Cargo.toml:

[dependencies]
axum-conf = "0.3"
axum = "0.8"
tokio = { version = "1", features = ["full"] }

2. Create config/dev.toml:

[http]
bind_port = 3000
max_payload_size_bytes = "1MiB"

3. Write src/main.rs:

use axum::{Router, routing::get};
use axum_conf::{Config, Result, FluentRouter};

async fn hello() -> &'static str {
    "Hello, World!"
}

#[tokio::main]
async fn main() -> Result<()> {
    let config = Config::default();
    config.setup_tracing();

    FluentRouter::without_state(config)?
        .route("/", get(hello))
        .setup_middleware()
        .await?
        .start()
        .await
}

4. Run:

RUST_ENV=dev cargo run

5. Test it:

curl http://localhost:3000/        # Your handler
curl http://localhost:3000/live    # Liveness probe
curl http://localhost:3000/ready   # Readiness probe
curl http://localhost:3000/metrics # Prometheus metrics

What You Get

Feature What it does Default
Health probes /live and /ready endpoints for Kubernetes Enabled
Prometheus metrics Request counts, latencies at /metrics Enabled
Request logging Structured logs with UUIDv7 correlation IDs Enabled
Rate limiting Per-IP request throttling 100 req/sec
Security headers X-Frame-Options, X-Content-Type-Options Enabled
Static files Serve assets, SPAs, protected downloads Available
Panic recovery Catches panics, returns 500, keeps running Enabled
Graceful shutdown Handles SIGTERM, drains connections 30s timeout
Compression gzip, brotli, deflate, zstd Available

Cargo Features

Enable optional capabilities by category:

Core Features

Feature What it adds
postgres PostgreSQL connection pooling with sqlx
keycloak OIDC/JWT authentication via Keycloak
basic-auth HTTP Basic Auth and API key authentication
session Cookie-based session management
opentelemetry Distributed tracing with OTLP export
rustls TLS support (auto-enabled by postgres)
circuit-breaker Per-target circuit breaker for external services
openapi OpenAPI spec generation via utoipa

Middleware Features

Feature What it adds
metrics Prometheus metrics at /metrics
rate-limiting Per-IP request throttling
security-headers Security headers (X-Frame-Options, etc.)
deduplication Request deduplication by request ID
compression gzip/brotli/deflate/zstd compression
cors CORS handling
api-versioning API version extraction (path/header/query)
concurrency-limit Max concurrent request limiting
path-normalization Trailing slash normalization
sensitive-headers Authorization header redaction in logs
payload-limit Request body size limits

Feature Groups

Group Includes
production metrics, rate-limiting, security-headers, compression, cors
full All features
# Example: Production setup with PostgreSQL
axum-conf = { version = "0.3", features = ["production", "postgres"] }

Feature Compatibility

Most features work together, with one important exception:

Feature Compatible With Notes
keycloak All except basic-auth Automatically enables session
basic-auth All except keycloak Cannot be used with OIDC
postgres All features Independent database layer
session All features Required by keycloak
opentelemetry All features Independent tracing layer

Important: keycloak and basic-auth are mutually exclusive. Choose one authentication method per application.

Examples

Run the examples to see axum-conf in action:

# Basic hello world
cargo run --example hello_world

# Application state management
cargo run --example with_state

# JSON REST API
cargo run --example json_api

# Middleware configuration (requires features)
cargo run --example with_middleware --features "cors,compression,rate-limiting"

Configuration Example

# config/prod.toml
[http]
bind_addr = "0.0.0.0"
bind_port = 8080
max_payload_size_bytes = "32KiB"
request_timeout = "30s"
max_requests_per_sec = 1000

[http.cors]
allowed_origins = ["https://app.example.com"]
allowed_methods = ["GET", "POST", "PUT", "DELETE"]

[database]
url = "{{ DATABASE_URL }}"
max_pool_size = 10

[logging]
format = "json"

Custom Tracing Layers

The default setup_tracing() configures logging based on your LoggingConfig. When you need additional tracing layers (OpenTelemetry, file appenders, custom filters), use setup_tracing_with():

use axum_conf::Config;
use tracing_subscriber::layer::SubscriberExt;

let config = Config::default();

// Add custom layers while keeping the configured fmt layer and env filter
config.setup_tracing_with(|subscriber| {
    subscriber
        .with(tracing_opentelemetry::layer().with_tracer(tracer))
        .with(my_file_appender_layer)
});

The callback receives a TracingBase subscriber with the fmt layer (respecting your logging.format setting) and EnvFilter already applied. Add your layers with .with() and return the result.

Documentation

Guide Description
Getting Started Build your first service step-by-step
Architecture How axum-conf works under the hood
Configuration
Overview Configuration methods and philosophy
TOML Reference Complete configuration schema
Environment Variables Using {{ VAR }} substitution
Features
PostgreSQL Database integration guide
Keycloak/OIDC Authentication setup
OpenTelemetry Distributed tracing
Basic Auth Simple authentication
Sessions Session management
Circuit Breaker External service resilience
OpenAPI API documentation generation
Deduplication Request deduplication
TLS/rustls TLS configuration
Static Files Asset serving and SPA support
Middleware
Overview Middleware stack architecture
Features API versioning, limits, normalization
Security Rate limiting, CORS, headers
Observability Logging, metrics, tracing
Performance Compression, timeouts, limits
Kubernetes
Health Checks Liveness and readiness probes
Graceful Shutdown Proper pod termination
Deployment Complete K8s manifests
Reference
Troubleshooting Common issues and solutions
API Docs Rustdoc API reference

Minimal Kubernetes Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-service
spec:
  template:
    spec:
      containers:
      - name: app
        image: my-service:latest
        ports:
        - containerPort: 8080
        env:
        - name: RUST_ENV
          value: "prod"
        livenessProbe:
          httpGet:
            path: /live
            port: 8080
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
      terminationGracePeriodSeconds: 35

License

MIT