Just added: Algorithms you can run and practice
Updated Jul 10, 2026 Edit on GitHub

What Rocket Is & Your First Server

You know Rust - ownership, traits, Result, and the slightly uncanny feeling of an attribute macro doing work above your function. Rust web code has a reputation for verbosity: routers assembled by hand, handler signatures spelled out, types threaded through builders. Rocket's answer: write #[get("/")] over a function, and you're done.

Rocket's whole personality is ergonomics - it wants your web code to read almost like Flask or Express, even though it's Rust underneath. It gets there by leaning hard on attribute macros: annotate a function with a route attribute, list your functions in routes![...], and Rocket reads those attributes and signatures and wires the whole thing together. The payoff is concise, readable code. The price is "magic" - behavior that isn't visible on the page. This guide makes that magic legible, one piece at a time.

The ergonomic trade

📝 Rocket - a Rust web framework built around attribute-route handlers. You write a normal function, put a route attribute like #[get("/")] above it, and that function becomes the code that runs for that path. Routing, request parsing, and response building are all inferred from the attribute and the function's signature.

Its sibling Rust frameworks mostly favor a builder style instead, assembling a router explicitly in code:

  • axum builds a Router and attaches handlers with method calls like .route("/", get(handler)) - ordinary Rust functions and values, no custom attributes.
  • actix-web is similar in spirit: register services and routes on an App builder, with a focus on raw throughput.
  • Rocket is attribute-first: the route lives in an attribute above the function, not in a builder call elsewhere. Less wiring on the page, more inference under it.

💡 Attributes give you the most concise, scannable web code in the Rust ecosystem - but they also hide machinery. When something doesn't compile, the error can point at a macro-generated thing you never wrote. The fix is a clear mental model of what each macro does, which is what we're building here. Want maximum explicitness with no macro magic? axum is the straightforward alternative. Want maximum ergonomics? You're in the right place.

Still fuzzy on why a framework calls your code instead of the other way around? What a framework even is is worth a detour - Rocket is a textbook case: "don't call us, we'll call you."

The mental model

Lock in the one sentence that makes every Rocket example readable:

The attribute is the route. The function signature is the request. The return type is the response. The macros wire those three together.

When you see a handler, read it in three glances: the attribute tells you which requests land here, the parameters tell you what gets pulled out of the request, and the return type tells you what gets sent back. This phase leans on the attribute and return type; the signature (path params, query, body, and request guards) gets its own treatment in Phases 2 and 3.

Your first server

Start a project and add Rocket as a dependency:

cargo new books-api
cd books-api
cargo add rocket

What just happened: cargo new scaffolded a normal Rust binary crate, and cargo add rocket wrote Rocket into Cargo.toml and fetched it. Rocket 0.5 is the current stable release, fully async under the hood - but the macros handle the async setup for you, so you won't have to think about that yet.

Now the smallest server that does something. Replace the contents of src/main.rs:

#[macro_use] extern crate rocket;

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

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

What just happened: read it through the three-part model.

  • #[macro_use] extern crate rocket; pulls Rocket's macros (get, routes, launch, and friends) into scope so you can use them bare. (use rocket::{get, routes, launch}; works too and skips the extern crate line - same result. The #[macro_use] form is what Rocket's own docs lead with.)
  • #[get("/")] is the route: "when a GET request arrives for the path /, run the function below." The attribute is the routing - there's no separate router line that mentions index.
  • fn index() -> &'static str { ... } is the handler. It takes no parameters (nothing needed from the request yet), and its return type is the response: Rocket turns a &'static str into a proper HTTP response with the right Content-Type, so you return a plain string and Rocket does the wrapping.
  • rocket::build() creates the application - an empty Rocket instance you then configure.
  • .mount("/", routes![index]) attaches your handlers. routes![index] collects the listed functions into a list Rocket understands, and mount hangs them off a base path (/ here, so index answers at /). A hundred handlers is the same call with a longer routes![...].
  • #[launch] sits on a function that returns the built Rocket, turning it into the program's entry point. Note the return type is a bare -> _ - the macro fills in the real (long, async) type for you. That underscore isn't a typo.

Now run it:

cargo run
$ cargo run
🚀 Rocket has launched from http://127.0.0.1:8000

What just happened: cargo run compiled and started your server. On launch, Rocket prints its configuration and the address it bound to - by default port 8000 on localhost. Hit it from another terminal:

curl localhost:8000
$ curl localhost:8000
Hello, Rocket

What just happened: the request for / matched your #[get("/")] route, Rocket called index(), took the &'static str it returned, and sent it back as the response body. A working Rust web server in a handful of lines - none of them a hand-assembled router.

