All articles
An Introduction to Actix: A Rust Web Framework
Web & Networking

An Introduction to Actix: A Rust Web Framework

Actix is a robust, pragmatic, fast Rust framework for web application building. It utilizes the actor model for managing state and…

By Luis SoaresMay 31, 2023Original on Medium

Actix is a robust, pragmatic, fast Rust framework for web application building. It utilizes the actor model for managing state and concurrency, borrowing from Erlang's philosophy. Actix is also based on a small, easy-to-use actor system, providing a cleaner and more intuitive interface.

Actix web is built on top of Actix, a high-performance, flexible, easy-to-use general-purpose actor framework. It provides tools and libraries to build HTTP servers and web applications.

Advantages of Actix Web

  1. Performance: Actix web is designed to be lightweight and fast. It's one of the fastest web frameworks available in any programming language.
  2. Safety: Actix and Rust both have a strong emphasis on type safety, memory safety, and concurrency, meaning your code will be both efficient and safe from common programming errors.
  3. Asynchronous: Actix Web supports asynchronous request handlers, which makes it ideal for programming web applications requiring handling many concurrent connections, such as real-time or microservices.
  4. Middleware System: Actix offers a powerful middleware system, allowing developers to extend and modify the request-response processing pipeline.
  5. Extensible: It has a robust set of modules and libraries for session handling, form processing, cookie handling, testing, etc., making the development process easy and effective.

CRUD RESTful Implementation with Actix Web

We'll implement a simple book management API, allowing us to Create, Read, Update, and Delete books.

Setup

Before we start, we need to have Rust and Actix installed. You can download Rust from the official website.

Create a new project with:

cargo new book_manager
cd book_manager

In your Cargo.toml, add the following dependencies:

[dependencies]
actix-web = "3"
serde = { version = "1", features = ["derive"] }

Define the Model

Let's start by defining our Book struct in src/main.rs:

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct Book {
    id: u32,
    title: String,
    author: String,
}

Creating Endpoints

Now, let's define our endpoints. For now, we'll set up our HTTP server and create stub handlers for each CRUD operation.

use actix_web::{web, App, HttpServer, Responder, HttpResponse};

async fn create_book(_book: web::Json<Book>) -> impl Responder {
    HttpResponse::Ok().json("Create book")
}

async fn read_book(_id: web::Path<u32>) -> impl Responder {
    HttpResponse::Ok().json("Read book")
}

async fn update_book(_id: web::Path<u32>, _book: web::Json<Book>) -> impl Responder {
    HttpResponse::Ok().json("Update book")
}

async fn delete_book(_id: web::Path<u32>) -> impl Responder {
    HttpResponse::Ok().json("Delete book")
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .route("/books", web::post().to(create_book))
            .route("/books/{id}", web::get().to(read_book))
            .route("/books/{id}", web::put().to(update_book))
            .route("/books/{id}", web::delete().to(delete_book))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

This will set up a server on localhost port 8080 and handle the CRUD operations at different endpoints. web::Json and web::Path are extractor types which extract data from a request's body or path.

Practice what you learned

Reinforce this article with hands-on coding exercises and AI-powered feedback.

View all exercises

Implementing Handlers

For the sake of simplicity, we'll use a simple HashMap as our database:

use std::collections::HashMap;
use std::sync::Mutex;
use actix_web::{web, App, HttpServer, Responder, HttpResponse};
use std::sync::Arc;

struct AppState {
    books: Mutex<HashMap<u32, Book>>,
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let shared_data = web::Data::new(AppState {
        books: Mutex::new(HashMap::new()),
    });

    HttpServer::new(move || {
        App::new()
            .app_data(shared_data.clone())
            .route("/books", web::post().to(create_book))
            .route("/books/{id}", web::get().to(read_book))
            .route("/books/{id}", web::put().to(update_book))
            .route("/books/{id}", web::delete().to(delete_book))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

Now, we can implement our handlers:

async fn create_book(book: web::Json<Book>, data: web::Data<AppState>) -> impl Responder {
    let mut books = data.books.lock().unwrap();
    books.insert(book.id, book.into_inner());
    HttpResponse::Ok().json("Created book")
}

async fn read_book(id: web::Path<u32>, data: web::Data<AppState>) -> impl Responder {
    let books = data.books.lock().unwrap();
    match books.get(&id.into_inner()) {
        Some(book) => HttpResponse::Ok().json(book),
        None => HttpResponse::NotFound().json("Book not found"),
    }
}

async fn update_book(id: web::Path<u32>, book: web::Json<Book>, data: web::Data<AppState>) -> impl Responder {
    let mut books = data.books.lock().unwrap();
    if books.contains_key(&id.into_inner()) {
        books.insert(book.id, book.into_inner());
        HttpResponse::Ok().json("Updated book")
    } else {
        HttpResponse::NotFound().json("Book not found")
    }
}

async fn delete_book(id: web::Path<u32>, data: web::Data<AppState>) -> impl Responder {
    let mut books = data.books.lock().unwrap();
    match books.remove(&id.into_inner()) {
        Some(_) => HttpResponse::Ok().json("Deleted book"),
        None => HttpResponse::NotFound().json("Book not found"),
    }
}

This will create a basic API with CRUD operations. Note that this is a simple example, and in a real-world application, you would want to use a real database, handle errors more gracefully, and add authentication.

More on Actix

While the example above illustrates a simple use case, Actix can handle more complex scenarios. It supports various forms of communication, such as WebSockets and Server-Sent Events (SSE). Additionally, Actix provides extensive middleware support, allowing the extension and customization of request-response pipelines.

Middleware in Actix

Middleware components in Actix are reusable software components that can handle requests and responses, modify them, and perform various operations like logging, authentication, session management, etc.

Creating a middleware in Actix Web is as simple as implementing the Middleware trait for your component. Once done, you can add your middleware to the application with wrap or wrap_fn.

Testing in Actix

Actix provides a test module to write unit tests for your application. It includes a TestServer and call_service function, which simulates an HTTP client to test your application's functionality.

#[actix_rt::test]
async fn test_read_book() {
    let mut app = test::init_service(App::new().route("/books/{id}", web::get().to(read_book))).await;
    let req = test::TestRequest::get().uri("/books/1").to_request();
    let resp = test::call_service(&mut app, req).await;
    assert!(resp.status().is_success());
}

This is a basic test to check if the /books/{id} route is functioning correctly. Real world testing would involve more complex scenarios, including checking if the returned response matches the expected output.

Integrating Actix with Databases

Actix being a web framework, doesn't directly support databases. Still, it's common to use databases in a web service, and Rust has several libraries to work with databases, like Diesel and SQLx. These can easily be integrated with Actix Web for creating full-fledged web services.

Error Handling

Error handling is crucial to any web application. In Actix, the Result type is usually used for returning and propagating errors. If an error occurs in a handler, Actix will stop processing and return a server error to the client.

Practice what you learned

Reinforce this article with hands-on coding exercises and AI-powered feedback.

View all exercises

Want to practice Rust hands-on?

Go beyond reading — solve interactive exercises with AI-powered code review on Rust Lab.