Rust Website Example: Building a Simple Website with Rust and Axum

Rust is a systems programming language known for its speed, safety, and modern tooling. It has gained popularity for web development due to frameworks like Axum, Rocket, and Actix-web, which simplify building scalable and efficient web applications. This article provides a step-by-step guide to creating a basic website using Rust and Axum.


Why Use Rust for Web Development?

  1. Performance: Rust compiles to machine code, ensuring fast execution.
  2. Memory Safety: Rust prevents common bugs like null pointer dereferences.
  3. Concurrency: Built-in support for asynchronous programming.
  4. Growing Ecosystem: Modern frameworks and libraries for web applications.

Setting Up a Rust Website

Step 1: Create a New Rust Project

Run the following command to create a new project:

cargo new rust_website
cd rust_website

This will create a new directory with the following structure:

rust_website/
├── Cargo.toml
└── src/
    └── main.rs

Step 2: Add Dependencies

Add the following dependencies to your Cargo.toml file:

[dependencies]
axum = "0.6"
tokio = { version = "1.0", features = ["full"] }
tower = "0.4"
hyper = "0.14"
serde = { version = "1.0", features = ["derive"] }

These dependencies include:

  • Axum: Web framework for Rust.
  • Tokio: Async runtime for Rust.
  • Tower: Middleware and utilities.
  • Hyper: HTTP server and client library.
  • Serde: Serialization library for JSON handling.

Run cargo build to download and compile the dependencies.


Step 3: Write the Website Code

Replace the content of src/main.rs with the following code:

use axum::{
    routing::{get, post},
    Router,
    Json,
};
use serde::{Deserialize, Serialize};
use std::net::SocketAddr;

#[tokio::main]
async fn main() {
    // Build the Axum application
    let app = Router::new()
        .route("/", get(homepage))
        .route("/about", get(about))
        .route("/contact", post(contact));

    // Define the address for the server
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    println!("Server running at http://{}", addr);

    // Run the Axum server
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

// Homepage handler
async fn homepage() -> &'static str {
    "Welcome to My Rust Website!"
}

// About page handler
async fn about() -> &'static str {
    "This is the About Page of the Rust Website."
}

// Contact form handler
#[derive(Deserialize)]
struct ContactForm {
    name: String,
    message: String,
}

#[derive(Serialize)]
struct ResponseMessage {
    status: String,
    message: String,
}

async fn contact(Json(payload): Json<ContactForm>) -> Json<ResponseMessage> {
    Json(ResponseMessage {
        status: "success".to_string(),
        message: format!("Thanks for reaching out, {}!", payload.name),
    })
}

Step 4: Run the Server

Start the server by running:

cargo run

You should see output like this:

Server running at http://127.0.0.1:3000

How the Code Works

  1. Homepage:
    • The homepage handler responds with a simple text message.
    • Access it via http://127.0.0.1:3000/.
  2. About Page:
    • The about handler provides information about the site.
    • Access it via http://127.0.0.1:3000/about.
  3. Contact Form:
    • The contact handler processes JSON payloads for contact form submissions.

Response:

{
    "status": "success",
    "message": "Thanks for reaching out, Alice!"
}

Example POST request:

curl -X POST http://127.0.0.1:3000/contact \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "message": "Hello from Rust!"}'

Testing the Website

1. Access Pages

  • Open a browser and visit:
    • Homepage: http://127.0.0.1:3000/
    • About Page: http://127.0.0.1:3000/about

2. Submit Data

Use tools like Postman or curl to test the /contact endpoint.


Adding HTML Templates (Optional)

To serve HTML pages instead of plain text, you can integrate a template engine like Tera.

Update Cargo.toml:

tera = "1.12"

Serve HTML Template:

use axum::{
    response::Html,
    routing::get,
    Router,
};
use tera::{Context, Tera};

async fn homepage() -> Html<String> {
    let tera = Tera::new("templates/**/*.html").unwrap();
    let mut context = Context::new();
    context.insert("title", "Rust Website");
    context.insert("message", "Welcome to My Rust Website!");

    Html(tera.render("homepage.html", &context).unwrap())
}

Create a templates/homepage.html file:

<!DOCTYPE html>
<html>
<head>
    <title>{{ title }}</title>
</head>
<body>
    <h1>{{ message }}</h1>
</body>
</html>

Best Practices for Rust Websites

  1. Use Middleware:
    • Add logging, authentication, or rate-limiting middleware using Tower.
  2. Secure with HTTPS:
    • Use hyper-rustls to enable HTTPS.
  3. Optimize Performance:
    • Leverage Rust’s async capabilities to handle concurrent requests efficiently.
  4. Testing:
    • Use cargo test to write unit and integration tests for your handlers.

Conclusion

Building a website with Rust and Axum is straightforward, thanks to its modern async ecosystem and ergonomic APIs. Whether you’re creating a simple website or a complex web application, Rust offers performance, reliability, and scalability. Start with the example above and expand it to include features like templates, databases, and authentication to build a full-featured web application.