How to serve static files

Web
Use the `axum` framework with the `tower_http` service for a modern, ergonomic approach, or `warp` for a lightweight alternative; both handle MIME types, caching headers, and directory traversal safely out of the box.

Use the axum framework with the tower_http service for a modern, ergonomic approach, or warp for a lightweight alternative; both handle MIME types, caching headers, and directory traversal safely out of the box. For production, ensure you serve from a dedicated directory and avoid exposing sensitive paths.

Here is a minimal example using axum and tower_http, which is the current standard for Rust web services:

use axum::{routing::get, Router};
use tower_http::services::ServeDir;

#[tokio::main]
async fn main() {
    // Serve files from the "static" directory relative to the binary
    let serve_static = ServeDir::new("static");

    let app = Router::new()
        .nest_service("/", serve_static)
        .route("/", get(|| async { "Hello, world!" }));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

If you prefer warp, the implementation is similarly concise but uses a different middleware style:

use warp::Filter;

#[tokio::main]
async fn main() {
    let files = warp::fs::dir("static");
    let routes = files;

    warp::serve(routes).run(([0, 0, 0, 0], 3000)).await;
}

Both examples assume a static folder exists in your project root containing your assets (HTML, CSS, JS, images). The ServeDir and warp::fs::dir functions automatically handle Content-Type headers based on file extensions and set appropriate Cache-Control headers. They also prevent directory traversal attacks by default, ensuring users cannot access files outside the specified directory.

For production deployments, consider adding compression middleware to reduce bandwidth. In axum, you can wrap the service with tower_http::compression::CompressionLayer:

use tower_http::compression::CompressionLayer;

let app = Router::new()
    .nest_service("/", ServeDir::new("static").compress())
    .route("/", get(|| async { "Hello, world!" }));

This automatically compresses responses using gzip or br if the client supports it. Always verify that your static directory permissions are restricted to read-only for the web server user to prevent accidental modification of assets at runtime. If you need to serve files from a specific subdirectory (e.g., /assets), simply nest the service at that path instead of /.