#[launch] vs. writing main yourself

#[launch] is a convenience. Rocket is async, so the "real" entry point is an async main running on Rocket's runtime - you can write it out by hand:

#[rocket::main]
async fn main() -> Result<(), rocket::Error> {
    rocket::build()
        .mount("/", routes![index])
        .launch()
        .await?;
    Ok(())
}

What just happened: #[rocket::main] sets up the async runtime so you can write an async fn main, and you explicitly .launch().await? the built app yourself - exactly what #[launch] generates under the hood.

💡 Use #[launch] unless you need to run code before the server starts (opening a database connection, reading a startup file with ? error handling). It's the right default here - it keeps the entry point to a single expression.

Our running example: a books API

Throughout this guide we grow one small service: a books API. The thing we're serving is a book:

struct Book {
    id: u32,
    title: String,
    author: String,
}

What just happened: nothing yet - just a plain Rust struct. But it's the spine of every phase ahead: route to individual books by id (Phase 2), accept new ones as JSON bodies (Phase 3), return them as JSON (Phase 4), store them in shared state (Phase 5), and wrap it all in a real CRUD API with error handling (Phase 6). Next up: the part we skipped - the signature - starting with paths that carry data, like /books/<id>.

Recap

  1. Rocket is the ergonomic Rust web framework: you write a function, put a route attribute like #[get("/")] above it, and that function becomes the handler. The code reads close to Flask or Express.
  2. The trade vs. siblings: axum and actix-web favor explicit builders (Router::new().route(...)), while Rocket is attribute-first - concise and scannable, at the cost of macro "magic" this guide makes legible.
  3. The mental model that unlocks everything: the attribute is the route, the function signature is the request, and the return type is the response - macros wire them together.
  4. A first server is tiny: #[get("/")] over a handler, rocket::build().mount("/", routes![index]), and #[launch] as the entry point. Run with cargo run; Rocket prints its config and binds to port 8000 by default.
  5. #[launch] is shorthand for a hand-written #[rocket::main] async fn main that builds and .launch().awaits the app. Use #[launch] unless you need setup code before the server starts.
  6. The running example is a books API built around a Book { id, title, author } struct, which we'll grow across the guide.

Quick check

Three questions on the ideas that have to stick - what makes Rocket Rocket, what the core macros do, and how the entry point works:

[
  {
    "q": "What is Rocket's defining design choice compared to axum and actix-web?",
    "choices": [
      "It defines routes with attribute macros placed above handler functions, rather than assembling a router with explicit builder calls",
      "It is the only Rust web framework that is synchronous instead of async",
      "It ships a built-in ORM and admin panel like a batteries-included framework",
      "It compiles to JavaScript so it can run in the browser"
    ],
    "answer": 0,
    "explain": "Rocket is attribute-first: #[get(\"/\")] above a function declares the route. axum and actix-web favor explicit builders like Router::new().route(...). The trade is ergonomic, concise code at the cost of macro magic."
  },
  {
    "q": "In the minimal server, what does `.mount(\"/\", routes![index])` do?",
    "choices": [
      "Attaches the handlers listed in routes![...] to the application at the base path \"/\"",
      "Starts the web server and blocks waiting for requests",
      "Declares that index() responds to POST requests instead of GET",
      "Reads the Rocket.toml config file and applies it"
    ],
    "answer": 0,
    "explain": "routes![index] is a macro that collects the listed handler functions, and .mount(\"/\", ...) hangs them off a base path on the built Rocket instance. The route attribute (#[get]) is what sets the method and path; #[launch] is what starts the server."
  },
  {
    "q": "What does the `#[launch]` attribute do?",
    "choices": [
      "It turns a function that returns the built Rocket into the program's entry point and runs the app - shorthand for a manual #[rocket::main] async fn main that builds then .launch().await's it",
      "It launches a separate browser window to preview the API",
      "It marks a handler as the default route when no other path matches",
      "It is required on every handler function, not just the entry point"
    ],
    "answer": 0,
    "explain": "#[launch] sits on a function returning the built Rocket (note the bare -> _, which the macro fills in). It generates the async entry point that builds and launches the app, so you don't write #[rocket::main] async fn main yourself."
  }
]

Before the quiz: without looking back, say (or jot down) the core idea of this phase in your own words.

Check your understanding 3 questions

1. What is Rocket's defining design choice compared to axum and actix-web?

2. In the minimal server, what does `.mount("/", routes![index])` do?

3. What does the `#[launch]` attribute do?