apigate-macros 1.0.0

Procedural macros for apigate: #[service], #[hook], #[map], and route attributes
Documentation

ApiGate

CI Coverage Crates.io Docs.rs License: MIT

ApiGate is a macro-driven API gateway for Rust services.

It lets you declare reverse-proxy routes as Rust modules, validate typed request data, run pre-proxy hooks, transform requests before forwarding, choose upstream backends with routing/balancing policies, and customize errors and observability without exposing axum details in your application code.

Under the hood ApiGate is built on axum, hyper-util, tower, and tracing.

Contents

What It Provides

  • Declarative service and route macros: #[apigate::service], #[apigate::get], #[apigate::post], etc.
  • Reverse proxying with streaming passthrough when a route does not need to read the body.
  • Typed validation for path, query, json, and form inputs.
  • before hooks for auth, headers, request metadata, and per-request state.
  • map functions for typed request transformation before the upstream call.
  • Multipart passthrough without buffering file bodies.
  • Built-in policies: round-robin, consistent hash, header/path sticky, least-request, least-time.
  • Custom routing strategies and custom balancers.
  • Custom error rendering, including JSON envelopes and fully custom hook/map responses.
  • Optional runtime observability through tracing or a custom runtime observer.
  • External tower/axum middleware composition through the underlying router.

Benchmarks

ApiGate is benchmarked against Kong, Apache APISIX, and a tuned Python ASGI gateway in a reproducible load-test repo: OlegDokuchaev/apigate-benchmark.

The latest run uses the same Go auth/data backends for every gateway and runs one gateway at a time on a 4 vCPU / 10 GiB Linux host.

Profile Result
Steady, 2500 RPS ApiGate p99 latency is 33-144% faster than APISIX, 52-391% faster than Kong, and 317-3857% faster than Python depending on route.
Ramp, 0 -> 20000 RPS ApiGate delivers 6-31% more average RPS than APISIX, 1-41% more than Kong, and 115-184% more than Python before the p99 abort threshold.
Stress, 9000 RPS ApiGate p99 latency is 7-70% faster than APISIX, 32-159% faster than Kong, and much faster than Python on saturated routes.

See Performance Notes for steady-state latency numbers and links to the full benchmark results.

Installation

Add the facade crate to your application:

[dependencies]
apigate = "1.0.0"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
serde = { version = "1", features = ["derive"] }
anyhow = "1"

Optional dependencies used in examples:

axum = "0.8"
http = "1"
serde_json = "1"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
tower-http = { version = "0.6", features = ["trace"] }
uuid = { version = "1", features = ["v4", "serde"] }

Supported Rust

ApiGate declares Rust 1.88 as its package rust-version. Rust 1.88 stabilizes let chains in the Rust 2024 edition, which ApiGate uses in its implementation. CI checks that the library crates compile on Rust 1.88 and runs the full test suite on the latest stable toolchain.

Quick Start

use std::net::SocketAddr;

#[apigate::service(prefix = "/sales")]
mod sales {
    #[apigate::get("/ping")]
    async fn ping() {}

    #[apigate::get("/public", to = "/internal")]
    async fn public_alias() {}

    #[apigate::get("/item/{id}/review", to = "/api/v2/reviews/{id}")]
    async fn item_review() {}
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let listen: SocketAddr = "127.0.0.1:8080".parse()?;

    let app = apigate::App::builder()
        .mount_service(sales::routes(), ["http://127.0.0.1:8081"])
        .request_timeout(std::time::Duration::from_secs(10))
        .connect_timeout(std::time::Duration::from_secs(3))
        .pool_idle_timeout(std::time::Duration::from_secs(60))
        .build()?;

    apigate::run(listen, app).await?;
    Ok(())
}

Run a local upstream and the example gateway:

caddy run --config apigate/examples/upstream/Caddyfile
cargo run --example basic

Then call:

curl http://127.0.0.1:8080/sales/ping
curl http://127.0.0.1:8080/sales/public
curl http://127.0.0.1:8080/sales/item/abc-123/review

Core Concepts

ApiGate has three layers:

