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
macrosde 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,
}