Last updated: April 13, 2025
Table of Contents
1. Introduction: Meet Rocket
Rocket is a popular web framework for Rust that focuses on ease of use, type safety, and developer productivity. It makes heavy use of Rust's powerful macro system to provide declarative routing and request handling, often resulting in concise and readable code.
This guide provides a beginner-friendly walkthrough of building a simple RESTful API using Rocket. We'll cover setting up a project, defining routes using attributes, handling path parameters, and working with JSON data.
As always, ensure you have Rust and Cargo installed (see Getting Started with Rust).
2. Project Setup
2.1 A Note on Nightly Rust (Historically)
Historically, Rocket required the nightly
version of the Rust compiler due to its extensive use
of unstable procedural macro features. While recent versions (like Rocket 0.5+) have made significant strides
towards stable Rust compatibility, some advanced features might still benefit from or require nightly.
For this basic guide, we will aim for stable Rust compatibility. If you encounter features requiring nightly,
you can switch your toolchain using rustup override set nightly
within your project directory
(use with understanding).
2.2 Adding Dependencies
Create a new Rust binary project:
cargo new rocket_api
cd rocket_api
Add Rocket and Tokio (as Rocket uses it internally) to your Cargo.toml
:
[package]
name = "rocket_api"
version = "0.1.0"
edition = "2021"
[dependencies]
rocket = { version = "0.5.0", features = ["json"] } # Check crates.io for latest v0.5+
# Note: Rocket uses Tokio internally, but usually you don't need to add it explicitly unless using Tokio features directly.
# If needed: tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] } # Needed for JSON feature
We enable the "json"
feature flag for Rocket to easily handle JSON request bodies and responses
using Serde.
Alternatively, use cargo add
:
cargo add rocket@0.5.0 --features json
cargo add serde --features derive # Rocket's json feature uses serde
3. Creating a Basic Server
Replace the contents of src/main.rs
with a minimal Rocket application:
#[macro_use] extern crate rocket; // Required for Rocket macros
// Define a handler function decorated with a route attribute
#[get("/")] // Handles GET requests to the root path "/"
fn index() -> &'static str {
"Hello, Rocket World!"
}
#[launch] // Macro to generate the main function and launch Rocket
fn rocket() -> _ { // The return type `_` lets the compiler infer `impl Launch`
println!("🚀 Rocket launching at http://127.0.0.1:8000");
// Build the Rocket instance and mount routes
rocket::build().mount("/", routes![index])
}
Key Rocket concepts:
#[macro_use] extern crate rocket;
: Imports Rocket's macros into scope.#[get("/")]
: A route attribute macro defining an HTTP GET endpoint for the path/
. Rocket has corresponding macros for other methods (#[post]
,#[put]
,#[delete]
, etc.).fn index() -> ...
: The handler function associated with the route. Rocket uses the function signature to determine how to handle requests and responses.#[launch]
: A special macro that generates theasync main
function needed to initialize and start the Rocket server.rocket::build()
: Creates a new Rocket application instance..mount("/", routes![index])
: Mounts one or more routes (defined in theroutes!
macro) at a base path (/
in this case).
Run the application using cargo run
. Rocket will start the server (defaulting to port 8000).
Access http://127.0.0.1:8000/
to see "Hello, Rocket World!".
4. Routing with Attributes
Adding more routes is straightforward using attribute macros:
#[macro_use] extern crate rocket;
#[get("/")]
fn index() -> &'static str {
"Hello, Rocket World!"
}
// New handler and route
#[get("/ping")]
fn ping() -> &'static str {
"pong"
}
#[launch]
fn rocket() -> _ {
println!("🚀 Rocket launching at http://127.0.0.1:8000");
// Add the new route to the routes! macro
rocket::build().mount("/", routes![index, ping])
}
Restart the server (cargo run
) and visit http://127.0.0.1:8000/ping
.
5. Handling Path Parameters
Rocket extracts path parameters directly into function arguments if the types match.
#[macro_use] extern crate rocket;
// ... (index, ping handlers) ...
// Define parameter in the route path
#[get("/greet/")]
fn greet(name: &str) -> String { // Parameter type matches segment (&str)
format!("Hello, {}!", name)
}
#[launch]
fn rocket() -> _ {
println!("🚀 Rocket launching at http://127.0.0.1:8000");
rocket::build().mount("/", routes![index, ping, greet]) // Mount the greet route
}
Accessing http://127.0.0.1:8000/greet/RocketFan
will return "Hello, RocketFan!". Rocket
automatically parses the <name>
segment into the name: &str
argument.
6. Handling JSON (Request and Response)
Rocket uses Serde (enabled via the "json"
feature) for handling JSON.
6.1 Serde Dependency
Ensure serde
is in Cargo.toml
with the derive
feature, and Rocket has
the json
feature enabled (as done in the setup step).
6.2 Defining Structs
Define Rust structs and derive Serialize
(for responses) and/or Deserialize
(for
requests).
use serde::{Serialize, Deserialize}; // Import traits
use rocket::serde::json::Json; // Import Rocket's JSON wrapper
#[derive(Serialize)] // For JSON responses
struct StatusMessage {
status: String,
healthy: bool,
}
#[derive(Deserialize)] // For JSON request bodies
struct CreateUserRequest {
username: String,
email: String,
}
6.3 Returning JSON
Wrap your response struct in rocket::serde::json::Json
.
// Add this handler
#[get("/status")]
fn status() -> Json {
Json(StatusMessage {
status: "OK".to_string(),
healthy: true,
})
}
// --- Remember to mount the route in the main `rocket()` function ---
// rocket::build().mount("/", routes![index, ping, greet, status])
Accessing /status
will now return the JSON representation of StatusMessage
.
6.4 Accepting JSON (POST Example)
To accept JSON in a request body (e.g., for a POST request), use Json<T>
as a function
argument, where T
is your struct deriving Deserialize
.
// Add this handler
#[post("/users", format = "json", data = "<user_data>")] // Specify format and data param name
fn create_user(user_data: Json) -> Json {
// Access deserialized data via user_data.into_inner() or directly
println!("Received new user: {} ({})", user_data.username, user_data.email);
// In a real app, you'd save the user to a database here
Json(StatusMessage { // Respond with success status
status: "Created".to_string(),
healthy: true, // Or appropriate status
})
}
// --- Remember to mount the route in the main `rocket()` function ---
// rocket::build().mount("/", routes![..., status, create_user])
You can test this endpoint using curl
or an API client:
curl -X POST http://127.0.0.1:8000/users \
-H "Content-Type: application/json" \
-d '{"username": "testuser", "email": "test@example.com"}'
7. Running and Testing
- Run: Use
cargo run
. Rocket provides detailed startup logging. - Test: Use
curl
or API clients to hit your defined endpoints (/
,/ping
,/greet/<name>
,/status
,/users
[POST]).
8. Conclusion and Next Steps
Rocket offers an expressive and type-safe way to build web APIs in Rust, leveraging attributes and macros for concise route definitions and request handling. We've covered the essentials: setup, routing, path parameters, and JSON handling.
Further areas to explore in Rocket include:
- Request Guards (for validation, authentication).
- Managing State.
- Handling Forms and Query Strings.
- Working with Databases.
- Configuration and Environment Variables.
- Templating Engines (for server-rendered HTML).
Rocket's focus on developer experience makes it a compelling choice for those who appreciate its declarative style, especially if coming from frameworks in other languages with similar philosophies.
9. Additional Resources
Related Articles
- Getting Started with Rust
- Rust Ownership and Borrowing Explained
- Common Rust Crates for Web Development
- REST vs GraphQL: API Design Comparison