Library / SDK
tokio-rs/axum avatar
tokio-rs/axum

axum has no middleware system of its own, and its default branch is not what crates.io serves

HTTP routing and request-handling library for Rust that focuses on ergonomics and modularity

27,815 stars1,623 forksRustMIT

At a glance

What is it?
A Rust HTTP routing and request-handling library that routes through tower::Service instead of shipping middleware of its own, forbids unsafe code and warns on todo markers at the workspace level, and has kept pushing to main since its last release, axum 0.8.9.
Who is it for?
axum suits a Rust service that already speaks tower and wants timeouts, tracing and compression from tower-http instead of from a framework-specific layer. It does not suit someone who needs the default branch to be what crates.io serves, because main carries changes made since the axum 0.8.9 release.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 13, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • tokio-rs/axum27,815 ★ · +529
Star history
Repository2026-09-282026-11-13
tokio-rs/axum27.3k27.8k

529 Stars in the last 46 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

main carries breaking changes, so the example you read is not the crate you get

The released line is 0.8.x, with axum-v0.8.9 as the newest axum release, while the default branch, `main`, has kept moving since.

The dates make the gap concrete. The three most recent releases are axum-v0.8.9, axum-macros-v0.5.1 and axum-extra-v0.12.6, all published on 2026-04-14. The default branch was last pushed on 2026-11-01, more than six months later. So the code a contributor reads after cloning is a moving target with unreleased changes, and the latest release is more than six months old.

The consequence for a reader is that a snippet copied from the README may not compile against the released crate. Pin to the released 0.8.x crate when you want the published behaviour, and treat main as unreleased code.

axum has no middleware system, it composes tower::Service instead

This is the design decision the README singles out as what sets axum apart. axum does not have its own middleware system; it uses `tower::Service`. Timeouts, tracing, compression and authorization therefore arrive from the tower and tower-http ecosystem rather than from axum, and middleware written once can be shared with applications built on hyper or tonic.

What you gain is a middleware stack that is not tied to axum's request or response types. What you take on is the assembly: you compose layers and services yourself rather than registering middleware in a framework list, and anything not already in tower or tower-http is yours to write.

The examples directory shows the shape of that work. compression/, cors/, jwt/, http-proxy/ and graceful-shutdown/ each solve one problem that in a framework with its own middleware system would be a single registration. That is the trade in one line: the ecosystem you inherit is broad, and so is the wiring you own.

Extractors parse the request and their rejections are a first-class outcome

Routing is a macro free API, and the app is built by composing a router:

rust
let app = Router::new()
    // `GET /` goes to `root`
    .route("/", get(root))
    // `POST /users` goes to `create_user`
    .route("/users", post(create_user));

Parsing is declarative through extractors, and the stated error handling model is simple and predictable. What makes that worth reading is not the wording but the set of examples devoted to the unhappy paths: customize-extractor-error/, customize-path-rejection/, global-404-handler/, error-handling/ and anyhow-error-response/. Five of the example directories exist to show a failure being shaped.

One of them, consume-body-in-extractor-or-middleware/, names the trap directly. An extractor that reads the body and a middleware that also wants it compete for the same bytes, and the directory exists because that ordering has to be decided rather than discovered. That is the practical cost of declarative parsing: the failure modes are enumerable, and this project has enumerated several of them for you.

Twenty-two examples, and the interesting ones are the database and proxy cases

The examples directory has its own Cargo.toml, its own Cargo.lock and its own README, which means the examples are a workspace of their own rather than snippets in a document. The listed directories run from anyhow-error-response/ to low-level-native-tls/.

Named individually, they cluster into three groups. Protocol and integration: async-graphql/, diesel-postgres/, diesel-async-postgres/, key-value-store/, chat/, http-proxy/, low-level-native-tls/. Application mechanics: auto-reload/, dependency-injection/, graceful-shutdown/, handle-head-request/, form/, consume-body-in-extractor-or-middleware/. And the rejection and response shapes already mentioned.

Two of the listed examples use diesel, one of them with an async driver.

unsafe_code is forbidden at the workspace level, and so is todo

The safety claim in the README is enforced rather than asserted. It says the crate uses `#![forbid(unsafe_code)]` to ensure everything is implemented in 100% safe Rust, and the workspace Cargo.toml turns the same thing into a lint entry: `unsafe_code = "forbid"` under `[workspace.lints.rust]`. That applies to every member of the workspace, so a new crate inherits it by being added.

