Rocket Tutorial 01: Basics
Tags: rust, api, beginners, tutorial
Published: 2020-11-01 17:41:24

<span>Photo by <a href="https://unsplash.com/@jipy32?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Jean-Philippe Delberghe</a> on <a href="https://unsplash.com/s/photos/space-shuttle?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a>, modified(cropped)</span>

In this series we are going to explore how to make a Rust server using [Rocket](https://rocket.rs)

The code for this tutorial can be found in this repository: [github.com/davidedelpapa/rocket-tut](https://github.com/davidedelpapa/rocket-tut)

```bash
git clone https://github.com/davidedelpapa/rocket-tut.git
cd rocket-tut
git checkout tags/tut1
```

## Setup

As first thing we create a new project

``` bash
cargo new rocket-tut
cd rocket-tut
```

We need to override the default of `rustup` for this project and use Rust Nightly.

``` bash
rustup override set nightly
```

we will use `cargo add` from the [cargo edit](https://github.com/killercup/cargo-edit) crate (if you do not have it, just run `cargo install cargo-edit`):

``` bash
cargo add rocket
```

And in fact the `[dependencies]` section of our *Cargo.toml* should look like the following:

```
[dependencies]
rocket = "0.4.5"
```

Fantastic!

Let's substitute the default *src/main.rs* with the following:

```rust
#![feature(proc_macro_hygiene, decl_macro)]

#[macro_use] use rocket::*;


#[get("/echo/<echo>")]
fn echo_fn(echo: String) -> String {
    format!("{}", echo)
}

fn main() {
    rocket::ignite().mount("/", routes![echo_fn]).launch();
}

```

Let's quickly analyze the code:

- we will use the rocket crate with the `#[macro_use]` directive.
- we will define a function for each route we need, in this case `echo_fn()`. This function takes as a parameter a `String`.
- we decorate each route with a directive that explains the HTTP method for the route (in this case `GET`), with the address of the route itself, and any parameter. Notice that the parameters are specified in `<>` and they must match the parameter names of the function.
- the function returns a `String` with the body of the page.
- lastly, in `main()`, we "ignite" the rocket engine (that is we start it), and we mount any route to it. After this, we "launch" the rocket (i.e., we start the server).

Now let's point the browser to [http://localhost:8000](http://localhost:8000). We will be greeted with a *404 page* by Rocket, since we will not set up the root (**/**) route.

![404 Error](https://dev-to-uploads.s3.amazonaws.com/i/nk9dlqoklx181zx8jnt6.jpg)

Let's get to the route we set up, */echo/*:

[http://localhost:8000/echo/test](http://localhost:8000/echo/test)

Our page greets us back with the same text we put after the *echo/*

![Echo route working!](https://dev-to-uploads.s3.amazonaws.com/i/ixfhp6iv31p3b47mkqj0.jpg)

The echo route is working!

Great!

## Serving static files

Let's see what else can we do now. We could make a static file server, for example.

Let's start it now with Cargo Watch (if it's not installed: `cargo install cargo-watch`)

```bash
cargo watch -x run
```

Let's create a folder *static/* and add an image file to it.

Now let's add the following to the *src/main.rs*:

```rust
use std::path::{Path, PathBuf};

#[macro_use] use rocket::*;
use rocket::response::NamedFile;
```

We will use the standard library's functions to deal with the filesystem.
At the same time we use rocket's `NamedFile` response to serve a file

Next we add a new route for file serving:

```rust
#[get("/file/<file..>")]
fn fileserver(file: PathBuf) -> Option<NamedFile> {
    NamedFile::open(Path::new("static/").join(file)).ok()
}
```

The route converts the parameter to a `PathBuf`: this ensures there's not a bug that allows to excalate the filesystem and go around inside the server fetching files.

We `join` the path to the `Path` of the *static/* folder we just created.

Finally we need to update our list of routes, adding the `fileserver` function:

```rust
fn main() {
    rocket::ignite().mount("/", routes![echo_fn, fileserver]).launch();
}
```
All easy:

![Serving an image](https://dev-to-uploads.s3.amazonaws.com/i/evi4ip7dq04grz40t1fu.jpg)

There's even a easier way. On the command line, let's add the crate `rocket-contrib`:

```bash
cargo add rocket_contrib
```

Now let's update the *src/main.rs* to the following:

```rust
#![feature(proc_macro_hygiene, decl_macro)]

#[macro_use] use rocket::*;
use rocket_contrib::serve::StaticFiles;

#[get("/echo/<echo>")]
fn echo_fn(echo: String) -> String {
    format!("{}", echo)
}

fn main() {
    rocket::ignite()
        .mount("/", routes![echo_fn])
        .mount("/files", StaticFiles::from("static/"))
        .launch();
}
```

As you can see we are using now a module from the `rocket_contrib`, *serve::StaticFiles*. This does exactly the "dirty work" of file serving.

We can see that we used as mount-point *"/files"* (while before it was `/` + `file` -> `/file`). We still fetch the files from the *static/* folder (with `StaticFiles::from()`)

Running it we can see its effectiveness.

![Same file, different manner](https://dev-to-uploads.s3.amazonaws.com/i/7mnm7s09l3bxipzl0oo0.jpg)

## Secure the server

If we get all de defaults in `rocket_contrib` we will have the fileserver, as well as support for JSON (some of it later).

However, `rocket_contrib` has got more interesting features, such as a security handler (a `helmet` similar to `helmetjs`), connections and pooling of databases, and two different templating engines, [tera](https://github.com/Keats/tera), and [handlebars](https://handlebarsjs.com/), together with a `uuid` function.

Let's protect our code with `helmet`! Let's update the `rocket_contrib` features:

```bash
cargo add rocket_contrib --features helmet
```

Do not worry about the fileserver: as long as the defaults are not excluded, it will be present.

Now let's secure our code.

We import the `SpaceHelmet` after `rocket`:

```rust
use rocket_contrib::helmet::SpaceHelmet;
```

The we will use it after we ignite the rocket:

```rust
fn main() {
    rocket::ignite()
        .attach(SpaceHelmet::default())
        .mount("/", routes![echo_fn])
        .mount("/files", StaticFiles::from("static/"))
        .launch();
}
```

[Here](https://api.rocket.rs/v0.4/rocket_contrib/helmet/index.html#supported-headers) you can consult a list of the default headers set by *helmet*.

This way it's super easy to secure our headers.

## Testing our server

Now before wrapping out this first tutorial, let's see how to test our server. Beware, it's Rocket easy, not rocket science!

As first thing, we factor out the creation of the Rocket instance:

We create a function `rocket()` which ingnites, and prepares out Rocket instance, without launching it. The function will return this Rocket instance:

```rust
fn rocket() -> rocket::Rocket {
    rocket::ignite().attach(SpaceHelmet::default())
    .mount("/", routes![echo_fn])
    .mount("/files", StaticFiles::from("static/"))
}
```

Now we need to call this instance from our `main()`:

```rust
 fn main() {
    rocket().launch();
}
```

This is totally equivalent from our last configuration, except that in this way we can create our Rocket instance for testing purposes, outside of `main()`.

Now we need to add a test at the end of the *src/main.rs*:

```rust
#[cfg(test)]
mod test {
    use super::rocket;
    use rocket::local::Client;
    use rocket::http::Status;

    #[test]
    fn echo_test() {
        let client = Client::new(rocket()).expect("Valid Rocket instance");
        let mut response = client.get("/echo/test_echo").dispatch();
        assert_eq!(response.status(), Status::Ok);
        assert_eq!(response.body_string(), Some("test_me".into()));
    }
}
```

With `super::rocket` we get the `rocket()` function in order to init the instance

We define the test function `echo_test()` which as first thing sets up a client to poll the server. The first thing we test is actually that we `excpect()` a valid instance of Rocket!

Then we dispatch a `GET` request to the server, at the route `echo/`, sending it a *"test_echo"* parameter. We store the `response`.

We test for two things: the first is that the response status is `Ok` (that is `400`).
The second thing is that the body of the response must match our echo parameter. In this case the code is set up to fail, since we sent *"test_echo"* as parameter, and we are testing against *"test_me"*.

A quick run with `cargo test` should in fact return a test failure:

```
cargo test

....

thread 'test::echo_test' panicked at 'assertion failed: `(left == right)`
  left: `Some("test_echo")`,
 right: `Some("test_me")`', src/main.rs:33:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    test::echo_test

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out

error: test failed, to rerun pass '--bin rocket-tut'
```

One little adjustment to the code, `assert_eq!(response.body_string(), Some("test_echo".into()));` and the test runs smooth:

```
running 1 test
test test::echo_test ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```

## Conclusion

Ok, that is all for today.

We have introduced *Rocket*, its basic usage, the securing that can be done using `rocket_contrib`, and how to run tests.

In our next installment we will see how to create a CRUD server with it. Stay tuned!