Rocket

Rust web framework focused on developer experience. Enables rapid development through type safety and code generation features. Migrated to stable version.

RustFrameworkWeb DevelopmentAsynchronousType-safe

GitHub Overview

rwf2/Rocket

A web framework for Rust.

Stars25,323
Watchers271
Forks1,608
Created:March 18, 2016
Language:Rust
License:Other

Topics

frameworkrocketrustwebweb-developmentweb-framework

Star History

rwf2/Rocket Star History
Data as of: 8/13/2025, 01:43 AM

Framework

Rocket

Overview

Rocket is an async framework for building high-performance and secure web applications in the Rust programming language.

Details

Rocket is one of the most popular web frameworks in the Rust ecosystem, with development started by Sergio Benitez in 2017. Designed with a focus on usability, security, extensibility, and speed, it leverages Rust's type system to the fullest to ensure compile-time correctness. By actively using procedural macros, it minimizes boilerplate code and provides an intuitive API. Adopting an async core with async/await support, it achieves high concurrency performance. Through unique concepts like Request Guards, Responders, and Fairings (middleware), it enables precise control of the request lifecycle. Combined with automatic security header configuration through Shield functionality, type-safe URI generation, JSON and HTTP/2 support, and other features necessary for modern web development, it enables rapid development of high-quality applications.

Pros and Cons

Pros

  • Type Safety: Compile-time correctness guarantees through Rust's type system
  • High Performance: Both memory safety and high-speed execution
  • Intuitive API: Clean code through procedural macros
  • Async Support: Concurrency processing with async/await
  • Built-in Security: Automatic security measures through Shield functionality
  • Flexibility: Extensibility through Fairings and custom guards
  • HTTP/2 Support: HTTP/2 support by default
  • Excellent Testing: Integrated testing functionality

Cons

  • Rust Learning Curve: Need to learn Rust language itself
  • Compile Time: Long compilation times for large projects
  • Lifetime Constraints: Constraints from Rust's ownership system
  • Ecosystem Size: Smaller library ecosystem compared to Node.js or Python
  • Nightly Rust Dependency: Some features require unstable Rust version (improving)
  • Debugging Difficulty: Complexity of debugging async processing

Key Links

Code Examples

Hello World

#[macro_use] extern crate rocket;

#[get("/")]
fn index() -> &'static str {
    "Hello, world!"
}

#[get("/hello/<name>")]
fn hello(name: &str) -> String {
    format!("Hello, {}!", name)
}

#[launch]
fn rocket() -> _ {
    rocket::build().mount("/", routes![index, hello])
}

Routing and Parameters

use rocket::serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
struct User {
    id: u64,
    name: String,
    email: String,
}

// Path parameters
#[get("/users/<id>")]
fn get_user(id: u64) -> String {
    format!("User ID: {}", id)
}

// Query parameters
#[get("/search?<q>&<limit>")]
fn search(q: String, limit: Option<usize>) -> String {
    let limit = limit.unwrap_or(10);
    format!("Search: {} (limit: {})", q, limit)
}

// Multi-segment paths
#[get("/files/<path..>")]
fn files(path: std::path::PathBuf) -> String {
    format!("File path: {}", path.display())
}

#[launch]
fn rocket() -> _ {
    rocket::build().mount("/", routes![get_user, search, files])
}

JSON API and Request Guards

use rocket::serde::{Deserialize, Serialize, json::Json};
use rocket::{State, get, post, put, delete};
use std::collections::HashMap;
use std::sync::Mutex;

#[derive(Serialize, Deserialize, Clone)]
#[serde(crate = "rocket::serde")]
struct User {
    id: Option<u64>,
    name: String,
    email: String,
}

type UserStore = Mutex<HashMap<u64, User>>;
type IdCounter = Mutex<u64>;

#[get("/users")]
fn get_users(store: &State<UserStore>) -> Json<Vec<User>> {
    let store = store.lock().unwrap();
    let users: Vec<User> = store.values().cloned().collect();
    Json(users)
}

#[get("/users/<id>")]
fn get_user(id: u64, store: &State<UserStore>) -> Option<Json<User>> {
    let store = store.lock().unwrap();
    store.get(&id).map(|user| Json(user.clone()))
}

