Skip to main content
  1. Projects/

Datguri

·1050 words

Introduction #

Datguri is a Chrome extension that is built to provide a way for users to Leave comments on any page you want!

The extension works available on Chrome computer environment. I was responsible for backend of the application that was built using Rust and Actix

😍 How to use it? 😍

  1. Leave comments on the website for anything you’re curious about!
  2. If you find a comment that resonates with you, click the Like button to make it a popular comment!
  3. Leave comments on any other page and become a popular commentator!

Some of the technical challenges I faced while building this project was that I wasn’t familiar with Rust and Actix. Also, for frontend developers, it was also difficult to understand how it is different between Chrome extension and normal web application.

Folder Structure #

.
├── Cargo.toml
├── Dockerfile
├── app
│   ├── Cargo.toml
│   └── src
│       ├── controller
│       ├── main.rs
│       └── usecase
├── entity
│   ├── Cargo.toml
│   └── src
│       ├── comment.rs
│       ├── lib.rs
│       ├── reply.rs
│       ├── report.rs
│       └── user.rs
├── storage
│   ├── Cargo.toml
│   └── src
│       ├── lib.rs
│       └── postgres
└── utils
    ├── Cargo.toml
    └── src
        └── lib.rs

12 directories, 14 files

The project is divided into 4 parts.

  1. app : This is the main part of the project. It is responsible for
    • controller : It is responsible for handling HTTP requests and responses.
    • usecase : It is responsible for business logic.
  2. entity : This is the part that contains the data structure of the project.
    • Tried to separate the data structure from the business logic. Also, because of possibilities of MSA (Microservice Architecture), I tried to make it as a separate part.
  3. storage : This is the part that is responsible for storing data.
    • Used postgres as a database and sqlx as a database driver
  4. utils : This is the part that contains the common functions that are used in the project.

Every folder, have its own Cargo.toml file. Which is integrated using workspace feature that is supported in Rust Cargo.

Tech Stack #

  1. Frontend: Typescript, ReactJS
  2. Backend: Rust, Actix, PostgresQL
  3. CI/CD: Github Actions, Docker, Wrangler CLI
  4. Cloud: Terraform, AWS, Cloudflare
  5. More: Nginx

As a backend developer, in this team I was responsible for building API server using Rust and considering server maintenance and CI/CD pipeline.

What I have learned #

1. JWT validation in Rust #

    #[derive(Serialize, Deserialize)]
    struct Claims {
        sub: String,
        iat: usize,
        exp: usize,
    }

    /// find user_id from jwt in Authorization field in header
    pub async fn validate_request(
        req: HttpRequest,
        pool: &Pool<Postgres>,
    ) -> Result<String, HttpResponse> {
        // access_token from request header
        let access_token: String = match req.headers().get("Authorization") {
            Some(token) => match token.to_str() {
                Ok(token) => token.replace("Bearer ", ""),
                Err(_) => {
                    return Err(HttpResponse::InternalServerError().json(JsonMessage {
                        msg: "Error parsing header to string".to_string(),
                    }));
                }
            },
            None => {
                return Err(HttpResponse::InternalServerError().json(JsonMessage {
                    msg: "Authorization field not exist".to_string(),
                }));
            }
        };

        // find user from database
        let user: Option<User> = match validate_jwt(access_token) {
            Ok(sub) => match find_user_by_email(pool, &sub).await {
                Ok(user) => user,
                Err(e) => {
                    return Err(HttpResponse::InternalServerError().json(format!("Error: {:?}", e)));
                }
            },
            // JwtError
            Err(e) => {
                return Err(HttpResponse::Unauthorized().json(JsonMessage {
                    msg: format!("{:?}", e),
                }));
            }
        };

        // returns email if valid
        return if let Some(user) = user {
            Ok(user.uuid)
        } else {
            Err(HttpResponse::Unauthorized().json(JsonMessage {
                msg: "User not found".to_string(),
            }))
        };
    }

In the code snippet shared above, you can see a Rust function called validate_request, which takes an HTTP request and a connection pool as input, and returns either the user’s UUID or an error response. Here’s a brief overview of the function’s workflow:

  1. Extract the access token from the “Authorization” header in the request.
  2. Validate the JWT and obtain the user’s email (the “sub” claim).
  3. Look up the user in the database using their email.
  4. If the user is found, return their UUID; otherwise, return an “Unauthorized” error.

Although the code may seem more unnecessarily complex compared to similar implementations in other languages, this seems to be a result of Rust’s emphasis on safety and explicit error handling. It initially required extra effort to process strings and perform simple actions, the safety guarantees provided by Rust made it an invaluable tool for building secure and reliable applications.

2. Concurrent Database query execution #

There are more detailed post about this topic. I recommend to read this post below and come back to this point.

Concurrent Database Connection and Query Execution in Rust

One of primary concerns is optimizing the performance of applications to provide the best possible user experience. Concurrency is one of the most important things to consider, allowing multiple tasks to be executed simultaneously, thereby improving efficiency. In this case, particularly in the context of database query execution.

The code at the post above demonstrates two distinct approaches to retrieving comments for a given room. Both functions, find_comment_by_room and find_best_comment_by_room, share the same objective but differ in the way they process comment_likes. The first function uses a simple for loop to sequentially query the database for comment_likes, while the second function employs Rust’s futures library to concurrently execute multiple database queries at once, using the join_all function.

After the concurrent execution is complete, we process the results and construct a CommentResponse object for each comment. This allows us to efficiently return the final results as a JSON response.

In the case of our Rust web application, utilizing concurrency allowed us to improve the performance of database query execution, resulting in a more responsive application.

Conclusion #

In conclusion, developing a web application using Rust was a valuable experience that taught me a lot about the language’s safety, concurrency, and thread management. However, it also became apparent that Rust might not be the ideal choice for creating a simple backend API server, as the effort required to ensure safety seems to be quite massive to other languages. Rust seems to be more suitable for low-level and system programming tasks where safety and performance are of utmost importance. Nonetheless, working with Rust allowed me to dive deeper into the world of concurrency and gain a better understanding of its benefits in improving the performance of applications. Despite the challenges, I truly enjoyed the strictness and safety features that Rust provided, and I believe this knowledge will be invaluable when working on future projects in different domains.