Saltar al contenido principal

Introducción

Instalaciones

  • Tokio y Axum
cargo add tokio axum --features tokio/full
  • Para procesar solicitudes y respuestas JSON
cargo add serde_json serde --features serde/derive

Ejemplo Cargo.toml

Cargo.toml
[dependencies]
# Server
tokio = { version = "1.44.2", features = ["full"] }
axum = { version = "0.8.3", features = ["macros"] }

# Utils
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"

dotenvy = "0.15.7"
  • La feature macros de Axum sirve para poder utilizar #[axum::debug_handler] sobre alguna función y así ver mensajes de errores y alertas más descriptivos

Servidor

src/main.rs
use axum::{
    Router,
    routing::{get, post},
};

use tokio::net::TcpListener;

#[tokio::main]
async fn main() {
    // Load .env file (development)
    dotenv().ok();

// Router
    let app = Router::new()
        .route("/", get(hello_word))
        .route("/users", post(create_user));

// Start server
let port = env::var("PORT")
        .expect("PORT undefined")
        .parse::<u16>()
        .expect("PORT isn't a u16 value");

    let listener = TcpListener::bind(format!("0.0.0.0:{}", port))
        .await
        .expect("Failed to create TCP Listener on the specified address");
   
    println!("Server running on port: {}", listener.local_addr().unwrap());
    axum::serve(listener, app).await.unwrap();
}

Rutas

  • Todos los controladores o handlers deben ser async
  • El retorno de las funciones serán las response para el cliente
  • Si no se especifica un código de respuesta, por defecto es 200
  • Para construir una respuesta se pueden usar tuplas o un tipo valido
    • Json<T>
    • (code, body)
    • (code, [headers], body)

Definir rutas fuera de la función main para facilitar la estructura y lectura

router.rs
fn router() -> Router {
    Router::new()
        .route("/", get(hello_axum).post("Hello post"))
        .route("/word", get(|| async { "Hello Word" }))
        .route("/user", get(get_user).post(create_user))
        .route("/other", get(other_route))
}

Agregar rutas al server

main.rs
axum::serve(listener, router()).await.unwrap();

Handlers

// Respuesta texto plano
async fn hello_axum() -> &'static str {
    "Hello from Axum!"
}

// Respuesta con cualquier tipo que
// implemente el trait axum::response::IntoResponse
async fn other_route() -> impl IntoResponse {
    // Ej. Solo texto
    //"Hello Word"

    // Ej. tupla con Json
    (
        StatusCode::UNAUTHORIZED,
        [("Content-Type", "application/json")],
        r#"{"status": "No token provided"}"#,
    )
}

// Respuestas JSON
// axum::Json
// serde_json::Value
async fn get_user() -> Json<Value> {
    Json(json!({"status" : 200, "message": "Example"}))
}

// Construir respuesta
// axum::response::Response
async fn create_user(Json(payload): Json<CreateUser>) -> Response {
    println!("Name: {}, age: {}", payload.name, payload.age);

    Response::builder()
        .status(StatusCode::UNAUTHORIZED)
        .header("Content-Type", "application/json")
        .body(Body::from(r#"{"status": "No token provided"}"#))
        .unwrap()
}

#[derive(Serialize, Deserialize)]
struct CreateUser {
    name: String,
    age: u8,
}