Axum is a web application framework that focuses on ergonomics and type safety, making it easier to build HTTP services. It leverages the powerful Tokio runtime for asynchronous processing, allowing for high-performance applications. In this guide, we will create a simple microservice that provides a RESTful API to manage a list of tasks.

Setting Up the Project

First, ensure you have Rust installed on your machine. You can install Rust using rustup. Once Rust is set up, create a new project:

cargo new task_manager
cd task_manager

Next, add the necessary dependencies in your Cargo.toml file:

[dependencies]
axum = "0.6"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1.0"

This configuration includes Axum for the web framework, Tokio for async support, and Serde for serialization and deserialization of JSON data.

Defining the Data Model

We will define a simple data model for our tasks. Create a new file named models.rs in the src directory:

// src/models.rs
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Clone)]
pub struct Task {
    pub id: usize,
    pub title: String,
    pub completed: bool,
}

This struct represents a task with an ID, a title, and a completion status. The Serialize and Deserialize traits allow us to convert between the Task struct and JSON.

Implementing the Microservice

Now, let's implement the main functionality of our microservice. Open src/main.rs and set up the Axum application:

// src/main.rs
use axum::{
    extract::{Json, Path},
    response::IntoResponse,
    routing::{get, post},
    Router,
};
use serde_json::json;
use std::sync::{Arc, Mutex};
use models::Task;

mod models;

type Db = Arc<Mutex<Vec<Task>>>;

#[tokio::main]
async fn main() {
    let db: Db = Arc::new(Mutex::new(Vec::new()));
    
    let app = Router::new()
        .route("/tasks", post(create_task).get(list_tasks))
        .route("/tasks/:id", get(get_task).delete(delete_task))
        .layer(axum::AddExtensionLayer::new(db));

    axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

async fn create_task(
    Json(task): Json<Task>,
    Extension(db): Extension<Db>,
) -> impl IntoResponse {
    let mut tasks = db.lock().unwrap();
    let id = tasks.len() + 1;
    let new_task = Task {
        id,
        title: task.title,
        completed: task.completed,
    };
    tasks.push(new_task.clone());
    (axum::http::StatusCode::CREATED, Json(new_task))
}

async fn list_tasks(Extension(db): Extension<Db>) -> impl IntoResponse {
    let tasks = db.lock().unwrap();
    Json(tasks.clone())
}

async fn get_task(Path(id): Path<usize>, Extension(db): Extension<Db>) -> impl IntoResponse {
    let tasks = db.lock().unwrap();
    if let Some(task) = tasks.iter().find(|&t| t.id == id) {
        Json(task.clone())
    } else {
        (axum::http::StatusCode::NOT_FOUND, "Task not found")
    }
}

async fn delete_task(Path(id): Path<usize>, Extension(db): Extension<Db>) -> impl IntoResponse {
    let mut tasks = db.lock().unwrap();
    if let Some(pos) = tasks.iter().position(|t| t.id == id) {
        tasks.remove(pos);
        (axum::http::StatusCode::NO_CONTENT, "")
    } else {
        (axum::http::StatusCode::NOT_FOUND, "Task not found")
    }
}

Explanation of Key Components

  • Router: The Router is defined to handle different routes. We set up routes for creating, listing, retrieving, and deleting tasks.
  • State Management: We use Arc<Mutex<Vec<Task>>> to manage shared state safely across asynchronous requests. The Mutex ensures that only one thread can access the task list at a time.
  • Handlers: Each route has an associated handler function that processes incoming requests. For example, create_task handles POST requests to add a new task.

Running the Microservice

To run your microservice, use the following command:

cargo run

You can interact with the API using tools like curl or Postman. Here are some example requests:

Create a Task

curl -X POST http://localhost:3000/tasks -H "Content-Type: application/json" -d '{"title": "Learn Rust", "completed": false}'

List All Tasks

curl http://localhost:3000/tasks

Get a Task by ID

curl http://localhost:3000/tasks/1

Delete a Task

curl -X DELETE http://localhost:3000/tasks/1

Conclusion

In this tutorial, you've learned how to build a simple microservice in Rust using the Axum framework. By leveraging Rust's safety and performance features, you can create robust and efficient web applications. This microservice can be expanded with additional features such as authentication, database integration, or more complex business logic.

Learn more with useful resources: