[go: up one dir, main page]

0% found this document useful (0 votes)
123 views17 pages

Build REST APIs with Rust Axum Guide

The document provides a comprehensive guide on building REST APIs in Rust using the Axum framework, highlighting its advantages such as memory safety, performance, and type safety. It includes practical examples for creating a task management API, covering aspects like routing, state management, middleware, and error handling. Additionally, it emphasizes the importance of observability and metrics for production-ready applications.

Uploaded by

aylton_21
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
123 views17 pages

Build REST APIs with Rust Axum Guide

The document provides a comprehensive guide on building REST APIs in Rust using the Axum framework, highlighting its advantages such as memory safety, performance, and type safety. It includes practical examples for creating a task management API, covering aspects like routing, state management, middleware, and error handling. Additionally, it emphasizes the importance of observability and metrics for production-ready applications.

Uploaded by

aylton_21
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Build REST APIs in Rust with Axum | Rustaceans about:reader?url=https%3A%2F%[Link]%2Frust...

[Link]

Build REST APIs in Rust with Axum |


Rustaceans
Ashish Sharda

13–16 minutes

Rust-Powered APIs with Axum: A Complete 2025


Guide

6 min read

Jun 2, 2025

In the fast-paced world of backend development, choosing the


right framework can make or break your application’s scalability
and maintainability. What if your API could guarantee zero
runtime crashes, scale to thousands of connections, and run with
blazing speed? Enter Axum, a modern Rust web framework that
delivers on these promises.

Zoom image will be displayed

1 of 17 7/31/25, 12:55
Build REST APIs in Rust with Axum | Rustaceans about:reader?url=https%3A%2F%[Link]%2Frust...

Rust, renowned for its safety, concurrency, and performance, is a


natural fit for building robust web services. Axum, built on the
Tokio async runtime and Hyper HTTP library, brings Rust’s
strengths to the web with an ergonomic, high-performance
framework. In 2025, Rust remains the “most loved” language ,
and Axum is powering production APIs, due to its simplicity and
reliability.

This guide walks you through building a production-ready task


management API with Axum. We’ll cover routing, extractors, state
management, middleware, error handling, WebAssembly
integration, observability, and a production checklist, with
practical code examples inspired by the Rust community on X.

Why Axum for Production?

Axum stands out for production APIs due to:

• Rust’s Guarantees: Memory and thread safety eliminate runtime


errors like null pointers or data races, ensuring stability.

• Performance: Asynchronous and non-blocking, Axum handles


thousands of concurrent connections with low latency.

2 of 17 7/31/25, 12:55
Build REST APIs in Rust with Axum | Rustaceans about:reader?url=https%3A%2F%[Link]%2Frust...

• Type Safety: Axum’s extractors validate data at compile time,


reducing runtime errors.

• Ergonomics: Inspired by [Link] and Actix-Web, Axum offers


an intuitive API despite Rust’s learning curve.

• Middleware: Tower’s Service and Layer system enables


composable middleware for logging, authentication, and metrics.

Building a Task Management API with Axum

1. Setup and Dependencies

Create a new Rust project:

cargo new task-api


cd task-api

Add dependencies in [Link]:

[dependencies]
axum = "0.7"
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tower-http = { version = "0.6", features = ["trace", "metrics"] }
prometheus = "0.13"
wasm-bindgen = "0.2"

2. Routes and Handlers

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


async fn hello_world() -> &'static str {
"Welcome to the Task API!"

3 of 17 7/31/25, 12:55
Build REST APIs in Rust with Axum | Rustaceans about:reader?url=https%3A%2F%[Link]%2Frust...

#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(hello_world))
.route("/tasks", post(create_task))
.route("/tasks", get(get_tasks));
let listener =
tokio::net::TcpListener::bind("[Link]:3000").[Link]();
axum::serve(listener, app).[Link]();
}

3. Extractors for Type-Safe Requests

use axum::{
extract::Json,
http::StatusCode,
response::{IntoResponse, Response},
routing::post,
Router
};
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Deserialize)]
struct CreateTask {
title: String,
description: String,
}

