Building a PasteBin in Rust: A Step-by-Step Tutorial
Tags: rust
Published: 2024-03-30 19:36:32

---
title: Building a PasteBin in Rust: A Step-by-Step Tutorial
published: true
date: 2024-03-30 11:35:36 UTC
tags: rust
canonical_url: https://eleftheriabatsou.hashnode.dev/pastebin-in-rust-tutorial
---

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e6pmdw0raxdfpl4x71bz.jpeg)

Hello, Rust friends! Today, we're going to build our very own PasteBin clone using Rust, [Actix](https://actix.rs/) for web server functionality, [Rustqlite](https://docs.rs/sqlite/latest/sqlite/) for the database operations, and HTML/CSS for the user interface. If you're new to Rust or web development, you're in for a treat. This project is a fantastic way to get hands-on experience while learning some core concepts of Rust and web programming.

By the end of this tutorial, you'll have a fully functional PasteBin clone that you can run locally on your machine.

{% embed https://twitter.com/BatsouElef/status/1767870730400645178 %}

#### What is PasteBin, and Why Rust?

PasteBin is a simple online service that allows users to upload and share text or code snippets. It's a handy tool for developers to quickly share code or configuration files.

Rust, known for its safety and performance, is an excellent choice for web services. It offers memory safety without a garbage collector, making it suitable for high-performance web applications.

**_Note_** : In this tutorial, we'll focus more on Rust rather than HTML/CSS. You can find the code [_here_](https://github.com/EleftheriaBatsou/paste-bin-rust)_._

## Setting Up

Before we dive into the code, make sure you have Rust and [Cargo](https://doc.rust-lang.org/cargo/) installed on your machine. You'll also need to add the following dependencies to your `Cargo.toml` file:

- `actix-web` for our web server and routing.
- `rusqlite` for SQLite database interactions.
- `rand` for generating unique tokens for each paste.
- `actix-files` for serving static files.
- `serde` for serializing and deserializing data.

```
[dependencies]
actix-web = "3.0"
rusqlite = { version = "0.29.0", features = ["bundled"] }
rand = "0.8"
actix-files = "0.5"
serde = { version = "1.0", features = ["derive"] }

```

## Main.rs

The `main` function initializes our SQLite database and sets up our web server with Actix-web. It maps our routes to their handlers and starts the server, listening for requests on [`localhost:8080`](http://localhost:8080).

When a user submits a paste, a unique token is generated using the `rand` crate. This token acts as the identifier for the paste, stored alongside the paste content in our SQLite database.

Retrieving a paste is straightforward; we query the database for the content associated with the token provided in the URL. If found, we display it; otherwise, we show a "Paste not found" message.

**Let's break down the key parts:**

**AppState** : This struct holds our application state, in this case, a connection to the SQLite database wrapped in a `Mutex` for safe access across threads.

```
struct AppState {
    db: Mutex<Connection>,
}

```

**Routes and Handlers** : We define several routes and their corresponding handlers:

- The `/` route serves our `index.html` file, presenting users with a simple form to submit their pastes.
- The `/submit` route handles form submissions, generating a unique token for each paste, storing it in the database, and redirecting the user to their paste's URL.
- The `/paste/{token}` route retrieves and displays the content associated with a given token.

```
async fn index() -> impl Responder { // sends the response body for index.html
    HttpResponse::Ok().body(include_str!("index.html"))
}

async fn submit(content: web::Form<FormData>, data: web::Data<AppState>) -> impl Responder { // it takes some content
    .
    .
    .
    .
}

async fn get_paste(token: web::Path<String>, data: web::Data<AppState>) -> impl Responder { // this function helps to retrieve the value someone is sending to us
    .
    .
    .
    .
}

```

### Diving Into the Code

**Imports** : These are the necessary imports for our project:

- `actix_web`: Provides tools and utilities for building web applications with Actix.
- `rusqlite`: Allows interaction with SQLite databases.
- `rand`: Provides random number generation capabilities.
- `std::sync::Mutex`: Provides mutual exclusion for accessing shared resources across threads.
- `actix_files::NamedFile`: Allows serving static files.

```
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
use rusqlite::{params, Connection};
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
use std::sync::Mutex;
use actix_files::NamedFile;

```

**index Function** : This will be an asynchronous function. It handles requests to the root URL ("/") and returns an HTTP response with the content of the `index.html` file.

```
async fn index() -> impl Responder {
    HttpResponse::Ok().body(include_str!("index.html"))
}

```

**submit Function** : This is also an asynchronous function that handles POST requests to the "/submit" endpoint. It receives form data containing the content to be submitted. It generates a random token, inserts the content and token into the SQLite database, and returns a redirect response to the URL containing the generated token.

```
async fn submit(content: web::Form<FormData>, data: web::Data<AppState>) -> impl Responder {
    // Generating a random token
    let token: String = thread_rng()
        .sample_iter(&Alphanumeric)
        .take(10)
        .map(char::from)
        .collect();

    let conn = data.db.lock().unwrap();

    conn.execute(
        "INSERT INTO pastes (token, content) VALUES (?, ?)",
        params![&token, &content.content],
    ).expect("Failed to insert into database");

    HttpResponse::SeeOther()
        .header("Location", format!("/paste/{}", token))
        .finish()
}

```

**get\_paste Function** : This is also an asynchronous function that handles GET requests to the "/paste/{token}" endpoint. It receives a token as a path parameter, retrieves the content associated with the token from the SQLite database, and returns an HTTP response with the retrieved content.

```
async fn get_paste(token: web::Path<String>, data: web::Data<AppState>) -> impl Responder {
    let conn = data.db.lock().unwrap();

    let content = conn
        .query_row(
            "SELECT content FROM pastes WHERE token = ?",
            params![token.to_string()],
            |row| row.get::<_, String>(0),
        )
        .unwrap_or_else(|_| "Paste not found".to_string());

    HttpResponse::Ok().body(format!("<pre>{}</pre>", content))
}

```

**FormData Struct** : Let's define a struct to represent form data received from the client. It will include a single field `content` to store the submitted content.

```
#[derive(serde::Deserialize)]
struct FormData {
    content: String,
}

```

**main Function** : Finally! This is the entry point of the program. It performs the following tasks:

- Opens or creates the SQLite database file named "pastes.db".
- Creates the "pastes" table if it does not exist, with columns for token (primary key) and content.
- Defines the application state with the database connection.
- Configures the Actix web server routes, including serving static files ("/style.css"), handling index requests ("/"), submitting form data ("/submit"), and retrieving paste content ("/paste/{token}").

- Binds the server to the address "127.0.0.1:8080" and starts the server.

```
#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let db = Connection::open("pastes.db").expect("Failed to open database");

    db.execute(
        "CREATE TABLE IF NOT EXISTS pastes (token TEXT PRIMARY KEY, content TEXT)",
        params![],
    )
    .expect("Failed to create table");

    let app_state = web::Data::new(AppState {
        db: Mutex::new(db),
    });

    HttpServer::new(move || {
        App::new()
            .app_data(app_state.clone())
            .service(web::resource("/style.css").to(|| {
                async { NamedFile::open("src/style.css") }
            }))
            .route("/", web::get().to(index))
            .route("/submit", web::post().to(submit))
            .route("/paste/{token}", web::get().to(get_paste))
    })
    .bind("127.0.0.1:8080")? // Binding the server to the specified address
    .run()
    .await // Starting the server and awaiting its completion
}

```

That's it folks! The Rust part is done :)

I'd suggest at the same time you're building the main.rs to have a look at the [HTML](https://github.com/EleftheriaBatsou/paste-bin-rust/blob/main/src/index.html) and [CSS](https://github.com/EleftheriaBatsou/paste-bin-rust/blob/main/src/style.css) files too.

### Run the Program

To run the program, hit `cargo run` , then open your browser at `localhost:8080`, you should see your page! Add some text or code, click `submit`, and then copy the unique `URL`. That's it!

If it wasn't on localhost but on a server you could share it with your friends and have a truly unique way to talk to each other :)

To stop the program hit `ctrl + c`.

{% embed https://github.com/EleftheriaBatsou/paste-bin-rust %}

## Wrapping Up

Congratulations! 🙌 You've just built your own PasteBin clone using Rust. This project not only gave you a practical introduction to Rust and web development but also demonstrated how Rust's safety features and performance make it an excellent choice for building web applications.

Through this tutorial, we've learned how to set up a Rust project, use external crates, handle web requests, interact with a database, and dynamically generate and serve content.

I encourage you to experiment with your PasteBin clone, perhaps by adding new features such as syntax highlighting or user authentication. The possibilities are endless, and with Rust, you're well-equipped to tackle them.

Happy coding. 🦀

* * *

👋 Hello, I'm Eleftheria, **Community Manager,** developer, public speaker, and content creator.

🥰 If you liked this article, consider sharing it.

🔗 [**All links**](https://limey.io/batsouelef) | [**X**](https://twitter.com/BatsouElef) | [**LinkedIn**](https://www.linkedin.com/in/eleftheriabatsou/)