Web Apps with Rust: Building a Real-World Application

Chapter 8: A comprehensive example to wrap up the series

June 10, 2024

By: Maria

Welcome to the final chapter of our series on building web apps with Rust! In this chapter, we’ll bring together all the concepts we’ve discussed by building a more complex, real-world application. This hands-on project will showcase the practical application of Rust for web development.

Rust and web apps Illustration: A 3D rendering of a gear-shaped logo featuring the letter 'R' in the center, crafted in brown against a white background. The design subtly symbolizes the Rust programming language, renowned for its performance and reliability.

Project Overview: Building a Simple Blog

We’ll build a simple blog application that allows users to create, read, update, and delete (CRUD) posts. This project will integrate state management, backend services, authentication, and performance optimization.

Setting Up the Project

1. Initialize the Rust Project:

cargo new rust_blog --bin
cd rust_blog

2. Add Dependencies:
Add the following dependencies to your Cargo.toml file:

[dependencies]
actix-web = "4"
actix-session = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sqlx = { version = "0.5", features = ["sqlite", "runtime-actix-native-tls"] }
jsonwebtoken = "8"

Setting Up the Backend

1. Database Configuration:
Use SQLite for simplicity. Set up your database schema:

CREATE TABLE posts (
    id INTEGER PRIMARY KEY,
    title TEXT NOT NULL,
    body TEXT NOT NULL,
    published_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

2. Backend API:
Implement RESTful endpoints for the blog posts in main.rs:

use actix_web::{web, App, HttpServer, HttpResponse, Responder};
use serde::{Deserialize, Serialize};
use sqlx::SqlitePool;

#[derive(Serialize, Deserialize)]
struct Post {
    id: i32,
    title: String,
    body: String,
    published_at: String,
}

async fn get_posts(pool: web::Data<SqlitePool>) -> impl Responder {
    let posts = sqlx::query_as!(Post, "SELECT * FROM posts")
        .fetch_all(pool.get_ref())
        .await
        .unwrap();
    HttpResponse::Ok().json(posts)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let pool = SqlitePool::connect("sqlite:blog.db").await.unwrap();
    HttpServer::new(move || {
        App::new()
            .data(pool.clone())
            .route("/posts", web::get().to(get_posts))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

Integrating Authentication

  1. JWT Authentication:
    Secure the endpoints using JWT. Add login and token generation logic.
use jsonwebtoken::{encode, Header, EncodingKey};
use actix_web::{web, HttpResponse, Error};

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

async fn login() -> Result<HttpResponse, Error> {
    let my_claims = Claims { sub: "user_id".to_string(), exp: 10000000000 };
    let token = encode(&Header::default(), &my_claims, &EncodingKey::from_secret("secret".as_ref())).unwrap();
    Ok(HttpResponse::Ok().body(token))
}

Frontend Integration

  1. State Management and UI:
    Use a frontend framework like Yew for state management and building the UI.
use yew::prelude::*;
use yew::services::fetch::FetchService;

struct Model {
    link: ComponentLink<Self>,
    posts: Vec<Post>,
}

impl Component for Model {
    type Message = ();
    type Properties = ();

    fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
        Self { link, posts: vec![] }
    }

    fn update(&mut self, _: Self::Message) -> ShouldRender {
        true
    }

    fn view(&self) -> Html {
        html! {
            <div>
                <h1>{"Blog Posts"}</h1>
                { for self.posts.iter().map(|post| html! {
                    <div>
                        <h2>{ &post.title }</h2>
                        <p>{ &post.body }</p>
                        <small>{ &post.published_at }</small>
                    </div>
                }) }
            </div>
        }
    }
}

Conclusion

By following this comprehensive example, you’ve learned how to build a full-featured Rust web application, integrating state management, backend services, authentication, and performance optimization. This hands-on project ties together all the concepts covered in this series, providing a robust foundation for your future Rust web development projects.

Thank you for following along with our series on building web apps with Rust. We hope these chapters have equipped you with the knowledge and confidence to create your own powerful, efficient, and secure Rust web applications.

Stay tuned for more Rust tutorials and happy coding!