This tutorial introduces building performant REST APIs using Rust. Unlike typical “Hello World” benchmarks, we’ll create a real-world Twitter clone with database operations.
Rust’s positioning: “Fast, reliable, productive - Pick three”
Prerequisites
- Cargo installed
- Basic Rust knowledge
- GitHub account
- Qovery account
API Design
We’ll create a miniature Twitter API with these endpoints:
Route: /tweets
GET: Retrieve last 50 tweets
POST: Create new tweet
Route: /tweets/:id
GET: Fetch tweet by identifier
DELETE: Remove tweet by identifier
Route: /tweets/:id/likes
GET: Retrieve all likes for a tweet
POST: Increment like counter
DELETE: Decrement like counter
Implementation
Framework: Actix Web
We’re using Actix, ranked as one of the most performant frameworks by Techempower.
Application Structure
The implementation uses three primary files:
- main.rs - HTTP request routing
- tweet.rs - /tweets endpoint handling
- like.rs - /tweets/:id/likes endpoint handling
Main Application Setup
#[actix_rt::main]
async fn main() -> io::Result<()> {
env::set_var("RUST_LOG", "actix_web=debug,actix_server=info");
env_logger::init();
HttpServer::new(|| {
App::new()
.wrap(middleware::Logger::default())
.service(tweet::list)
.service(tweet::get)
.service(tweet::create)
.service(tweet::delete)
.service(like::list)
.service(like::plus_one)
.service(like::minus_one)
})
.bind("0.0.0.0:9090")?
.run()
.await
}
Route Definitions
Routes use attribute macros for clean path binding:
#[get("/tweets")]
async fn list() -> impl Responder {
// Implementation
}
#[get("/tweets/{id}")]
async fn get(id: web::Path<String>) -> impl Responder {
// Implementation
}
#[post("/tweets")]
async fn create(tweet: web::Json<NewTweet>) -> impl Responder {
// Implementation
}
#[delete("/tweets/{id}")]
async fn delete(id: web::Path<String>) -> impl Responder {
// Implementation
}
PostgreSQL Integration
Diesel ORM
Diesel serves as the primary ORM for PostgreSQL connectivity.
Diesel lacks tokio support, requiring use of web::block() to execute blocking database operations on separate threads, preventing server thread blocking.
Database Schema
table! {
likes (id) {
id -> Uuid,
created_at -> Timestamp,
tweet_id -> Uuid,
}
}
table! {
tweets (id) {
id -> Uuid,
created_at -> Timestamp,
message -> Text,
}
}
joinable!(likes -> tweets (tweet_id));
allow_tables_to_appear_in_same_query!(likes, tweets);
Connection Pool Setup
#[actix_rt::main]
async fn main() -> io::Result<()> {
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL");
let manager = ConnectionManager::<PgConnection>::new(database_url);
let pool = r2d2::Pool::builder()
.build(manager)
.expect("Failed to create pool");
HttpServer::new(move || {
App::new()
.data(pool.clone())
.wrap(middleware::Logger::default())
// ... service registrations
})
.bind("0.0.0.0:9090")?
.run()
.await
}
Local Testing
Test your API endpoints locally:
# List tweets
curl http://localhost:9090/tweets
# Get specific tweet
curl http://localhost:9090/tweets/abc
# Create tweet
curl -X POST -d '{"message": "This is a tweet"}' \
-H "Content-type: application/json" http://localhost:9090/tweets
# Delete tweet
curl -X DELETE http://localhost:9090/tweets/abc
# List likes
curl http://localhost:9090/tweets/abc/likes
# Add like
curl -X POST http://localhost:9090/tweets/abc/likes
# Remove like
curl -X DELETE http://localhost:9090/tweets/abc/likes
Deploy to Qovery
Create Project
Sign in to Qovery web console and create a new project.
Create Environment
Establish a new environment within your project.
Register Application
Link your GitHub repository:
- Repository:
https://github.com/evoxmusic/twitter-clone-rust
- Branch:
master
Configure Port
- Navigate to application settings
- Select Ports section
- Add port
9090
Deploy PostgreSQL
Create a PostgreSQL database named my-pql-db following the database guide. Configure Database Connection
- Access Variables tab in application overview
- Add environment variable
DATABASE_URL
- Alias it to the PostgreSQL connection string
Deploy Application
Navigate to application dashboard and click Deploy button. Monitor status until completion.
Live Testing
After deployment, test your production API:
# Create tweet
curl -X POST -d '{"message": "This is a tweet"}' \
-H "Content-type: application/json" \
https://main-gxbuagyvgnkbrp5l-gtw.qovery.io/tweets
# List tweets
curl https://main-gxbuagyvgnkbrp5l-gtw.qovery.io/tweets
# Get specific tweet
curl https://main-gxbuagyvgnkbrp5l-gtw.qovery.io/tweets/<id>
# Like management
curl -X POST https://main-gxbuagyvgnkbrp5l-gtw.qovery.io/tweets/<id>/likes
curl -X DELETE https://main-gxbuagyvgnkbrp5l-gtw.qovery.io/tweets/<id>/likes
# Delete tweet
curl -X DELETE https://main-gxbuagyvgnkbrp5l-gtw.qovery.io/tweets/<id>
Next Steps
Part 2 will compare this Rust implementation’s performance against an equivalent Go application.
Learning Resources