Skip to main content
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

Twitter Clone Project

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:
  1. main.rs - HTTP request routing
  2. tweet.rs - /tweets endpoint handling
  3. 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

1

Create Project

Sign in to Qovery web console and create a new project.
2

Create Environment

Establish a new environment within your project.
3

Register Application

Link your GitHub repository:
  • Repository: https://github.com/evoxmusic/twitter-clone-rust
  • Branch: master
Register Rust application
4

Configure Port

  1. Navigate to application settings
  2. Select Ports section
  3. Add port 9090
Configure application port
5

Deploy PostgreSQL

Create a PostgreSQL database named my-pql-db following the database guide.
6

Configure Database Connection

  1. Access Variables tab in application overview
  2. Add environment variable DATABASE_URL
  3. Alias it to the PostgreSQL connection string
Open environment variables
Alias database connection
7

Deploy Application

Navigate to application dashboard and click Deploy button. Monitor status until completion.
Deploy application

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