Building a Simple Web API in Rust with Actix Web
Rust is a powerful language known for its speed and memory safety, but its strict type system and borrow checker can be challenging, especially for those coming from JavaScript or C++. When exploring web development with Rust, I found a lack of beginner-friendly resources, particularly for Actix Web.
When I first attempted to build a web backend in Rust, I underestimated the complexity. Between the borrow checker and the scarcity of beginner-oriented Actix Web tutorials, the learning curve was steep. This guide aims to provide a structured approach to setting up an Actix Web backend, covering essential concepts and API development.
Scope of the Article
We'll cover:
- Setting up a Rust project for Actix Web
- Writing a simple API with route handling
- Running and testing the server
- Understanding the project structure and key concepts
Framework and Tools Used
- Actix Web – The Rust web framework we’ll use for handling HTTP requests
- Cargo – The Rust package manager for managing dependencies
- Rust (Stable Version) – Make sure you have Rust installed before proceeding
-
Postman – We will use Postman to test the API rather than
curl
.
Now, let's dive in and build our Actix Web backend.
1. Creating the Project
Open your terminal and run:
cargo new actix-web-demo --bin
This creates a new Rust project named actix-web-demo
with a binary (--bin
) target. Now, navigate into the project directory:
cd actix-web-demo
2. Adding Dependencies
Open the Cargo.toml
file and add actix-web
as a dependency:
[dependencies]
actix-web = "4"
Then, run:
cargo build
This downloads and compiles the dependencies, ensuring everything is set up correctly.
3. Writing a Simple API with Route Handling
Open src/main.rs
and replace its contents with the following:
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
// A simple handler function that returns a welcome message
async fn greet() -> impl Responder {
HttpResponse::Ok().body("Hello from Actix Web!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.route("/", web::get().to(greet))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
This creates a basic Actix Web server with a route (/
) that maps GET
requests to greet()
.
4. Running and Testing the Server
Run:
cargo run
Now, open your browser and navigate to 127.0.0.1:8080
. You should see the message:
Hello from Actix Web!
Testing with Postman
Now that the server is running, let's test the API using Postman instead of curl
.
Open Postman
If you haven't already, download and install Postman.-
Set up the request in Postman
- Method: GET
-
URL:
http://127.0.0.1:8080/
- Click "Send"
-
Expected Result: You should see the response
Hello from Actix Web!
.
5. Adding More Routes
We’ll extend this API by adding multiple routes that return dummy JSON data. Our endpoints will include:
-
GET /users
– Returns a list of users -
GET /users/{id}
– Fetches a single user by ID -
POST /users
– Creates a new user (dummy response) -
DELETE /users/{id}
– Deletes a user (dummy response)
6. Updating Dependencies
Modify Cargo.toml
to include serde
and serde_json
for JSON parsing:
[dependencies]
actix-web = "4"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Run:
cargo build
7. Writing the API
Modify src/main.rs
:
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
use serde::{Deserialize, Serialize};
// Struct to represent a user
#[derive(Serialize, Deserialize)]
struct User {
id: u32,
name: String,
email: String,
}
// Dummy user data
fn get_dummy_users() -> Vec<User> {
vec![
User { id: 1, name: "Alice".into(), email: "alice@example.com".into() },
User { id: 2, name: "Bob".into(), email: "bob@example.com".into() },
]
}
// GET /users - Returns all users
async fn get_users() -> impl Responder {
HttpResponse::Ok().json(get_dummy_users())
}
// GET /users/{id} - Fetch user by ID
async fn get_user_by_id(path: web::Path<u32>) -> impl Responder {
let user_id = path.into_inner();
let users = get_dummy_users();
if let Some(user) = users.into_iter().find(|u| u.id == user_id) {
HttpResponse::Ok().json(user)
} else {
HttpResponse::NotFound().body("User not found")
}
}
// POST /users - Create a new user (dummy response)
#[derive(Deserialize)]
struct NewUser {
name: String,
email: String,
}
async fn create_user(user: web::Json<NewUser>) -> impl Responder {
let new_user = User {
id: 3, // Dummy ID
name: user.name.clone(),
email: user.email.clone(),
};
HttpResponse::Created().json(new_user)
}
// DELETE /users/{id} - Delete a user (dummy response)
async fn delete_user(path: web::Path<u32>) -> impl Responder {
let user_id = path.into_inner();
HttpResponse::Ok().body(format!("User with ID {} deleted", user_id))
}
// Main function to set up the Actix Web server
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.route("/users", web::get().to(get_users))
.route("/users/{id}", web::get().to(get_user_by_id))
.route("/users", web::post().to(create_user))
.route("/users/{id}", web::delete().to(delete_user))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
8. Running and Testing the API
Start the server:
cargo run
Test Endpoints Using Postman
1. Get All Users
- Method: GET
-
URL:
http://127.0.0.1:8080/users
- Click "Send"
- Expected Result: You should see a list of users in JSON format.
2. Get a User by ID
- Method: GET
-
URL:
http://127.0.0.1:8080/users/1
(or any other ID) - Click "Send"
- Expected Result: You should see the user with the specified ID or a "User not found" message.
3. Create a New User
- Method: POST
-
URL:
http://127.0.0.1:8080/users
-
Body:
- Select raw and set the type to JSON.
- Add the following JSON:
{ "name": "Charlie", "email": "charlie@example.com" }
Click "Send"
Expected Result: You should see a response with the newly created user, including a dummy ID.
4. Delete a User
- Method: DELETE
-
URL:
http://127.0.0.1:8080/users/2
(replace2
with the ID of the user you want to delete) - Click "Send"
-
Expected Result: You should receive a message like
"User with ID 2 deleted"
.
Conclusion
Now we have a simple CRUD-style API in Rust using Actix Web. From here, you can:
- Connect it to a real database like PostgreSQL using Diesel
- Improve error handling with
Result
- Implement authentication with JWT
Rust’s strictness might feel frustrating at first, but once you get through it, the speed and safety make it worth the effort. With Postman, testing and interacting with your API is more intuitive, and you can expand the API with additional functionality or features as needed.