#[post("/users", data = "<user>")]
fn create_user(
    user: Json<User>,
    store: &State<UserStore>,
    counter: &State<IdCounter>
) -> Json<User> {
    let mut store = store.lock().unwrap();
    let mut counter = counter.lock().unwrap();
    
    *counter += 1;
    let id = *counter;
    let mut new_user = user.into_inner();
    new_user.id = Some(id);
    
    store.insert(id, new_user.clone());
    Json(new_user)
}

#[put("/users/<id>", data = "<user>")]
fn update_user(id: u64, user: Json<User>, store: &State<UserStore>) -> Option<Json<User>> {
    let mut store = store.lock().unwrap();
    if store.contains_key(&id) {
        let mut updated_user = user.into_inner();
        updated_user.id = Some(id);
        store.insert(id, updated_user.clone());
        Some(Json(updated_user))
    } else {
        None
    }
}

#[delete("/users/<id>")]
fn delete_user(id: u64, store: &State<UserStore>) -> Option<()> {
    let mut store = store.lock().unwrap();
    store.remove(&id).map(|_| ())
}

#[launch]
fn rocket() -> _ {
    rocket::build()
        .manage(UserStore::new(HashMap::new()))
        .manage(IdCounter::new(0))
        .mount("/api", routes![get_users, get_user, create_user, update_user, delete_user])
}

Form Processing and Validation

use rocket::form::{Form, Errors};
use rocket::response::Redirect;
use rocket::serde::{Deserialize, Serialize};
use rocket::{get, post};

#[derive(FromForm, Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
struct ContactForm {
    #[field(validate = len(1..50))]
    name: String,
    #[field(validate = contains('@'))]
    email: String,
    #[field(validate = len(10..500))]
    message: String,
}

#[get("/contact")]
fn contact_form() -> &'static str {
    r#"
    <form method="post" action="/contact">
        <input type="text" name="name" placeholder="Name" required>
        <input type="email" name="email" placeholder="Email" required>
        <textarea name="message" placeholder="Message" required></textarea>
        <button type="submit">Send</button>
    </form>
    "#
}

#[post("/contact", data = "<form>")]
fn submit_contact(form: Form<ContactForm>) -> Result<Redirect, String> {
    // Process form data
    println!("Received contact from {}: {}", form.name, form.message);
    Ok(Redirect::to("/thank-you"))
}

#[post("/contact", data = "<form>", rank = 2)]
fn contact_error(form: Errors<ContactForm>) -> String {
    format!("Form errors: {:?}", form)
}

#[get("/thank-you")]
fn thank_you() -> &'static str {
    "Thank you for your message!"
}

#[launch]
fn rocket() -> _ {
    rocket::build().mount("/", routes![
        contact_form, submit_contact, contact_error, thank_you
    ])
}

Async Processing and Database

use rocket::serde::{Deserialize, Serialize, json::Json};
use rocket::{get, post, State};
use sqlx::{PgPool, Row};

#[derive(Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
struct Post {
    id: Option<i32>,
    title: String,
    content: String,
    published: bool,
}

#[get("/posts")]
async fn get_posts(db: &State<PgPool>) -> Result<Json<Vec<Post>>, String> {
    let posts = sqlx::query!("SELECT id, title, content, published FROM posts")
        .fetch_all(db.inner())
        .await
        .map_err(|e| format!("Database error: {}", e))?
        .into_iter()
        .map(|row| Post {
            id: Some(row.id),
            title: row.title,
            content: row.content,
            published: row.published,
        })
        .collect();
    
    Ok(Json(posts))
}

#[get("/posts/<id>")]
async fn get_post(id: i32, db: &State<PgPool>) -> Result<Json<Post>, String> {
    let post = sqlx::query!("SELECT id, title, content, published FROM posts WHERE id = $1", id)
        .fetch_one(db.inner())
        .await
        .map_err(|e| format!("Database error: {}", e))?;
    
    Ok(Json(Post {
        id: Some(post.id),
        title: post.title,
        content: post.content,
        published: post.published,
    }))
}

