---
title: How to use rocket crate in Rust web framework
summary: Set up Rocket 0.5 by adding the crate, defining handlers with #[get]/#[post], and starting the server with #[launch]. Use State for shared data and Json for request bodies.
lang: en
slug: how-to-use-rocket-crate-in-rust-web-framework
published: 2026-04-17T12:05:42+00:00
updated: 2026-04-25T22:00:13+00:00
canonical: https://www.rustfaq.org/en/how-to-use-rocket-crate-in-rust-web-framework/
---

# How to use rocket crate in Rust web framework

## When you want a Rust web server that gets out of your way

You've decided to build a web app or a small HTTP API in Rust. You've heard of Actix and Axum and warp, and somewhere in the mix is a framework called Rocket that everyone says is friendly. You want endpoints defined with attributes, type-safe URL parameters, and not too many ceremony layers between you and a working "Hello, world." That's Rocket's pitch in one paragraph.

Rocket leans hard on macros and Rust's type system to make web code read more like a controller in Flask or FastAPI than like a wiring diagram. You'll write `#[get("/users/<id>")]` and Rocket will figure out how to parse the URL segment, give it to your handler as the right type, and return the response you produced. The cost is some compile-time pain (proc macros aren't fast) and a slightly opinionated structure. The benefit is that small apps stay genuinely small.

## Setting up the project

Start with a fresh binary crate:

```bash
# Create a new binary project for your web service.
cargo new my_api
cd my_api
```

Then edit `Cargo.toml`. Modern Rocket (0.5 and later) is async by default, which means you don't need to add `tokio` separately. You just enable it through Rocket's feature.

```toml
# Cargo.toml
[package]
name = "my_api"
version = "0.1.0"
edition = "2021"

[dependencies]
# 0.5 is the modern, async, stable Rocket. The "json" feature pulls in
# serde-based JSON support, which you almost always want.
rocket = { version = "0.5", features = ["json"] }
serde = { version = "1", features = ["derive"] }
```

Older tutorials may show `0.4` and a synchronous API based on `rocket::ignite()`. Treat those as historical. The current entry point is `rocket::build()` and the launch is async.

## The minimal "Hello, world"

Replace `src/main.rs` with this:

```rust
// `#[macro_use]` lets us use Rocket's macros (get, post, routes, launch)
// without prefixing them every time.
#[macro_use] extern crate rocket;

// A handler. The attribute defines the HTTP method and the URL pattern.
// Returning a string is the simplest possible response: it's sent
// as a 200 OK with content-type text/plain.
#[get("/")]
fn index() -> &'static str {
    "Hello, world!"
}

// `#[launch]` builds and returns the Rocket instance. The runtime
// starts the server for us.
#[launch]
fn rocket() -> _ {
    rocket::build().mount("/", routes![index])
}
```

Run it:

```bash
cargo run
```

You'll see Rocket print its startup banner, the routes it mounted, and the address it's listening on (by default `127.0.0.1:8000`). Hit it from another terminal:

```bash
curl http://127.0.0.1:8000/
# Hello, world!
```

A few things to call out about that tiny snippet. `#[launch]` is a Rocket macro that wraps the standard async runtime setup so you don't have to write `#[rocket::main]` and an explicit `.launch().await` (you can write that if you want; `#[launch]` is a shortcut). The `_` return type is intentional: it means "let the compiler figure it out," because Rocket's builder type is unwieldy to write. And `mount("/", routes![...])` is how you attach a list of routes to a base path; you can mount multiple groups under different prefixes if you like.

## Path parameters and type-safe extraction

The thing Rocket is genuinely good at: turning bits of the URL into typed Rust values for you.

```rust
#[macro_use] extern crate rocket;

// `<id>` in the path is a placeholder. Rocket parses the segment
// and converts it to u32. If parsing fails, Rocket returns a 404
// without ever calling the handler.
#[get("/users/<id>")]
fn show_user(id: u32) -> String {
    format!("User #{id}")
}

// You can have multiple typed segments. Each one is parsed.
#[get("/posts/<year>/<slug>")]
fn show_post(year: u16, slug: &str) -> String {
    format!("Post from {year}: {slug}")
}

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

Behind the scenes, Rocket uses a trait called `FromParam` to convert the URL segment to your parameter type. Numbers, strings, and `Uuid` (with the right feature flag) all work out of the box. You can implement `FromParam` for your own types: a typical use is parsing IDs into a domain-specific newtype wrapper.

If somebody hits `/users/abc`, the `u32` parse fails and the request short-circuits to a 404. You don't have to write any matching logic. Compare to a framework where you'd have to manually check the segment and return a 400. Rocket trades flexibility for ergonomic safety here.

## Handling JSON requests and responses

Pretty much any real API takes JSON in and sends JSON out. Rocket's `json` feature makes that close to one line of code per side.

```rust
#[macro_use] extern crate rocket;

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

// Derive the serde traits so this struct can be (de)serialized.
// `crate = "rocket::serde"` is needed because Rocket re-exports serde
// and the derive macros need to know which path to use.
#[derive(Deserialize)]
#[serde(crate = "rocket::serde")]
struct CreateUser {
    name: String,
    age: u8,
}

