# Rust Development: Creating a REST API with Actix Web for Beginners

## TL;DR: Le code

%[https://github.com/dirien/quick-bites/tree/main/rust-actix-web-rest-api] 

## Introduction

This article walks through building a REST API in Rust using the Actix Web framework. The best way to learn something new? Try it yourself, make mistakes, and improve.

We're going to implement the REST API for the classic TODO app.

The API will have the following endpoints:

* Create a new TODO item
    
* Get a TODO item by its unique identifier
    
* Delete a TODO item by its unique identifier
    
* Get all TODO items
    
* Update an existing TODO item
    

Before we start implementing, let's cover some REST API basics.

## What is a REST API?

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1678537767798/8f5a0e20-b46f-4d7d-b5a6-ef4bc10b125c.png align="center")

REST stands for REpresentational State Transfer. [Roy Fielding](https://en.wikipedia.org/wiki/Roy_Fielding) introduced the term in his doctoral dissertation "**Architectural Styles and the Design of Network-based Software Architectures**". REST is an architectural style for building distributed systems (often web services), not a standard. REST-based systems communicate using HTTP.

RESTful systems have two parts: the client initiates requests for resources, and the server sends them back.

### Architectural Constraints of a REST API:

Six constraints apply when building a REST API (one is optional):

* Uniform Interface
    
* Stateless
    
* Cacheable
    
* Client-Server
    
* Layered System
    
* Code on Demand (optional)
    

Let's look at each one.

#### Uniform Interface

Every REST API needs a uniform interface. This means there's a consistent way to interact with server resources regardless of the client device or application type.

Fielding defined four properties for this:

* Identification of resources
    
* Manipulation of resources through representations
    
* Self-descriptive messages
    
* Hypermedia as the engine of application state (HATEOAS)
    

In practice:

* Use nouns instead of verbs in resource names: `/todos` instead of `/getTodos`
    
* Use HTTP methods like **GET**, **POST**, **PUT**, and **DELETE** to specify the operation
    
* Use plural resource names: `/todos` instead of `/todo`
    
* Send appropriate HTTP status codes: `200` for success, `201` for resource creation, `404` when not found
    

#### Stateless

The server doesn't store any context between requests. All state needed to handle a request is part of the request itself. This helps with scaling and availability.

#### Cacheable

A good REST API should support caching to reduce unnecessary network traffic. The tradeoff: clients might occasionally receive stale data.

#### Client-Server

Client and server maintain strict separation of concerns. Each can evolve independently.

#### Layered System

You can use a layered architecture: one server for the API, another for data storage, another for authentication.

#### Code on Demand (Optional)

The server can send executable code to the client. I've never used this and honestly don't know when it would be useful.

## What is Actix Web?

Actix Web is a popular Rust web framework. It was originally built on the Actix actor framework but is now independent. Applications built with Actix Web expose an HTTP server in a native Rust binary.

%[https://github.com/actix/actix-web] 

## Prerequisites

You'll need:

* [Rust](https://www.rust-lang.org)
    
* An IDE or text editor
    

## Initialize the project

```shell
cargo init
```

Add the `actix-web`, `serde`, `chrono`, `uuid`, `env_logger`, and `log` crates:

```shell
cargo add actix-web
cargo add serde --features derive
cargo add chrono --features serde
cargo add uuid --features v4
cargo add env_logger
cargo add log
```

We add `env_logger` and `log` to enable request logging via the actix-web Logger middleware.

## Creating the application entry point

Add the following code to `main.rs`:

```rust
use actix_web::{get, web, App, HttpResponse, HttpServer, Responder, Result};
use serde::Serialize;

#[derive(Serialize)]
pub struct Response {
    pub message: String,
}

#[get("/health")]
async fn healthcheck() -> impl Responder {
    let response = Response {
        message: "Everything is working fine".to_string(),
    };
    HttpResponse::Ok().json(response)
}

async fn not_found() -> Result<HttpResponse> {
    let response = Response {
        message: "Resource not found".to_string(),
    };
    Ok(HttpResponse::NotFound().json(response))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| App::new().service(healthcheck).default_service(web::route().to(not_found)))
        .bind(("127.0.0.1", 8080))?
        .run()
        .await
}
```

Here's what this does:

* Defines a `Response` struct for sending JSON responses
    
* Creates a `health` handler that clients can probe to check if the server is running
    
* Uses the `#[actix_web::main]` macro to run `main` as an async function with the actix-web runtime. The `main` function:
    
    * Creates a server using `HttpServer` with an `App` instance that registers routes
        
    * Registers a default `not_found` handler for unmatched routes
        
    * Binds to `localhost:8080` and starts the server
        

Run the application:

```bash
cargo run
```

Test the health endpoint:

```bash
curl localhost:8080/health -vvv
*   Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /health HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.79.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< content-length: 59
< content-type: application/json
< date: Sat, 11 Mar 2023 08:27:37 GMT
<
* Connection #0 to host localhost left intact
{"status":"success","message":"Everything is working fine"}
```

## Organizing the code with modules

Rust modules let you split code into logical units and manage visibility between them.

Create three folders under `src`: `api`, `models`, and `repository`. Add a `mod.rs` file to each (all items in a module are private by default):

```bash
mkdir src/api src/models src/repository
touch src/api/mod.rs src/models/mod.rs src/repository/mod.rs
```

Reference these modules in `main.rs`:

```rust
use actix_web::{get, web, App, HttpResponse, HttpServer, Responder, Result};
use serde::Serialize;

mod api;
mod models;
mod repository;

#[derive(Serialize)]
pub struct Response {
    pub status: String,
    pub message: String,
}
// ...
```

## Creating the first API endpoint

Create a model for the `Todo` resource in `src/models/todo.rs`:

```rust
use chrono::prelude::{DateTime, Utc};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Todo {
    pub id: Option<String>,
    pub title: String,
    pub description: Option<String>,
    pub created_at: Option<DateTime<Utc>>,
    pub updated_at: Option<DateTime<Utc>>,
}
```

The `derive` macro generates implementations for formatting, cloning, and serialization. The `pub` modifier makes fields accessible from other modules.

Register this file in `src/models/mod.rs`:

```rust
pub mod todo;
```

Now for the database logic. We'll use an in-memory vector with mutexes for thread safety. (A future article will cover real databases with actix-web.)

Create `src/repository/database.rs`:

```rust
use std::fmt::Error;
use chrono::prelude::*;
use std::sync::{Arc, Mutex};

use crate::models::todo::Todo;

pub struct Database {
    pub todos: Arc<Mutex<Vec<Todo>>>,
}

impl Default for Database {
    fn default() -> Self {
        Self::new()
    }
}

impl Database {
    pub fn new() -> Self {
        Database {
            todos: Arc::new(Mutex::new(vec![])),
        }
    }

    pub fn create_todo(&self, todo: Todo) -> Result<Todo, Error> {
        let mut todos = self.todos.lock().unwrap();
        let id = uuid::Uuid::new_v4().to_string();
        let created_at = Utc::now();
        let updated_at = Utc::now();
        let todo = Todo {
            id: Some(id),
            created_at: Some(created_at),
            updated_at: Some(updated_at),
            ..todo
        };
        todos.push(todo.clone());
        Ok(todo)
    }
}
```

Here's what this does:

* Defines a `Database` struct with a `todos` field of type `Arc<Mutex<Vec<Todo>>>`. `Arc` provides thread-safe reference counting; `Mutex` ensures only one thread accesses the data at a time.
    
* Implements the `Default` trait (good Rust practice, avoids clippy warnings)
    
* The `new` function creates an instance with an empty, thread-safe vector
    
* `create_todo` locks the mutex, generates a UUID, sets timestamps, adds the todo to the vector, and returns it
    

Now create the API endpoint in `src/api/api.rs`:

```rust
use actix_web::{web, get, post, delete, put, HttpResponse};
use crate::{models::todo::Todo, repository::database::Database};

#[post("/todos")]
pub async fn create_todo(db: web::Data<Database>, new_todo: web::Json<Todo>) -> HttpResponse {
    let todo = db.create_todo(new_todo.into_inner());
    match todo {
        Ok(todo) => HttpResponse::Created().json(todo),
        Err(err) => HttpResponse::InternalServerError().body(err.to_string()),
    }
}

pub fn config(cfg: &mut web::ServiceConfig) {
    cfg.service(
        web::scope("/api")
            .service(create_todo)
    );
}
```

Note: We use `HttpResponse::Created()` (201) instead of `HttpResponse::Ok()` (200). The HTTP spec says 201 is the correct status code when creating a new resource.

The `config` function registers all API endpoints under `/api` using `web::scope`.

Wire everything together in `src/main.rs`:

```rust
#[actix_web::main]
async fn main() -> std::io::Result<()> {
    env_logger::init();

    let todo_db = repository::database::Database::new();
    let app_data = web::Data::new(todo_db);

    HttpServer::new(move ||
        App::new()
            .app_data(app_data.clone())
            .configure(api::api::config)
            .service(healthcheck)
            .default_service(web::route().to(not_found))
            .wrap(actix_web::middleware::Logger::default())
    )
        .bind(("127.0.0.1", 8080))?
        .run()
        .await
}
```

The `env_logger::init()` call activates logging. Run with `RUST_LOG=info cargo run` to see request logs.

Run the application:

```bash
cargo run
```

Create a new TODO:

```bash
curl -X POST -H "Content-Type: application/json" -d '{"title": "My first todo", "description": "This is my first todo"}' http://localhost:8080/api/todos
```

Expected output:

```json
{
  "id": "d70053a9-721d-4c20-9a27-b26b4fbaecae",
  "title": "My first todo",
  "description": "This is my first todo",
  "created_at": "2023-03-11T10:33:56.441332Z",
  "updated_at": "2023-03-11T10:33:56.441390Z"
}
```

Now we can implement the remaining API endpoints.

## Implementing the remaining API endpoints

### GET `/todos`

Add to `src/api/api.rs`:

```rust
#[get("/todos")]
pub async fn get_todos(db: web::Data<Database>) -> HttpResponse {
    let todos = db.get_todos();
    HttpResponse::Ok().json(todos)
}
```

Add `get_todos` to the `config` function:

```rust
pub fn config(cfg: &mut web::ServiceConfig) {
    cfg.service(
        web::scope("/api")
            .service(create_todo)
            .service(get_todos)
    );
}
```

Add to `src/repository/database.rs`:

```rust
impl Database {
    // ...

    pub fn get_todos(&self) -> Vec<Todo> {
        let todos = self.todos.lock().unwrap();
        todos.clone()
    }
}
```

### GET `/todos/{id}`

Add to `src/api/api.rs`:

```rust
#[get("/todos/{id}")]
pub async fn get_todo_by_id(db: web::Data<Database>, id: web::Path<String>) -> HttpResponse {
    let todo = db.get_todo_by_id(&id);
    match todo {
        Some(todo) => HttpResponse::Ok().json(todo),
        None => HttpResponse::NotFound().body("Todo not found"),
    }
}
```

Add `get_todo_by_id` to the `config` function:

```rust
pub fn config(cfg: &mut web::ServiceConfig) {
    cfg.service(
        web::scope("/api")
            .service(create_todo)
            .service(get_todos)
            .service(get_todo_by_id)
    );
}
```

Add to `src/repository/database.rs`:

```rust
impl Database {
    // ...

    pub fn get_todo_by_id(&self, id: &str) -> Option<Todo> {
        let todos = self.todos.lock().unwrap();
        todos.iter().find(|todo| todo.id == Some(id.to_string())).cloned()
    }
}
```

### PUT `/todos/{id}`

Add to `src/api/api.rs`:

```rust
#[put("/todos/{id}")]
pub async fn update_todo_by_id(db: web::Data<Database>, id: web::Path<String>, updated_todo: web::Json<Todo>) -> HttpResponse {
    let todo = db.update_todo_by_id(&id, updated_todo.into_inner());
    match todo {
        Some(todo) => HttpResponse::Ok().json(todo),
        None => HttpResponse::NotFound().body("Todo not found"),
    }
}
```

Add `update_todo_by_id` to the `config` function:

```rust
pub fn config(cfg: &mut web::ServiceConfig) {
    cfg.service(
        web::scope("/api")
            .service(create_todo)
            .service(get_todos)
            .service(get_todo_by_id)
            .service(update_todo_by_id)
    );
}
```

Add to `src/repository/database.rs`:

```rust
impl Database {
    // ...

    pub fn update_todo_by_id(&self, id: &str, todo: Todo) -> Option<Todo> {
        let mut todos = self.todos.lock().unwrap();
        let index = todos.iter().position(|t| t.id == Some(id.to_string()))?;
        let existing_todo = &todos[index];
        let updated_todo = Todo {
            id: Some(id.to_string()),
            title: todo.title,
            description: todo.description,
            created_at: existing_todo.created_at,
            updated_at: Some(Utc::now()),
        };
        todos[index] = updated_todo.clone();
        Some(updated_todo)
    }
}
```

Note: We explicitly preserve `created_at` from the existing todo. Using `..todo` would overwrite it with `None` since the incoming JSON doesn't include `created_at`.

### DELETE `/todos/{id}`

Add to `src/api/api.rs`:

```rust
#[delete("/todos/{id}")]
pub async fn delete_todo_by_id(db: web::Data<Database>, id: web::Path<String>) -> HttpResponse {
    let todo = db.delete_todo_by_id(&id);
    match todo {
        Some(todo) => HttpResponse::Ok().json(todo),
        None => HttpResponse::NotFound().body("Todo not found"),
    }
}
```

Add to the `config` function:

```rust
pub fn config(cfg: &mut web::ServiceConfig) {
    cfg.service(
        web::scope("/api")
            .service(create_todo)
            .service(get_todos)
            .service(get_todo_by_id)
            .service(update_todo_by_id)
            .service(delete_todo_by_id)
    );
}
```

Add to `src/repository/database.rs`:

```rust
impl Database {
    // ...

    pub fn delete_todo_by_id(&self, id: &str) -> Option<Todo> {
        let mut todos = self.todos.lock().unwrap();
        let index = todos.iter().position(|todo| todo.id == Some(id.to_string()))?;
        Some(todos.remove(index))
    }
}
```

Done! Now we can start the server and test the API.

## Testing the API

Start the server with logging enabled:

```bash
RUST_LOG=info cargo run
```

Test with curl:

```bash
# Create new Todo items
curl -X POST -H "Content-Type: application/json" -d '{"title": "Buy milk", "description": "Buy 2 liters of milk"}' http://localhost:8080/api/todos
curl -X POST -H "Content-Type: application/json" -d '{"title": "Buy eggs", "description": "Buy 12 eggs"}' http://localhost:8080/api/todos
curl -X POST -H "Content-Type: application/json" -d '{"title": "Buy bread", "description": "Buy 1 loaf of bread"}' http://localhost:8080/api/todos

# Get all Todo items
curl -s http://localhost:8080/api/todos | jq
[
  {
    "id": "590538de-56c4-4057-b4e6-c91021fc04be",
    "title": "Buy milk",
    "description": "Buy 2 liters of milk",
    "created_at": "2023-03-11T11:58:28.176321Z",
    "updated_at": "2023-03-11T11:58:28.176376Z"
  },
  {
    "id": "54f7695f-55a0-423f-9aba-0d2ec323eef3",
    "title": "Buy eggs",
    "description": "Buy 12 eggs",
    "created_at": "2023-03-11T11:58:28.183312Z",
    "updated_at": "2023-03-11T11:58:28.183314Z"
  },
  {
    "id": "cd574ca3-0d18-4e34-adad-c493140607a5",
    "title": "Buy bread",
    "description": "Buy 1 loaf of bread",
    "created_at": "2023-03-11T11:58:28.189685Z",
    "updated_at": "2023-03-11T11:58:28.189687Z"
  }
]

# Get a Todo item by id
curl -s http://localhost:8080/api/todos/590538de-56c4-4057-b4e6-c91021fc04be | jq
{
  "id": "590538de-56c4-4057-b4e6-c91021fc04be",
  "title": "Buy milk",
  "description": "Buy 2 liters of milk",
  "created_at": "2023-03-11T11:58:28.176321Z",
  "updated_at": "2023-03-11T11:58:28.176376Z"
}

# Update a Todo item by id
curl -s -X PUT -H "Content-Type: application/json" -d '{"title": "Buy 2 liters of milk", "description": "Buy 2 liters of milk"}' http://localhost:8080/api/todos/590538de-56c4-4057-b4e6-c91021fc04be | jq
{
  "id": "590538de-56c4-4057-b4e6-c91021fc04be",
  "title": "Buy 2 liters of milk",
  "description": "Buy 2 liters of milk",
  "created_at": "2023-03-11T11:58:28.176321Z",
  "updated_at": "2023-03-11T11:58:28.176376Z"
}

# Delete a Todo item by id
curl -s -X DELETE http://localhost:8080/api/todos/590538de-56c4-4057-b4e6-c91021fc04be | jq
{
  "id": "590538de-56c4-4057-b4e6-c91021fc04be",
  "title": "Buy 2 liters of milk",
  "description": "Buy 2 liters of milk",
  "created_at": "2023-03-11T11:58:28.176321Z",
  "updated_at": "2023-03-11T11:58:28.176376Z"
}

# Get all Todo items
curl -s http://localhost:8080/api/todos | jq
[
  {
    "id": "54f7695f-55a0-423f-9aba-0d2ec323eef3",
    "title": "Buy eggs",
    "description": "Buy 12 eggs",
    "created_at": "2023-03-11T11:58:28.183312Z",
    "updated_at": "2023-03-11T11:58:28.183314Z"
  },
  {
    "id": "cd574ca3-0d18-4e34-adad-c493140607a5",
    "title": "Buy bread",
    "description": "Buy 1 loaf of bread",
    "created_at": "2023-03-11T11:58:28.189685Z",
    "updated_at": "2023-03-11T11:58:28.189687Z"
  }
]
```

## Conclusion

You've built a RESTful API with Rust and `actix-web`. You learned basic REST API concepts and set up an in-memory database using `Mutex` and `Arc`.

In the next article, we'll replace the in-memory database with a real one. Tell me in the comments what database you'd like to see (and why it should be `postgres`).

## Resources

* https://restfulapi.net/rest-architectural-constraints/
    
* https://restfulapi.net/
    
* https://www.techtarget.com/searchapparchitecture/tip/The-5-essential-HTTP-methods-in-RESTful-API-development
    
* https://medium.com/@andreasreiser94/why-hateoas-is-useless-and-what-that-means-for-rest-a65194471bc8
    
* https://actix.rs/
    
* https://hackernoon.com/easily-understand-rust-modules-across-multiple-files-with-this-guide
    
* https://aeshirey.github.io/code/2020/12/23/arc-mutex-in-rust.html
