Subscribe

Let's build a REST API in Rust 🦀 - Part 1

Oct 28, 2024

Today and the next 2 weeks we will take the FastAPI app we built in Python these past weeks, and rewrite it from scratch in Rust.

We will do it in mini steps, not to get lost. Because Rust is harder to learn than Python.

Let’s start!

Github repository


You can find all the source code in this repository
Give it a star ⭐ on Github to support my work

   

Steps

1. Install Rust and Cargo ⬇️

First, you'll need to install Rust and its package manager, Cargo. The installation process differs between Unix-based systems and Windows.

For Unix-based systems, like Linux or Mac:

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

For Windows:

1. Download the rustup-init.exe from https://rustup.rs/
2. Run the installer and follow the on-screen instructions
3. After installation, open a new command prompt to ensure the PATH is updated

For both systems, verify the installation with:

$ rustc --version
$ cargo --version

 

2. Create a New Rust Project 🆕

Create a new Rust project using Cargo. This will set up a new directory with all the necessary files for a Rust application:

$ cargo new taxi-data-api-rust
$ cd taxi-data-api-rust

This creates the following project structure:

├── Cargo.toml
└── src
    └── main.rs

What are these 2 files?

  • The Cargo.toml file is your project manifest, containing metadata and dependencies.

  • The src/main.rs is your application's entry point.

 

3. The Main Entry Point 🚪

Initially, your src/main.rs will contain a basic "Hello, World!" program. This is your application's entry point:

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

 

4. Running the Application 🏃🏻‍♂️‍➡️

Test your initial setup by running the application using Cargo:

$ cargo run

You should see "Hello, World!" printed to your console.

 

5. Adding actix-web

Actix-web is to Rust what FastAPI is to Python.

Actix-web is a powerful, high-performance web framework for Rust that's built on top of the actor framework Actix. It's known for being one of the fastest web frameworks available, capable of handling millions of requests per second.

Update your Cargo.toml to include actix-web

[package]
name = "taxi-data-api-rust"
version = "0.1.0"
edition = "2021"

[dependencies]
actix-web = "4.4"

or simply run

$ cargo add actix-web

Then, update src/main.rs to use async:

use actix_web::{App, HttpServer};

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    println!("Starting server...");
    Ok(())
}

There is a lot of new things in this last snippet, so let’s go line by line

First line

use actix_web::{App, HttpServer};

is Rust's way of importing specific types from a module, similar to Python's from import. In this case we import the App and HttpServer structs from the actix-web crate.

  • crate is Rust's equivalent to a Python package

  • struct in Rust is similar to a Python class, but focused purely on data structure. Think of it as a more powerful version of Python's dataclass.


Second line

#[actix_web::main]

is an attribute macro in Rust. Think of it like a Python decorator, that transforms our main function to handle the async runtime setup automatically.

 

Third line

async fn main() -> std::io::Result<()> {
    // ... 
}

This would be the Python equivalent to

# In Python
async def main() -> Optional[NoReturn]:
    try:
        # ...
        return None
    except IOError:
        # handle IO errors

where

  • async fn declares that this is an asynchronous function, just like Python's async def

  • std::io is the Rust standard library's I/O module, and

  • Result<T, E> is Rust's way of handling errors (similar to Python's try/except but at compile time). Result<()> means:

    • Success case: returns () (like Python's None)

    • Error case: returns an I/O error

 

What is async❓
Async programming in Rust is a way to handle concurrent operations without blocking the main thread. When you mark a function as async, it means the function can yield control back to the runtime when it's waiting for something (like I/O operations).

 

6. Creating the HttpServer 🖥 

Extend your main function to create and configure the HTTP server:

use actix_web::{App, HttpServer};

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
    })
    .bind(("0.0.0.0", port))?
    .run()
    .await
}

The Python equivalent for this would be

# In Python
app = FastAPI()
uvicorn.run(app, host="0.0.0.0", port=8080)

where

  • HttpServer::new(|| { ... }) creates a new server instance,

  • || { ... } is a closure (like a Python lambda function). The closure is used because Actix creates multiple workers, and each worker needs its own App instance

  • .bind(("0.0.0.0", 8080))? similar to setting host and port in Python. The ? operator is Rust's way of saying "if there's an error, return it immediately".

  • .run()starts the server an returns a Future (like a Python coroutine)

  • .await awaits for the server to complete (which it won't unless there's an error).

 

7. Adding a Health Endpoint 🩺

First, add the serde_json crate to your project

$ cargo add serde_json

Then define the function that will be called when a client hits the health endpoint.

async fn health() -> HttpResponse {
    HttpResponse::Ok().json(serde_json::json!({
        "status": "healthy",
    }))
}

where HttpResponse is an enum (a type that can be one of several variants) where Ok( is a variant representing a 200 OK response.

Finally, attach this function health to the /health route in your HttpServer

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .route("/health", web::get().to(health))
    })
    .bind(("0.0.0.0", port))?
    .run()
    .await
}

 

8. Testing the Health Endpoint 🕵️

Once your server is running, test the health endpoint using curl:

$ curl http://localhost:8080/health

You should receive a JSON response like:

{
    "status": "healthy",
}

Wow. That was A LOT of stuff!

Congratulations for it making it that far! 💪

 

Next steps 👣 

Next week we will add logging to the API and start building the business logic of our app.

 

Wanna Rust with me? 🦀

In 10 days (November 4th) we will start a new live course, Let’s Rust!

The goal of this LIVE course is to learn together (yes, you and me) how to build production-ready ML software using Rust.

I am no Rust expert. Faaaaaar from that.

In this adventure I help you. And you help me. From line to line. From bug to bug. We build, struggle, think, search, solve, and learn.

Again and again. Together.

Live.

2 times a week.
3 hours a day.
2 weeks.


12 hours of hard-core programming.

 

Let’s Rust?

Join today and get access to this live course and lifetime access to all sessions’ video recordings, slides and full source code. So you can follow at your own pace as well.

JOIN TODAY

 

Talk to you next week,

Enjoy the weekend

Pau

The Real World ML Newsletter

Every Saturday

For FREE

Join 19k+ ML engineers ↓