Skip to content

Building Web API's With Rust 2

Building Web API's With Rust 2

Following up on the first post about building web APIs with Rust, this time we'll explore routing with the reset_router library and then move on to using Axum.

From hyper to Axum

While hyper provides the foundation, building APIs with just hyper requires a lot of boilerplate. Enter Axum - a web framework that builds on hyper and tower.

Basic Axum Setup

use axum::{
    routing::get,
    Router,
};

#[tokio::main] async fn main() { let app = Router::new() .route("/", get(handler));

let listener = tokio::net::TcpListener::bind("0.0.0.0:3000") .await .unwrap(); println!("Listening on {}", listener.local_addr().unwrap()); axum::serve(listener, app).await.unwrap(); }

async fn handler() -> &'static str { "Hello, World!" }

Routing

let app = Router::new()
    .route("/", get(root))
    .route("/users", get(list_users).post(create_user))
    .route("/users/:id", get(get_user).put(update_user).delete(delete_user));

Extractors

use axum::extract::{Path, Query, Json};
use serde::Deserialize;

#[derive(Deserialize)] struct Pagination { page: Option<u32>, per_page: Option<u32>, }

async fn list_users( Query(pagination): Query<Pagination>, ) -> Json<Vec<User>> { // ... }

async fn get_user(Path(id): Path<u64>) -> Json<User> { // ... }

async fn create_user( Json(user): Json<CreateUserRequest>, ) -> Json<User> { // ... }

Middleware

use tower_http::trace::TraceLayer;

let app = Router::new() .route("/", get(handler)) .layer(TraceLayer::new_for_http());

State Management

use axum::extract::State;
use std::sync::Arc;

#[derive(Clone)] struct AppState { db: DatabaseConnection, }

let state = Arc::new(AppState { db });

let app = Router::new() .route("/users", get(list_users)) .with_state(state);

async fn list_users( State(state): State<Arc<AppState>>, ) -> Json<Vec<User>> { // Access state.db }

Error Handling

use axum::response::{IntoResponse, Response};
use axum::http::StatusCode;

enum AppError { DatabaseError(String), NotFound, }

impl IntoResponse for AppError { fn into_response(self) -> Response { let (status, message) = match self { AppError::DatabaseError(e) => { (StatusCode::INTERNAL_SERVER_ERROR, e) } AppError::NotFound => { (StatusCode::NOT_FOUND, "Not found".to_string()) } }; (status, message).into_response() } }

Axum combines the power of hyper with ergonomic APIs - making Rust web development a joy!