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
- Performance: Actix web is designed to be lightweight and fast. It’s one of the fastest web frameworks available in any programming language.
- 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.
- 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.
- Middleware System: Actix offers a powerful middleware system, allowing developers to extend and modify the request-response processing pipeline.
- 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.
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.
Check out more articles about Rust in my Rust Programming Library!
Conclusion
Actix is a powerful, safe, and efficient framework for building web applications in Rust. It combines Rust’s emphasis on zero-cost abstractions and memory safety with a pragmatic, easy-to-use approach to web service development.
Its actor-based design provides a clean and intuitive approach to managing state and handling concurrency. At the same time, its extensive middleware support and integration with other Rust libraries allow for robust, feature-rich web applications. Whether you’re a newcomer to Rust or an experienced developer, Actix offers a compelling option for web development.
Stay tuned, and happy coding!
Check out more articles about Rust in my Rust Programming Library!
Visit my Blog for more articles, news, and software engineering stuff!
Follow me on Medium, LinkedIn, and Twitter.
Check out my most recent book — Application Security: A Quick Reference to the Building Blocks of Secure Software.
All the best,
Luis Soares
CTO | Head of Engineering | Blockchain Engineer | Solidity | Rust | Smart Contracts | Web3 | Cyber Security
#rust #programming #language #actix #web #framework #rest #API #microservices #web3 #security #privacy #confidentiality #cryptography #softwareengineering #softwaredevelopment #coding #software #safety #development #building