Layer Purpose
Service A macro-generated collection of routes with an optional prefix and default policy.
Route An HTTP method/path declaration with optional validation, hooks, mapping, rewrite, and policy override.
App Runtime configuration: mounted services, upstream backends, shared state, timeouts, policies, errors, and observability.

The normal flow is:

  1. A request matches an axum route generated by ApiGate.
  2. ApiGate optionally extracts typed path parameters.
  3. before hooks run in order.
  4. The route optionally validates or maps query, json, or form data.
  5. A routing strategy selects candidate backends.
  6. A balancer picks one backend.
  7. ApiGate rewrites the URI and proxies the request to the upstream.
  8. Runtime events are emitted only if a runtime observer is configured.

API Reference

The detailed API guide lives in docs/reference.md. It keeps the README focused on installation, the first gateway, core concepts, and performance.

Reference sections:

Topic Link
Services and route declarations Services, Routes
Typed inputs and rewrites Typed Inputs, Path Rewrites
Hooks and maps Hooks, Maps, Hook and Map Parameters
Shared and per-request state Shared and Per-Request State
Errors Error Handling
Observability and tower layers Runtime Observability and Tracing, External Tower Layers
Policies and balancing Policies, Routing, and Balancing
Runtime tuning and serving helpers App Builder Reference

Performance Notes

ApiGate is designed to avoid unnecessary work on routes that do not need it:

  • Routes without path, before, query, json, form, or map have no generated pipeline and proxy the body as streaming passthrough.
  • Multipart routes stream the request body without reading or buffering it.
  • json and form validation read the body only when the route declares typed validation or mapping.
  • query validation does not read the body.
  • Shared app state is accessed by reference through Extensions; read-only &T access does not clone per request.
  • Per-request RequestScope local storage allocates only when values are inserted.
  • Route metadata is stored in a table and request routing carries a small route index.
  • The upstream client uses keep-alive pooling, TCP_NODELAY, configurable connect timeout, configurable idle timeout, and exposes hyper-util client/connector tuning hooks.
  • Built-in balancers are lock-free and use atomics.
  • Runtime observer is disabled by default; when disabled, the hot path only performs an Option check.

Routes with json, form, or mapped bodies intentionally allocate for parsed/serialized payloads. Keep those routes for boundaries where validation or transformation is worth the cost.

Benchmark Results

The benchmark suite compares ApiGate with Kong, Apache APISIX, and a tuned Python ASGI gateway over the same Go auth/data backends. The latest run used a 4 vCPU / 10 GiB Linux host and tested plain proxying, auth hook + header injection, JSON validation, and typed request mapping/rewrite.

Steady-state p99 latency at 2500 RPS:

Route ApiGate APISIX Kong Python
GET /items 1.76 ms 2.40 ms 2.84 ms 7.33 ms
GET /my-items 3.34 ms 8.15 ms 16.38 ms 132.1 ms
POST /items/search 1.91 ms 2.53 ms 2.94 ms 20.52 ms
POST /items/lookup 1.86 ms 2.75 ms 2.83 ms 22.05 ms

For comparative throughput and latency numbers, see the reproducible ApiGate benchmark suite and its latest results.

Examples

Run the mock upstream first:

caddy run --config apigate/examples/upstream/Caddyfile

Then run any example:

cargo run --example basic
cargo run --example hooks
cargo run --example errors
cargo run --example logging
cargo run --example tower_logging
cargo run --example runtime_tuning
cargo run --example path
cargo run --example map
cargo run --example policy
cargo run --example multipart

Example guide:

Example Shows
basic Passthrough proxying, static rewrite, rewrite templates.
hooks Shared state, auth, header injection, hook chains, per-request scope data.
errors Global JSON error renderer, user/debug message separation, custom JSON from hooks.
logging Built-in tracing observer and custom runtime observer.
tower_logging External tower_http::TraceLayer with .with_router(...).
runtime_tuning Listener socket tuning plus upstream hyper-util client/connector settings.
path Typed path validation, path data in hooks, path data in maps.
map Query, JSON, and form transformations.
policy Header/path sticky routing, consistent hash, least-request, least-time, round-robin.
multipart Multipart upload passthrough with and without auth.

Each example prints ready-to-run curl commands.