axum

repository·main·Indexed 12 days ago

https://github.com/tokio-rs/axum

An ergonomic and modular HTTP routing and request-handling library for Rust built on top of hyper and tower. Version 0.8.9 features macro-free routing, declarative extraction, and full compatibility with the tower and tower-http middleware ecosystem. It includes supporting crates such as axum-core for fundamental types, axum-extra for extended utilities, and axum-macros for procedural macros. Requires Rust 1.75+.

Tokens
66.7K
Snippets
169
Records
221
Agent score
96%

What's inside axum

  1. Overview of axum-core

    main

    axum-core provides the fundamental types and traits used by the axum web framework. It serves as the foundational layer for the ecosystem's core abstractions.

    Key characteristics:

    • Safety: The crate uses #![forbid(unsafe_code)], ensuring all implementations are in 100% safe Rust.
    • MSRV: The Minimum Supported Rust Version is 1.75.
  2. Use axum-macros for axum applications

    main

    The axum-macros crate provides procedural macros designed specifically for use with the axum web framework. These macros help automate common tasks in defining handlers and working with the axum ecosystem.

    Requirements:

    • Minimum Supported Rust Version (MSRV): 1.75
    • The crate is implemented using 100% safe Rust (#![forbid(unsafe_code)]).
  3. Core features of axum

    main

    Axum provides several high-level features for building HTTP services:

    • Macro-free Routing: Route requests to handlers without needing complex macros.
    • Declarative Extraction: Use extractors to parse request components (like JSON, query params, or headers) directly in handler arguments.
    • Predictable Error Handling: A simple model for managing errors.
    • Minimal Boilerplate: Generate responses easily.
    • Tower Compatibility: Full support for tower and tower-http middleware and utilities.
  4. What is an axum handler?

    main

    In axum, a handler is an asynchronous function that serves as the core of your application logic. To be a valid handler, a function must satisfy two requirements:

    1. Arguments: It must accept zero or more extractors as arguments.
    2. Return Type: It must return a type that can be converted into a response.

    Axum applications are constructed by defining routes that map incoming requests to these handlers.

  5. How nesting captures dynamic path segments from outer routes

    main

    When using .nest() with dynamic segments in the outer path (e.g., /{version}/api), the nested router will capture those outer segments. If you use a Path extractor in the nested handler, it will contain both the segments defined in the nested router and the segments captured by the outer nesting path.

    use axum::{extract::Path, routing::get, Router};
    use std::collections::HashMap;
    
    async fn users_get(Path(params): Path<HashMap<String, String>>) {
        // Both `version` and `id` are captured even though `users_api` only
        // explicitly captures `id`.
        let version = params.get("version");
        let id = params.get("id");
    }
    
    let users_api = Router::new().route("/users/{id}", get(users_get));
    
    // The 'version' segment from the nest is passed into users_api
    let app = Router::new().nest("/{version}/api", users_api);
    # let _: Router = app;
    use axum::{
        extract::Path,
        routing::get,
        Router,
    };
    use std::collections::HashMap;
    
    async fn users_get(Path(params): Path<HashMap<String, String>>) {
        // Both `version` and `id` were captured even though `users_api` only
        // explicitly captures `id`.
        let version = params.get("version");
        let id = params.get("id");
    }
    
    let users_api = Router::new().route("/users/{id}", get(users_get));
    
    let app = Router::new().nest("/{version}/api", users_api);
    # let _: Router = app;
  6. Configure the `Allow` header in fallbacks

    main
    By default, MethodRouter automatically sets the Allow header when returning a 405 Method Not Allowed response. If you implement a custom fallback that returns a 405 Method Not Allowed, Axum will also attempt to set the Allow header unless your fallback handler has already explicitly set it in the response. If you are using a fallback to handle additional methods, ensure you manage the Allow header correctly to reflect the supported methods.
  7. How middleware works in axum

    main

    axum does not have a bespoke middleware system; instead, it integrates directly with tower. This allows you to use the entire ecosystem of tower and tower-http middleware.

    To use middleware, you can apply it at different levels of granularity:

    • Entire routers: Use Router::layer or Router::route_layer.
    • Method routers: Use MethodRouter::layer or MethodRouter::route_layer.
    • Individual handlers: Use Handler::layer.
    use axum::{Router, routing::get};
    use tower_http::trace::TraceLayer;
    
    async fn handler() {}
    
    let app = Router::new()
        .route("/", get(handler))
        .layer(TraceLayer::new_for_http());
  8. Understanding the `S` in `Router<S>`

    main

    In axum, the type parameter S in Router<S> represents the state that is missing from the router. It does not mean the router contains state S; it means the router needs state S to be able to handle requests.

    • Router<AppState>: A router that requires AppState to be provided before it can handle requests.
    • Router<()>: A router that is not missing any state (the state is effectively ()). This is the only type that can be used with into_make_service to start a server.
    • State Chaining: Calling .with_state(T) on a Router<S> consumes the missing state S and allows you to specify a new missing state type. For example, Router<AppState> can be turned into a Router<String> by providing AppState and then adding routes that require State<String>.
    use axum::{Router, routing::get, extract::State};
    
    #[derive(Clone)]
    struct AppState {}
    
    // A router that _needs_ an `AppState` to handle requests
    let router: Router<AppState> = Router::new()
        .route("/", get(|_: State<AppState>| async {}));
    
    // Once we call `with_state`, the router isn't missing the state anymore.
    // The type becomes `Router<()>`. 
    let router: Router<()> = router.with_state(AppState {});
  9. Build responses using `IntoResponse`

    main

    In axum, any type that implements the IntoResponse trait can be returned from a handler. Axum provides built-in implementations for several common types, which automatically set appropriate HTTP status codes and Content-Type headers:

    • (): Returns an empty response.
    • String: Returns text/plain; charset=utf-8.
    • Vec<u8> (Bytes): Returns application/octet-stream.
    • Json<T>: Returns application/json (requires T: serde::Serialize).
    • Html<&'static str>: Returns text/html.
    • StatusCode: Returns an empty response with the specified status code.
    • HeaderMap: Returns an empty response with the provided headers.
    • [(HeaderName, &'static str); N]: An array of tuples used to provide headers.
    use axum::response::{Html, IntoResponse};
    use axum::Json;
    use axum::http::{StatusCode, Uri};
    
    // Returns an empty response
    async fn empty() {}
    
    // Returns text/plain
    async fn plain_text(uri: Uri) -> String {
        format!("Hi from {}", uri.path())
    }
    
    // Returns application/json
    async fn json() -> Json<Vec<String>> {
        Json(vec!["foo".to_owned(), "bar".to_owned()])
    }
    
    // Returns text/html
    async fn html() -> Html<&'static str> {
        Html("<p>Hello, World!</p>")
    }
    
    // Returns a specific status code
    async fn status() -> StatusCode {
        StatusCode::NOT_FOUND
    }
  10. Use wildcards with `/{*key}`

    main

    Wildcards match all remaining segments in a path and store them in a single key. Use the syntax /{*key}.

    Key Rules:

    • Wildcards do not match empty segments. For example, /{*key} matches /a or /a/, but does not match the root /.
    • The leading slash of the captured portion is excluded. For a route /foo/{*rest} and a request to /foo/bar/baz, the captured value of rest is bar/baz.

    Extract wildcard values using the Path extractor.

    use axum::{
        Router,
        routing::get,
        extract::Path,
    };
    
    let app: Router = Router::new().route("/{*key}", get(handler));
    
    async fn handler(Path(path): Path<String>) -> String {
        path
    }
  11. What are extractors in axum?

    main

    An extractor is a type used as an argument in an asynchronous handler function to pull data out of an incoming HTTP request. A handler can take any number of extractors.

    Extractors are categorized into two types based on the traits they implement:

    1. FromRequestParts: Extractors that only need access to the request metadata (like headers, method, or URI) and do not consume the request body.
    2. FromRequest: Extractors that can consume the request body (like Json or String).

    Because the request body is an asynchronous stream that can only be consumed once, you can only have one extractor that consumes the request body in your handler, and it must be the last argument.

    async fn handler(
        method: Method,           // FromRequestParts
        headers: HeaderMap,       // FromRequestParts
        State(state): State<S>,  // FromRequestParts
        body: String,             // FromRequest (MUST be last)
    ) { ... }