#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
struct UserResponse {
    id: u32,
    name: String,
}

// `data = "<input>"` tells Rocket to deserialize the request body
// as the type given (Json<CreateUser> here) and pass it as `input`.
#[post("/users", data = "<input>")]
fn create_user(input: Json<CreateUser>) -> Json<UserResponse> {
    let user = UserResponse {
        id: 42,
        name: input.name.clone(),
    };
    Json(user)
}

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

Test it from a terminal:

```bash
curl -X POST http://127.0.0.1:8000/users \
     -H 'Content-Type: application/json' \
     -d '{"name": "Ada", "age": 36}'
# {"id":42,"name":"Ada"}
```

If the JSON is malformed, Rocket returns a 400. If a required field is missing, Rocket returns a 422. You don't write any of that yourself. Same trade-off as before: less ceremony, less flexibility.

## A more realistic example: a tiny notes service

Let's wire up a service with multiple routes, in-memory state, and proper error responses.

```rust
#[macro_use] extern crate rocket;

use rocket::{State, http::Status, response::status::Custom};
use rocket::serde::{Deserialize, Serialize, json::Json};
use std::sync::Mutex;

#[derive(Serialize, Clone)]
#[serde(crate = "rocket::serde")]
struct Note {
    id: u32,
    text: String,
}

#[derive(Deserialize)]
#[serde(crate = "rocket::serde")]
struct NewNote {
    text: String,
}

// Rocket's `State<T>` gives every handler access to shared state.
// We wrap a Vec in a Mutex so handlers can safely mutate it.
struct Db {
    notes: Mutex<Vec<Note>>,
}

#[get("/notes")]
fn list(db: &State<Db>) -> Json<Vec<Note>> {
    let guard = db.notes.lock().unwrap();
    Json(guard.clone())
}

#[get("/notes/<id>")]
fn show(id: u32, db: &State<Db>) -> Result<Json<Note>, Status> {
    let guard = db.notes.lock().unwrap();
    guard.iter().find(|n| n.id == id)
        .cloned()
        .map(Json)
        .ok_or(Status::NotFound)
}

#[post("/notes", data = "<input>")]
fn create(input: Json<NewNote>, db: &State<Db>) -> Custom<Json<Note>> {
    let mut guard = db.notes.lock().unwrap();
    let id = guard.len() as u32 + 1;
    let note = Note { id, text: input.text.clone() };
    guard.push(note.clone());
    Custom(Status::Created, Json(note))
}

#[launch]
fn rocket() -> _ {
    rocket::build()
        .manage(Db { notes: Mutex::new(Vec::new()) })
        .mount("/", routes![list, show, create])
}
```

The interesting bits are `State<Db>` (Rocket's dependency injection for shared state, registered with `.manage(...)`), the `Mutex` (because handlers run concurrently and a plain `Vec` isn't thread-safe), and `Status::NotFound` returned via `Result` (Rocket converts a `Result::Err(Status)` to that HTTP status automatically).

For real persistence you'd reach for SQLx, Diesel, or sea-orm and replace the in-memory `Mutex<Vec<...>>` with database calls. The shape of the code stays the same.

## Common pitfalls

You're on Rocket 0.4 and copying examples from the docs. Symptoms include `rocket::ignite()` not existing, attributes prefixed with `#[rocket::get]` instead of plain `#[get]`, and async errors. The fix: pin a major version explicitly in `Cargo.toml` and follow tutorials matching that version. The 0.4 to 0.5 migration was substantial.

You forgot `#[macro_use] extern crate rocket;` at the top of `main.rs`. Symptoms: `routes!` macro not found, `#[get]` not recognized. Fix: add the line. Yes, `extern crate` is mostly obsolete in Edition 2018+, but Rocket's macros are simpler to use this way.

You hit "the trait `Sync` is not implemented for X" when registering managed state. State must be `Send + Sync` because handlers can run on multiple threads. Wrap mutable bits in `Mutex` or `RwLock`.

You set up Rocket in production and it's only listening on localhost. Set `ROCKET_ADDRESS=0.0.0.0` (or use a `Rocket.toml`) to bind on all interfaces. Same for `ROCKET_PORT`.

## When to use Rocket vs alternatives

Rocket shines when you want a small, expressive web app and you don't mind a slightly slower compile. Tutorials and small services land here naturally.

Reach for Axum when you want tighter integration with the Tokio ecosystem, more explicit middleware composition, and a thinner abstraction layer. Axum is the current mainstream choice for production APIs.

Reach for Actix-web when you want maximum throughput in benchmarks and don't mind the actor-flavored API. It's older and has more battle-testing in industry.

The core skills transfer between them. Once you're comfortable with one, the others are an afternoon to pick up.

## Where to go next

If you stick with Rocket, the framework's own guide is excellent. From here, you'll want to understand state and routing in more detail.

[How to Build a Web Application with Rocket in Rust](/en/how-to-build-a-web-application-with-rocket-in-rust/)