#[derive(Serialize)]
struct Task {
id: u64,
title: String,

4 of 17 7/31/25, 12:55
Build REST APIs in Rust with Axum | Rustaceans about:reader?url=https%3A%2F%[Link]%2Frust...

description: String,
}

#[derive(Debug)]
enum AppError {
InvalidInput(String),
}

impl fmt::Display for AppError {


fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AppError::InvalidInput(msg) => write!(f, "Invalid input: {}",
msg),
}
}
}

impl IntoResponse for AppError {


fn into_response(self) -> Response {
let (status, error_message) = match self {
AppError::InvalidInput(msg) => (StatusCode::BAD_REQUEST,
msg),
};

(status, error_message).into_response()
}
}

async fn create_task(Json(payload): Json<CreateTask>) ->


Result<(StatusCode, Json<Task>), AppError> {
if [Link].is_empty() {
return Err(AppError::InvalidInput("Title cannot be

5 of 17 7/31/25, 12:55
Build REST APIs in Rust with Axum | Rustaceans about:reader?url=https%3A%2F%[Link]%2Frust...

empty".to_string()));
}

let task = Task {


id: 123,
title: [Link],
description: [Link],
};

Ok((StatusCode::CREATED, Json(task)))
}

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

let listener = tokio::net::TcpListener::bind("[Link]:3000")


.await
.unwrap();

println!("Server running on [Link]


axum::serve(listener, app).[Link]();
}

4. State Management with Database

use axum::{
extract::{Json, State},
http::StatusCode,
response::{IntoResponse, Response},
routing::{get, post},
Router
};
use serde::{Deserialize, Serialize};
use sqlx::{PgPool, FromRow};

6 of 17 7/31/25, 12:55
Build REST APIs in Rust with Axum | Rustaceans about:reader?url=https%3A%2F%[Link]%2Frust...

use std::{fmt, sync::Arc};


#[derive(Clone)]
struct AppState {
db_pool: Arc<PgPool>,
}

#[derive(Deserialize)]
struct CreateTask {
title: String,
description: String,
}

#[derive(Serialize, FromRow)]
struct Task {
id: i64,
title: String,
description: String,
}

#[derive(Debug)]
enum AppError {
InvalidInput(String),
InternalServerError,
}

impl fmt::Display for AppError {


fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AppError::InvalidInput(msg) => write!(f, "Invalid input: {}",
msg),
AppError::InternalServerError => write!(f, "Internal server
error"),

7 of 17 7/31/25, 12:55
Build REST APIs in Rust with Axum | Rustaceans about:reader?url=https%3A%2F%[Link]%2Frust...

}
}
}

impl IntoResponse for AppError {


fn into_response(self) -> Response {
let (status, error_message) = match self {
AppError::InvalidInput(msg) => (StatusCode::BAD_REQUEST,
msg),
AppError::InternalServerError =>
(StatusCode::INTERNAL_SERVER_ERROR, "Internal server
error".to_string()),
};

(status, error_message).into_response()
}
}

async fn get_tasks(State(state): State<AppState>) ->


Result<Json<Vec<Task>>, AppError> {
let tasks = sqlx::query_as::<_, Task>("SELECT id, title, description
FROM tasks")
.fetch_all(&*state.db_pool)
.await
.map_err(|_| AppError::InternalServerError)?;
Ok(Json(tasks))
}

async fn create_task(
State(state): State<AppState>,
Json(payload): Json<CreateTask>
) -> Result<(StatusCode, Json<Task>), AppError> {
if [Link].is_empty() {

8 of 17 7/31/25, 12:55
Build REST APIs in Rust with Axum | Rustaceans about:reader?url=https%3A%2F%[Link]%2Frust...

return Err(AppError::InvalidInput("Title cannot be


empty".to_string()));
}

let task = sqlx::query_as::<_, Task>(


"INSERT INTO tasks (title, description) VALUES ($1, $2)
RETURNING id, title, description"
)
.bind(&[Link])
.bind(&[Link])
.fetch_one(&*state.db_pool)
.await
.map_err(|_| AppError::InternalServerError)?;

Ok((StatusCode::CREATED, Json(task)))
}

#[tokio::main]
async fn main() {
let db_pool = PgPool::connect("postgres://user:pass@localhost/
db")
.await
.expect("Failed to connect to database");

let state = AppState {


db_pool: Arc::new(db_pool)
};

let app = Router::new()


.route("/tasks", get(get_tasks))
.route("/tasks", post(create_task))
.with_state(state);

let listener = tokio::net::TcpListener::bind("[Link]:3000")


.await
.unwrap();

9 of 17 7/31/25, 12:55
Build REST APIs in Rust with Axum | Rustaceans about:reader?url=https%3A%2F%[Link]%2Frust...

println!("Server running on [Link]


axum::serve(listener, app).[Link]();
}

5. Middleware for Observability

use axum::{
body::Body,
extract::State,
http::Request,
middleware::{self, Next},
response::{IntoResponse, Response},
routing::get,
Router,
};
use prometheus::{Counter, Histogram, HistogramOpts, Registry,
TextEncoder, Encoder};
use std::sync::Arc;
use std::time::Instant;
use tower_http::trace::TraceLayer;
use tracing_subscriber::{layer::SubscriberExt,
util::SubscriberInitExt};
use axum::http::StatusCode;

#[derive(Clone)]
struct MetricsState {
registry: Arc<Registry>,
request_counter: Counter,
request_duration: Histogram,
}

impl MetricsState {
fn new() -> Self {

10 of 17 7/31/25, 12:55
Build REST APIs in Rust with Axum | Rustaceans about:reader?url=https%3A%2F%[Link]%2Frust...

let registry = Registry::new();

let request_counter = Counter::new(


"http_requests_total",
"Total number of HTTP requests"
).unwrap();

let request_duration = Histogram::with_opts(


HistogramOpts::new(
"http_request_duration_seconds",
"HTTP request duration in seconds"
)
).unwrap();

[Link](Box::new(request_counter.clone())).unwrap();

[Link](Box::new(request_duration.clone())).unwrap();

Self {
registry: Arc::new(registry),
request_counter,
request_duration,
}
}
}

async fn metrics_middleware(
State(metrics): State<MetricsState>,
request: Request<axum::body::Body>,
next: Next,
) -> Response {
let start = Instant::now();

11 of 17 7/31/25, 12:55
Build REST APIs in Rust with Axum | Rustaceans about:reader?url=https%3A%2F%[Link]%2Frust...

metrics.request_counter.inc();

let response = [Link](request).await;

let duration = [Link]().as_secs_f64();


metrics.request_duration.observe(duration);

response
}

async fn metrics_handler(State(metrics): State<MetricsState>) ->


impl IntoResponse {
let encoder = TextEncoder::new();
let metric_families = [Link]();

match encoder.encode_to_string(&metric_families) {
Ok(output) => (StatusCode::OK, output),
Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Failed to
encode metrics".to_string()),
}
}

async fn hello_world() -> &'static str {


"Welcome to the Task API with Metrics!"
}

async fn tasks_handler() -> &'static str {


"Tasks endpoint"
}

#[tokio::main]
async fn main() {

12 of 17 7/31/25, 12:55
Build REST APIs in Rust with Axum | Rustaceans about:reader?url=https%3A%2F%[Link]%2Frust...

tracing_subscriber::registry()

.with(tracing_subscriber::EnvFilter::new("info,tower_http=debug"))
.with(tracing_subscriber::fmt::layer())
.init();

let metrics_state = MetricsState::new();

let app = Router::new()


.route("/", get(hello_world))
.route("/tasks", get(tasks_handler))
.route("/metrics", get(metrics_handler))
.layer(middleware::from_fn_with_state(
metrics_state.clone(),
metrics_middleware,
))
.layer(TraceLayer::new_for_http())
.with_state(metrics_state);

let listener = tokio::net::TcpListener::bind("[Link]:3000")


.await
.unwrap();

println!("Server running on [Link]


println!("Metrics available at [Link]

axum::serve(listener, app).[Link]();
}

This logs requests and exposes metrics at /metrics, critical for


production monitoring.

6. Robust Error Handling

13 of 17 7/31/25, 12:55
Build REST APIs in Rust with Axum | Rustaceans about:reader?url=https%3A%2F%[Link]%2Frust...

use axum::{http::StatusCode, response::{IntoResponse,


Response}, Json};
use serde::Serialize;
#[derive(Debug, Serialize)]
enum AppError {
InternalServerError,
NotFound,
InvalidInput(String),
}

impl IntoResponse for AppError {


fn into_response(self) -> Response {
let (status, error_message) = match self {
AppError::InternalServerError =>
(StatusCode::INTERNAL_SERVER_ERROR, "Internal Server
Error".to_string()),
AppError::NotFound => (StatusCode::NOT_FOUND,
"Resource Not Found".to_string()),
AppError::InvalidInput(msg) => (StatusCode::BAD_REQUEST,
format!("Invalid Input: {}", msg)),
};
(status, Json(error_message)).into_response()
}
}

7. WebAssembly for Client-Side Validation

use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn validate_task(title: &str) -> bool {
!title.is_empty() && [Link]() <= 100
}

14 of 17 7/31/25, 12:55
Build REST APIs in Rust with Axum | Rustaceans about:reader?url=https%3A%2F%[Link]%2Frust...

Compile and use in HTML:

cargo install wasm-pack


wasm-pack build --target web

<script type="module">
import init, { validate_task } from './pkg/task_validator.js';
async function run() {
await init();
[Link](validate_task("My Task")); // true
[Link](validate_task("")); // false
}
run();
</script>

This reduces server load by validating inputs client-side.

8. Testing the API

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


use axum_test::TestServer;
#[tokio::test]
async fn test_create_task() {
let app = Router::new().route("/tasks", post(create_task));
let server = TestServer::new(app).unwrap();
let response = server
.post("/tasks")
.json(&serde_json::json!({"title": "Test", "description": "Test
task"}))
.await;
assert_eq!(response.status_code(), StatusCode::CREATED);
}

Production Checklist

15 of 17 7/31/25, 12:55
Build REST APIs in Rust with Axum | Rustaceans about:reader?url=https%3A%2F%[Link]%2Frust...

• Structured Logging: Use tracing with ELK or Datadog

• Configuration: Manage secrets with dotenv or config crate

• Database: Use sqlx for async PostgreSQL queries

• Health Checks: Add GET /health returning 200 OK

• Graceful Shutdown:

use tokio::signal;
async fn shutdown_signal() {
signal::ctrl_c().[Link]("Failed to install Ctrl+C handler");
}

• Docker:

FROM rust:1.80 AS builder


WORKDIR /app
COPY . .
RUN cargo build
FROM debian:buster-slim
COPY
CMD ["task-api"]

• Monitoring: Expose Prometheus metrics at /metrics

• Security: Use JWT, HTTPS, and cargo audit

Common Pitfalls

• Forgetting Arc for shared state, causing ownership errors

• Not using Result for error propagation

• Blocking operations in async handlers (e.g., sync DB calls)

• Ignoring graceful shutdown, risking request loss

Conclusion

16 of 17 7/31/25, 12:55
Build REST APIs in Rust with Axum | Rustaceans about:reader?url=https%3A%2F%[Link]%2Frust...

Axum, powered by Rust’s safety and speed, is a game-changer for


production-ready REST APIs. With routing, extractors,
middleware, WebAssembly, and observability, you’re equipped to
build scalable, secure web services. The Rust community on X
praises Axum’s reliability in production, making it a top choice in
2025.

Ready to deploy your Axum API? Try the code, share your
project on X with #RustLang and #Axum, and join the Rust
community on Reddit or Discord.

What’s your experience with Axum? Share in the comments below!

17 of 17 7/31/25, 12:55

You might also like