The rest of the lint table is more informative about what the maintainers consider costly. `todo = "warn"` means a todo marker cannot be committed quietly. `exit = "warn"` stops a library from terminating the process underneath its caller. `missing_docs = "warn"` and `unreachable_pub = "warn"` keep the public surface documented and the visibility honest. `await_holding_lock = "warn"` guards the failure that async code invites.

About forty clippy lints are switched on at warn, with exactly one exception: `type_complexity = "allow"`. That single exemption says the maintainers accept deeply nested types where the compiler needs them to line up, and have otherwise taken the default position that most of these lints are worth fixing. Two files carry the machinery: `.clippy.toml` at the root and `deny.toml`, which is where the dependency licence and advisory policy for the whole workspace is configured.

release-plz writes the changelog, and the typo checker is told to keep a misspelling

Three files at the root describe how changes leave the project. `release-plz.toml` configures the tool that cuts releases and writes `CHANGELOG.md`, `deny.toml` configures cargo-deny, and `.clippy.toml` configures the linter.

The most revealing lines in the workspace Cargo.toml are the ones nobody needs. Under a comment referencing the crate-ci typos checker, two identifiers are added to a list of exceptions: `DefaultOnFailedUpdgrade` and `OnFailedUpdgrade`. Both are misspellings of upgrade, and the comment explains that they have been fixed in the code but are still present in the changelog, so the checker is instructed to leave them alone rather than rewrite published history.

That is a small thing and it is a good indicator of how the project operates: releases are automated from commits, the changelog is treated as permanent, and the tooling is configured to be conservative about both.

One rust-version for the workspace, four crates on independent version lines

The workspace sets `rust-version = "1.85"` in `[workspace.package]`. Because it is declared once, every member inherits the same floor, and a contributor with an older toolchain cannot build any of them.

Membership is a glob. `members = ["axum", "axum-*"]` with `resolver = "2"` picks up axum/, axum-core/, axum-extra/ and axum-macros/ from one line, and any directory added later with that prefix joins automatically. A contrib/ directory sits outside that pattern, and it is not named as a member here.

The version lines are independent, which is the part that takes reading. On 2026-04-14 the project published axum 0.8.9, axum-macros 0.5.1 and axum-extra 0.12.6 together, so each crate carries its own version number. axum-macros exists as its own crate while the README advertises a macro free API for routing; the file does not say what the macro crate derives, which is exactly the kind of gap the crate documentation would have to fill.

Licensing is the simple part. The repository is MIT licensed.

Editorial conclusion

axum suits a Rust service that already speaks tower and wants timeouts, tracing and compression from tower-http instead of from a framework-specific layer. It does not suit someone who needs the default branch to be what crates.io serves, because main carries changes made since the axum 0.8.9 release. Before adopting, build against the released 0.8.x crate rather than main, read deny.toml to see the licence and advisory policy your build inherits, and decide early whether a 1.85 floor on your toolchain is acceptable.

Frequently asked questions

Does Axum use Tokio?

The usage example is built on it. The main function is annotated `#[tokio::main]`, the listener is `tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap()`, and the comment on that line says the app runs with hyper. The README does not state whether tokio is a required dependency of the crate itself or simply the runtime the example chooses.

What is an axum server?

In the example it is a value built by `Router::new()` with routes attached, where `.route("/", get(root))` sends GET requests to the `root` handler and `.route("/users", post(create_user))` sends POST requests to `create_user`. The value is then served over a TcpListener bound to 0.0.0.0:3000.

What is an axum?

An HTTP routing and request-handling library for Rust that focuses on ergonomics and modularity. It routes requests with a macro free API, parses requests declaratively with extractors, has a simple and predictable error handling model, and relies on tower and tower-http for middleware rather than providing its own. Its workspace rust-version is 1.85.

How to use axum?

Build a Router with routes, bind a tokio TcpListener, and serve it with hyper, as the README's usage example shows. The examples folder contains runnable projects including hello-world, graceful-shutdown, jwt, cors and compression, and the crate documentation at docs.rs carries many more snippets.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. tokio-rs/axum on GitHub
Community

Where developers are discussing it

Posts on Hacker News, dev.to and Lobsters that link to this repository, found by our scan of those communities. 1 in the last 30 days; newest first.

Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/tokio-rs-axum.svg)](https://hysenlabs.com/projects/tokio-rs-axum)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/tokio-rs-axum.svg)](https://hysenlabs.com/projects/tokio-rs-axum)

Was this useful?