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

Rust Blog

Official Rust blog and updates

The Rust Book

Free comprehensive Rust programming guide

Jon Gjengset

MIT distributed systems and Rust live-coding
https://mintcdn.com/qovery/Nvnl0g5BHzA0XQmy/images/logos/github-icon.svg?fit=max&auto=format&n=Nvnl0g5BHzA0XQmy&q=85&s=8bd221fee047ba947afcfd39bd14ef08

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