#[post("/posts", data = "<post>")]
async fn create_post(post: Json<Post>, db: &State<PgPool>) -> Result<Json<Post>, String> {
    let record = sqlx::query!(
        "INSERT INTO posts (title, content, published) VALUES ($1, $2, $3) RETURNING id",
        post.title,
        post.content,
        post.published
    )
    .fetch_one(db.inner())
    .await
    .map_err(|e| format!("Database error: {}", e))?;
    
    let mut created_post = post.into_inner();
    created_post.id = Some(record.id);
    
    Ok(Json(created_post))
}

#[launch]
fn rocket() -> _ {
    rocket::build()
        .manage(/* PgPool initialization */)
        .mount("/api", routes![get_posts, get_post, create_post])
}

Middleware (Fairings) and Authentication

use rocket::fairing::{Fairing, Info, Kind};
use rocket::{Request, Response, Data};
use rocket::request::{self, Request as Req, FromRequest};
use rocket::outcome::Outcome;
use rocket::http::Status;

// Log fairing
pub struct RequestLogger;

#[rocket::async_trait]
impl Fairing for RequestLogger {
    fn info(&self) -> Info {
        Info {
            name: "Request Logger",
            kind: Kind::Request | Kind::Response
        }
    }

    async fn on_request(&self, req: &mut Request<'_>, _: &mut Data<'_>) {
        println!("Request: {} {}", req.method(), req.uri());
    }

    async fn on_response<'r>(&self, req: &'r Request<'_>, res: &mut Response<'r>) {
        println!("Response: {} - {}", req.uri(), res.status());
    }
}

// Authentication guard
struct ApiKey(String);

#[rocket::async_trait]
impl<'r> FromRequest<'r> for ApiKey {
    type Error = &'static str;

    async fn from_request(req: &'r Req<'_>) -> request::Outcome<Self, Self::Error> {
        if let Some(key) = req.headers().get_one("X-API-Key") {
            if key == "secret-api-key" {
                Outcome::Success(ApiKey(key.to_string()))
            } else {
                Outcome::Error((Status::Unauthorized, "Invalid API key"))
            }
        } else {
            Outcome::Error((Status::BadRequest, "Missing API key"))
        }
    }
}

#[get("/protected")]
fn protected_route(_key: ApiKey) -> &'static str {
    "This is a protected route!"
}

#[launch]
fn rocket() -> _ {
    rocket::build()
        .attach(RequestLogger)
        .mount("/", routes![protected_route])
}

Testing

#[cfg(test)]
mod tests {
    use super::*;
    use rocket::local::blocking::Client;
    use rocket::http::{Status, ContentType};

    #[test]
    fn test_hello_world() {
        let client = Client::tracked(rocket()).expect("valid rocket instance");
        let response = client.get("/").dispatch();
        assert_eq!(response.status(), Status::Ok);
        assert_eq!(response.into_string(), Some("Hello, world!".into()));
    }

    #[test]
    fn test_hello_name() {
        let client = Client::tracked(rocket()).expect("valid rocket instance");
        let response = client.get("/hello/John").dispatch();
        assert_eq!(response.status(), Status::Ok);
        assert_eq!(response.into_string(), Some("Hello, John!".into()));
    }

    #[test]
    fn test_json_api() {
        let client = Client::tracked(rocket()).expect("valid rocket instance");
        
        // POST request
        let new_user = User {
            id: None,
            name: "Test User".to_string(),
            email: "[email protected]".to_string(),
        };
        
        let response = client
            .post("/api/users")
            .header(ContentType::JSON)
            .json(&new_user)
            .dispatch();
        
        assert_eq!(response.status(), Status::Ok);
        
        // GET request
        let response = client.get("/api/users").dispatch();
        assert_eq!(response.status(), Status::Ok);
    }

    #[test]
    fn test_protected_route() {
        let client = Client::tracked(rocket()).expect("valid rocket instance");
        
        // Without API key
        let response = client.get("/protected").dispatch();
        assert_eq!(response.status(), Status::BadRequest);
        
        // With valid API key
        let response = client
            .get("/protected")
            .header(("X-API-Key", "secret-api-key"))
            .dispatch();
        assert_eq!(response.status(), Status::Ok);
    }
}