Con axum un servidor web con rutas y JSON cabe en treinta líneas. Es el framework más usado hoy y se apoya en tokio, el entorno asíncrono de Rust, así que este es el primer ejemplo de la serie con async.
También te puede interesar
Las dependencias
cargo add axum cargo add tokio --features full cargo add serde --features derive
[dependencies]
axum = "0.8.9"
tokio = { version = "1.53.1", features = ["full"] }
serde = { version = "1.0.229", features = ["derive"] }
Tres rutas: texto, JSON con parámetro y recepción de JSON
use axum::{extract::Path, routing::{get, post}, Json, Router};
use serde::{Deserialize, Serialize};
#[derive(Serialize)]
struct Articulo { id: u32, nombre: String, unidades: u32 }
#[derive(Deserialize)]
struct NuevoArticulo { nombre: String, unidades: u32 }
async fn raiz() -> &'static str { "inventario en marcha" }
async fn ver(Path(id): Path<u32>) -> Json<Articulo> {
Json(Articulo { id, nombre: "tornillos".into(), unidades: 120 })
}
async fn crear(Json(nuevo): Json<NuevoArticulo>) -> Json<Articulo> {
Json(Articulo { id: 99, nombre: nuevo.nombre, unidades: nuevo.unidades })
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let app = Router::new()
.route("/", get(raiz))
.route("/articulo/{id}", get(ver))
.route("/articulo", post(crear));
let escucha = tokio::net::TcpListener::bind("127.0.0.1:3000").await?;
println!("escuchando en http://127.0.0.1:3000");
axum::serve(escucha, app).await?;
Ok(())
}
Arrancándolo con cargo run --release y consultándolo con curl desde otra terminal:
curl http://127.0.0.1:3000/inventario en marcha
curl http://127.0.0.1:3000/articulo/7{"id":7,"nombre":"tornillos","unidades":120}
curl -X POST http://127.0.0.1:3000/articulo -H 'Content-Type: application/json' -d '{"nombre":"clavos","unidades":500}'{"id":99,"nombre":"clavos","unidades":500}
Cómo encajan las piezas
Lo que hace legible a axum es que cada manejador declara en su firma lo que necesita y lo que devuelve, y el framework se encarga del resto.
Path(id): Path<u32> saca el trozo de la URL y lo convierte a número; si alguien pide /articulo/abc, axum responde un error sin que tú escribas nada. Json(nuevo): Json<NuevoArticulo> lee el cuerpo y lo convierte a tu estructura con serde, y rechaza la petición si el JSON no encaja. Y devolver Json<Articulo> serializa la respuesta y pone la cabecera correcta.
/articulo/{id}. En las versiones anteriores se escribía /articulo/:id, con dos puntos, y esa forma ya no funciona. Es el cambio que más confunde al seguir tutoriales antiguos: el código compila pero la ruta nunca coincide.async por primera vez. Los manejadores son async fn y main lleva #[tokio::main], que monta el entorno de ejecución. Un servidor atiende muchas conexiones a la vez y casi todo su tiempo lo pasa esperando a la red, que es exactamente el caso en que la asincronía gana sobre los hilos de la lección de concurrencia: miles de conexiones en espera no necesitan miles de hilos.Lo que viene después
| Qué quieres | Cómo |
|---|---|
| Parámetros de consulta | Query<T> en la firma |
| Devolver un código concreto | (StatusCode::CREATED, Json(x)) |
| Compartir estado, como una base | State<T> y .with_state(...) |
| Registro de peticiones, CORS | capas de tower-http |
| Servir archivos estáticos | ServeDir de tower-http |
| Agrupar rutas | Router::nest("/api", otro) |
El código de esta página se compiló y ejecutó con rustc 1.96.1, axum 0.8.9 y tokio 1.53.1 antes de publicarla; el servidor se arrancó de verdad y las tres respuestas están copiadas de peticiones reales con curl. Documentación oficial: axum en docs.rs.