5 releases

0.1.5 Feb 27, 2025
0.1.4 Feb 25, 2025

#533 in Database interfaces

Download history 125/week @ 2025-02-15 319/week @ 2025-02-22 104/week @ 2025-03-01 2/week @ 2025-03-08

550 downloads per month

MIT license

21KB
501 lines

axum_rh

Axum router helper helps you build an API simply by using macros to define the router and all endpoints.

Install

To add axum-rh to your project, run:

cargo add axum-rh

Middlewares

All middlewares are optional and can be overridden by your own implementations.

Logging

A simple logger middleware that can also send logs to Betterstack (with the async feature). It formats request logs.

use axum_rh::router::logger::init_logger;
use axum_rh::router::middlewares;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    init_logger();
    let tcp = TcpListener::bind(&"0.0.0.0:3005").await.unwrap();
    let app = ApiRouter::load_routers().layer(from_fn(
            middlewares::logging::logging_middleware,
        ));
    serve(tcp, app.into_make_service()).await?;
    Ok(())
}

Sessions

A default implementation of session management using Redis storage. This middleware will be updated to be more user-friendly in the future.

Auth

A default JWT authentication middleware. The JWT token requires a user_id field. This middleware will also be updated to be more user-friendly in the future.

How to:

Declare Router

To declare a router, use the RouterHelper derive macro and the router_config attribute to define the state type and router definitions.

#[derive(RouterHelper)]
#[router_config((), Health, Samples)]
pub struct ApiRouter;

Use States

To use states in your router, define a state struct and include it in the router_config attribute.

pub struct ApiState {
    pub counter: i32
}

#[derive(RouterHelper)]
#[router_config(ApiState, Health, Samples)]
pub struct ApiRouter;

Declare Endpoints

To declare an endpoint, use two macros. The first macro is applied to the implementation of the module definition.

pub struct Samples;

#[router(base_path = "/samples")]
impl Samples {

}

The base_path argument is optional and defaults to / if not set. The second macro is applied to each function definition under the struct implementation.

pub struct Samples;

#[router(base_path = "/samples")]
impl Samples {
    #[get("")]
    async fn get_samples() -> ApiResponse<serde_json::Value> {
        ...
    }
}

HTTP Methods

Define endpoints using the following macros:

  • GET: Define a GET endpoint.

    #[get("")]
    
  • POST: Define a POST endpoint.

    #[post("")]
    
  • PUT: Define a PUT endpoint.

    #[put("")]
    
  • DELETE: Define a DELETE endpoint.

    #[delete("")]
    

Return Values

The ApiResponse struct defines a default response type for your endpoints. You can use it or define your own response type as long as it implements IntoResponse.

pub struct ApiResponse<T> {
    pub status: StatusCode,
    pub body: ResponseBody<T>,
    pub error: bool,
    pub headers: Option<Vec<(HeaderName, String)>>,
}

pub struct ResponseBody<T> {
    #[serde(skip_serializing_if = "String::is_empty", default)]
    pub error: String,
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub data: Option<T>,
}

Example:

#[router(base_path = "/samples")]
impl Samples {
    #[get("")]
    async fn get_samples() -> ApiResponse<serde_json::Value> {
        let samples: Vec<SampleData> = (0..10)
            .map(|i| SampleData {
                id: i,
                name: format!("Sample {}", i),
            })
            .collect();
        ApiResponse::ok(Some(json!(samples)))
    }

    #[post("/{id}")]
    async fn post_samples(Path(id): Path<i32>) -> ApiResponse<SampleData> {
        let sample = SampleData {
            id: id,
            name: "Sample".to_string(),
        };
        ApiResponse::ok(Some(sample))
    }
}

Dependencies

~0.2–12MB
~145K SLoC