Axum boilerplate fatigue — I built a thin opinionated layer (Oxide) with benchmarks

Hi everyone,

I've been using Axum + Tower for a few production services, and every time I start a new project I end up repeating the same setup work:

  • Wiring CORS, request timeouts, rate limiting (per-IP), panic recovery (clean 500 JSON), tracing, and basic state/DI
  • Making sure middleware order is correct
  • Building simple controller and response conventions from scratch

It works, but it's repetitive and easy to miss something.

To reduce that friction I created a small opinionated wrapper called Oxide on top of Axum. The goal is to provide sensible production defaults and convention-over-configuration (Spring Boot-like feel) while keeping overhead very low and staying fully compatible with the Axum/Tower ecosystem.

What it adds "for free":

  • Automatic middleware pipeline (rate limiting, timeouts + connection guard, panic recovery, logging, CORS, etc.)
  • Simple attribute-based controllers
  • Standardized JSON responses and error handling

Performance:

  • Full middleware stack: 40.5k requests/sec
  • Raw Axum baseline: 42.2k requests/sec
  • Added overhead: only 13–19 µs per request

I've attached three screenshots:

  1. Side-by-side throughput benchmark
  2. The "for free" middleware pipeline
  3. Boilerplate comparison (raw Axum vs Oxide)

It's still early-stage, built purely on top of excellent existing crates (no runtime reflection, no magic).

I'm sharing this here because I'd genuinely like feedback from the community:

  • Do others find the initial middleware + structure setup the most annoying/repetitive part of new Axum projects?
  • Would this kind of thin convention layer be useful, or do you strongly prefer staying 100% manual?

Repository: GitHub - i228808/oxide · GitHub

Happy to answer questions, take criticism, or discuss design decisions. The Rust community is usually excellent at catching bad ideas early.

Looking forward to your thoughts!



2 Likes

If the "full middleware stack" is 1700 req/sec slower, then it's 1/1700 sec/req slower. That's 0.000588 sec/req, or 5.88 µs.

Where did 13-19 come from?

2 Likes

Awesome man, thanks a lot. Which hardware did you use for a performance measurement?

1 Like

Maybe a throughout vs latency thing?

1 Like

Good catch, and you’re thinking about it the right way.

The ~5.9 µs figure comes from the throughput (wrk/load test) numbers:
~42K req/s (Axum) vs ~40–41K req/s (Oxide) → ~1700 req/s difference → ~5–6 µs per request.

The 13–19 µs comes from the Criterion microbenchmarks, where we measure isolated per-request latency:
~84 µs (Axum) vs ~97–100 µs (Oxide).

So they’re measuring slightly different things:

  • Load tests (wrk/k6) → end-to-end throughput under concurrency
  • Criterion → per-request latency in a controlled environment

In real-world scenarios, the throughput numbers are usually the more representative ones, since network and concurrency effects dominate.

I should probably clarify that distinction better in the README. Appreciate you calling it out.

Good question.

Benchmarks were run on an HP Omen 16 (wf0000 series), Windows 10, with an Intel Core i5-13500HX.

Exact numbers will vary depending on hardware, OS, and load conditions, but the relative comparison between raw Axum and Oxide should remain consistent.

If you run it on your setup, I’d be very interested in seeing the results.