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/ < i d >
# Like management
curl -X POST https://main-gxbuagyvgnkbrp5l-gtw.qovery.io/tweets/ < i d > /likes
curl -X DELETE https://main-gxbuagyvgnkbrp5l-gtw.qovery.io/tweets/ < i d > /likes
# Delete tweet
curl -X DELETE https://main-gxbuagyvgnkbrp5l-gtw.qovery.io/tweets/ < i d >
Next Steps
Part 2 will compare this Rust implementation’s performance against an equivalent Go application.
Learning Resources
Rust Blog Official Rust blog and updates
The Rust Book Free comprehensive Rust programming guide
Jon Gjengset MIT distributed systems and Rust live-coding
GitHub Repository Complete source code for this tutorial
Deploy Your First App Step-by-step deployment guide
Database Configuration Configure PostgreSQL databases
Environment Variables Manage environment variables
Application Settings Configure application settings