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!");}
tutorials
All code samples, examples and projects for all Threenine articles and tutorials for the threenine.blog
threenine/tutorialsAdd 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.
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.