DEV Community

Chigozie Oduah
Chigozie Oduah

Posted on

Overview of Actix Web in Rust

Introduction

This article presents the actix-web framework for building restful apis with Rust. The actix-web framework helps you to quickly build fast and efficient restful apis with the Rust programming language.

Prerequisites

To understand this article, you need to have a working knowledge of Rust, and you need to have Rust installed in your computer.

What are REST APIs?

REST APIs are APIs that conforms to the Representational State Transfer design principle. REST APIs allow the use HTTP requests to control data and resources on a server. These APIs are very useful in building interfaces for other applications to access the resources on a server.

What is Actix Web?

Actix Web is a micro-framework for building REST APIs in Rust. This framework is small, simple, powerful, and fast. It is built with Rust, which is a reason for its speed.

It is very easy to pick up this framework, especially if you are a Rust programmer. If you don’t have much experience with Rust, It would be a little more challenging when you go into the details too early. This framework is very easy to start with, but takes some time to get used to it.

Why use Actix Web?

The following are the reasons to be using Actix Web for your APIs:

  • It is type safe, which means that it is strongly-typed, everywhere.
  • It comes with a lot of features, like static serving, middlewares, WebSockets and so on.
  • It is efficient, which means a single server can serve billions of users without leaving a large footprint.
  • It is pragmatic, which means it is easy to pick up for the majority of Rust developers.

A Simple API with Actix Web

In this section, you will learn how to create a simple API with Actix Web. This example will give you a basic understanding of how we create RESTful APIs using Actix Web.

Before creating the API, you need to initialize your project and add the version 4.0.1 Actix Web Dependency in your Cargo.toml file.

actix-web = "4.0.1"
Enter fullscreen mode Exit fullscreen mode

After adding the above to the Cargo.toml file, build the project to install the library and all its dependencies using the command below:

cargo build
Enter fullscreen mode Exit fullscreen mode

When the project is built, navigate to the src folder, and copy the below into the main.rs file.

use actix_web::{
    HttpServer,
    App,
    web,
    get,
    post,
    Responder,
};

#[get("/")]
async fn greet() -> impl Responder {
    "Hello"
}

#[post("/hello-post")]
async fn hello_post() -> impl Responder {
    let greeting = "Hello";
    format!("{}, from the post method", greeting)
}

async fn route_hello() -> impl Responder {
    let greeting = "Hi";
    format!("{greeting}, from route-hello")
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    println!("Server running...");
    HttpServer::new(|| {
      // Create and return an new app
        App::new()
            .service(greet)
            .service(hello_post)
            .route("/route-hello", web::get().to(route_hello))
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}
Enter fullscreen mode Exit fullscreen mode

The program above creates three routes when you run the program.

The first route is the root ( “/” ). When you send a get request to this route, it responds with “Hello”.

The second route is “/hello-post”. When you send a post request to this route, it responds with “Hello, from the post method”.

The third route is “/route-hello”. When you send a get request to this route, it responds with “Hello, from route-hello”

The third route is created using a more manual method of creating an API route. If you are building your API in a single file ( without splitting code into modules ), I recommend using the first two methods of creating routes. Tracking which function belongs to a route gets harder when all the functions are in a single file.

Path Parameters

Path parameters are used to specify the category of data to get or manipulate on the API. An example program that uses the path parameters is below:

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

#[get("/hello/{name}/{age}")]
async fn hello_1(path: web::Path<(String, u32)>) -> impl Responder {
    // The method call below moves "path" out of scope
    let (name, age) = path.into_inner();
    format!("Name: {name}\nAge: {age}")
}

#[get("/{person}/greet")]
async fn hello_2(path: web::Path<(String, )>) -> impl Responder {
    // path is borrowed here, which means you can use it again and again
    let person = &path.0;
    format!("{person}: Hello!")
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .service(hello_1)
            .service(hello_2)
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}
Enter fullscreen mode Exit fullscreen mode

The program above creates three routes when you run the program.

The first route is “/hello/{name}/{age}”. If you send a get request to “/hello/John/23”, you will receive the response below.

Name: John
Age: 23
Enter fullscreen mode Exit fullscreen mode

The second route is “/{person}/speak”. If you send a get request to “/Hannah/speak”, you will receive the response below.

Hanna: Hello!
Enter fullscreen mode Exit fullscreen mode

State variables

Rust doesn’t allow global variables, which means that you can’t have a general application state in the normal way. Application state is useful for sharing data between the API routes. To have an application state, create a struct variable and register it to the application.

An example API that uses an application state is given below.

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

// The App State's structure
struct AppState {
  name: String,
  numbers: Vec<u32>,
}

#[get("/")]
async fn display(data: web::Data<AppState>) -> impl Responder {
  // data is a borrowed version of the state
  format!("Name: {}\nNumbers: {:?}", &data.name, &data.numbers)
}

#[get("/{query}")]
async fn access_data(path: web::Path<(String, )>, data: web::Data<AppState>) -> impl Responder {
  let query = &path.0;

  if query == "list" {
    return format!("{:?}", &data.numbers);
  } else {
    return "not found".to_string();
  }
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
  HttpServer::new(|| {
    App::new()
      .app_data(web::Data::new(
          // Register the app state
          AppState {
            name: "numbers".to_string(),
            numbers: vec![10, 20, 30],
          }
      ))
      .service(display)
      .service(access_data)
  })
  .bind(("127.0.0.1", 8080))?
  .run()
  .await
}
Enter fullscreen mode Exit fullscreen mode

The app state makes it possible to use the “name” and “numbers” variables across all routes.

Mutable State variables

The application state created in the last section is immutable, which means it cannot change. By default, Rust makes these variables immutable to prevent them from changing at runtime. To make the app state mutable you need to use Mutex.

Mutex is a structure that is provided by the standard library, and can be used to add mutable state variables to the application’s general state.

To create a mutable state variable, we create a normal struct and initialize each field as a Mutex<T> type. The Mutex structure is generic, and can be initialized with any other data structure.

An example of a simple API with mutable state variables is given below:

use std::sync::Mutex;
use actix_web::{
  HttpServer,
  App,
  get,
  web,
  Responder,
};

struct AppState {
  numbers: Mutex<Vec<u32>>,
}

#[get("/add/{number}")]
async fn add_number(path: web::Path<(u32, )>, data: web::Data<AppState>) -> impl Responder {
  /* Add the number from the path parameter to the end of "numbers"
     in the app state */

  let mut number = data.numbers.lock().unwrap();
  let number_to_add = path.into_inner().0;
  number.push( number_to_add );

  format!("Current list: {:?}", number)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {

  // Mutable app state should be created outside
  let state = web::Data::new(AppState {
    numbers: Mutex::new(vec![10, 20, 30])
  });

  HttpServer::new(move || {
    App::new()
      .app_data(state.clone())   // <- move created app state to closure and register
      .service(add_number)
  })
  .bind(("127.0.0.1", 8080))?
  .run()
  .await
}
Enter fullscreen mode Exit fullscreen mode

In the main function of the program above, the state variable was created outside the closure because web::Data uses an Arc internally. Creating the app state prevents the app from creating more Arc’s.

Conclusion

If you want to understand more about the Actix Web framework be sure to check its documentation. I hope you found this article very useful.

Thanks for reading! And Happy Hacking!

Top comments (0)