How to build Web based API with Rust
Rust

How to build Web based API with Rust (part 1)

A getting started guide to developing web based API with rust and actix-web.

Gary Woodfine

Gary Woodfine

24 Sept 2024

At threenine, we were recently engaged to build a highly performant web-based API for a large retail client. Our client was keen to try and squeeze every ounce of performance as possible, with even requesting that we explore and develop the API using Rust. Now Rust, as you may know, Rust is blazingly fast and memory-efficient: with no runtime or garbage collector, it can power performance-critical services, run on embedded devices, and easily integrate with other languages. However, from all the background we knew of Rust at the time, it seemed it was a really great fit for system-based software, but we had up until this point not seen any Line of business applications or Web API's out in the world. We were sure it is possible they existed, but none of our existing clients seemed to have any in real-world implementations.

At the time, the team here at threenine, also had very limited experience with Rust, but we were up for the challenge and were looking forward to learning and playing with Rust. In this post, we provide some useful tips we learned early on in our journey to help others get started too.

Getting started

We won't go through the details of installing Rust, because quite frankly the Official Rust Documentation is best source to turn to for this, along with taking the time to read and learn from The Rust Reference

The best way to start a new Rust project is to make use of the Cargo CLI, so lets go ahead and do that to create our project by running the following command in the terminal.

cargo new rust-api

This will create a minimal project template basically consisting of one file and one line of code, with the usual "Hello World" introduction.

fn main() { println!("Hello, world!");}

Add the Actix-web and serde frameworks

After spending some time analyzing and reading various articles and benchmarks we came to the conclusion that the consensus within the Rust community is that Actix-web is the preferred and most popular Web Framework for Rust. The key thing for us, was that it was also the one with the most Documentation, and from what we could determine it seemed it had the largest community of users. Which is important when you're learning the Framework.

To add Actix-web to your project is as simple as executing the following cargo command

cargo add actix-web

Implementing your first endpoint

We'll get started with the obligatory Hello World example. We'll create a very basic endpoint to get familiar with some basic concepts.

In our src directory we'll create a new directory which we'll call routes. The purpose of this directory, is basically for code organization more than anything else. You can create this directory, using whichever mechanism you prefer, but for this article we'll use the terminal.

mkdir src/routes

Once your directory is created we can create our Rust file that will contain our initial greetings endpoints. We'll once again use the terminal to do this.

touch src/routes/greetings.rs

We can now add some code to implement our basic logic required by our endpoint.

use actix_web::{get, HttpResponse, Responder};

#[get("/")]
async fn hello_world() -> impl Responder {
    HttpResponse::Ok().body("Hello World")
}

The use actix_web::{get, web, HttpResponse, Responder};: imports several useful components from the actix_web crate.

  • get: A macro for defining HTTP GET request handlers.
  • web: A module for working with web-related types.
  • HttpResponse: A type used to construct HTTP responses.
  • Responder: A trait for types that can provide a response.

#[get("/")] is an attribute macro that specifies that this function should handle HTTP GET requests to the root path (/).

async fn hello_world() -> impl Responder defines an asynchronous function named hello_world that returns a type that implements the Responder trait.

HttpResponse::Ok().body("Hello World") creates an HTTP response with a status code of 200 (OK) and a body containing the string "Hello World".

This is all the code for our endpoints for now. We can now go ahead and edit our main.rs to wire our Http Server. Remove all current code in your main.rs file and replace it with the below code.

use actix_web::{ App, HttpServer};
mod routes {
    pub mod greetings;
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .service(routes::greetings::hello_world)
    }).bind(("127.0.0.1", 8080))?
      .run()
      .await
}

use actix_web::{ App, HttpServer}; uses Rust's use keyword to bring specific components from the actix_web crate into scope.

  • actix_web: This is a crate for the Actix Web framework, which is a powerful, pragmatic, and extremely fast web framework for Rust. It is commonly used to build web servers and web applications.
  • App: The App struct is used to configure the application's services and middleware. It allows you to define routes, middlewares, and various other settings for your web application.
  • HttpServer: The HttpServer struct is responsible for creating and running the HTTP server. It listens for incoming connections on a specified address and serves your application by passing requests to the App instance.
mod routes {
    pub mod greetings;
}

Defines a module structure within a Rust program. In Rust, a module is a way to group related items (such as functions, structs, enums, traits, etc.) together. Modules also help in organizing code in a more readable and maintainable way.

Inside the routes module, there's a declaration for another module named greetings. The pub keyword stands for public, which means this greetings module is being made public. Making it public allows other modules outside routes to access the items defined in greetings.

The semicolon ; at the end means that greetings is declared but not defined within the same file. Typically, this would refer to another file (like greetings.rs or a greetings directory with a mod.rs file) where the actual content of the greetings module is defined.

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .service(routes::greetings::hello_world)
    })
        .bind(("127.0.0.1", 8080))?
        .run()
        .await
}

#[actix_web::main] This attribute macro specifies that the main function will be an asynchronous entry point for the program, using Actix's runtime. It is a shorthand for setting up the actix_rt::System and starting it.

async fn main() -> std::io::Result<()> is an asynchronous main function returning a Result type. The Result will contain an Ok value of type () (unit) if the program runs successfully or an Err value if an I/O error occurs.

HttpServer::new(|| { ... }) This creates a new instance of an HTTP server. The argument is a closure that constructs an Actix App instance.

Inside this closure: App::new() creates a new Actix App, which is an application factory for configuring routes and services.

.service(routes::greetings::hello_world) registers a service (a route handler) with the App. The hello_world function is expected to be defined in the routes::greetings module and will be used to handle specific HTTP requests.

.bind(("127.0.0.1", 8080))? binds the server to the address 127.0.0.1 (localhost) on port 8080. The ? operator propagates any errors that occur during binding.

.run().await starts the HTTP server and runs it asynchronously. The await keyword makes the function pause until the server runs to completion.

Build & Run

We can now build and run our simple project, to get it working on our local system. To do this we'll use following cargo commands from the root of our project directory.

First build your application

cargo build

Then we can run our project using

cargo run

You can test your fantastic new API by using your browser and Navigating to http://localhost:8080.

Conclusion

Despite this being a very simple app, and is not going to win any innovation awards, it has helped us to get familiar with a number of concepts within rust and has a foundation to start building more onto it.

Gary Woodfine
Gary Woodfine

Back-end software engineer

Experienced software developer, specialising in API Development, API Design API Strategy and Web Application Development. Helping companies thrive in the API economy by offering a range of consultancy services, training and mentoring.

Need help starting your API project?

We'll help you with your API First strategy, Design & Development