Web service with Rust, Rocket and Diesel
By: Marco Amann
Tags: Rust, Programming, Rocket, Webservice

Web service with Rust, Rocket and Diesel

A hands-on walk-trough on how to create a REST-API with Rust, Rocket and Diesel

Rocket, ready to launch. Photo by Felipe Simo on Unsplash

What and why

If you are comfortable writing web-services in Java, why should you try writing a web-service in Rust?

Microservices enable us to quickly scale our application according to consumer needs, requiring reasonable start-up times to achieve elasticity. When considering a serverless scenario, this aspect is even more important. But is it worth learning a new language and several frameworks just for a few milliseconds? Obviously not. But Rust has more in store than brisk startup times and raw performance. Over the course of this article, we will build a web-service and by the way have a look at several aspects that make rust an intriguing alternative for your next web service.

Creating the API

Before I detail on how to use rocket, let’s just have a quick overview of the available options to write HTTP-Endpoints in rust, you can refer to AWWY for more frameworks and libraries.

Ecosystem overview

There are several low-level frameworks like hyper or even the raw TCPListener if you need to build something truly minimal. But since I do not care that much about the last few nanoseconds of performance or the size of dependencies, we opted for a higher-level framework.

There is gotham, which claims to focus on stability and hence works with stable rust but coding with it leads to a bit verbose code, especially if you are used to the nice annotation syntax common in frameworks like flask or spring. Further, there is iron that I skipped for this article, since it lacks a beginner-friendly documentation. A promising framework is actix that is based on an actor model and is insanely fast but it was in midst of hefty refactoring at the time I had to decide for a framework, so I chose to postpone its evaluation. The framework that has given me the best first impression and therefore was evaluated further is rocket.

Starting the Rocket

The Rocket framework describes itself as follows:

Rocket is a web framework for Rust that makes it simple to write fast, secure web applications without sacrificing flexibility, usability, or type safety.

That sounds pretty nice, so how do you actually use it? First of all, since Rocket is heavily relying on macros, you need to have a nightly-toolchain for compiling Rocket. This is achieved by a simple rustup toolchain add nightly , if you have rustup installed.

In our example, we will make use of automatic JSON handling, so we need to add that in addition to the default rocket dependencies in our Cargo.toml file:

[dependencies.rocket]

version = "0.4.2"

features = ["private-cookies"] [dependencies.rocket_contrib]

default-features = false

features = ["json", "diesel_postgres_pool", "serve"]

With this, we can directly start coding our first endpoint. With Rocket you add a macro to a function you want to have called by the framework. Let’s create a simple health endpoint, that returns ok if you send it a GET request:

The #[get(...) annotation is a macro that enables rocket to invoke the health method. In the main function, in the .mount() call, we make use of another macro, the routes! one, that complements the above get-macro (you will see later, what this does exactly).

If you want, you can run that code with cargo run already (optionally add the --release flag to have a binary with all the optimizations). The nice part about this is, we do no have any external dependencies in our resulting executable (except for glibc but that should be installed anyways), so we do not have to care about matching python versions, updating ruby environments or select the correct JRE vendor when we deploy our code. This means that you can simply copy the executable to your server and run it there. But let’s get back to the code for now.

Since an endpoint without any parameters is quite boring, let’s expand the example with a request object. (I will omit adding the routes since this is always the same here).

We can simply add strings:

#[get("/hello/<name>")]

fn hello(name: String) -> String {

format!("hello {}", name)

}

and more complex objects:

In the above example, the Stay type used in the function is directly parsed by rocket and injected into the function call. How does this work? There is surprisingly little magic involved here: All types that can be magically generated from a request have to implement the conveniently named trait FromRequest . The basic, built-in types like String or i32 already to this, so you are done in that case. Since we wrapped our Stay type in a Json Type, actually Json has to implement FromRequest and not our Stay type. Since Json is coming from the rocket-contrib crate and was designed to do exactly this, we are pretty much set. The only thing to note here is, that types that are used with Json must derive the serde traits ( #[derive(Serialize, Deserialize)] above the struct declaration).

But the FromRequest trait is much more powerful that just deserializing JSON. Let’s have a closer look at a nice use case: authentication.

#[get(“/whatever/<id>”)]

fn super_secure_function(id: i32, user: AuthUser) -> ... {

user.do_something();

...

}

That user and id parameters of our function are so-called request guards, a parameter that is injected by Rocket and “guards” the function. If either one of them fails to be created successfully, the function is not called and an ErrorCatcher is invoked instead. This is an easy way to validate basic input data and it can be also used to enable authentication. Let’s do this for that AuthUser struct: (Ignore the lifetimes ( 'a, 'r ) for now)

This is the (shortened) implementation of FromRequest for the AuthUser . It reads the username from a private cookie (encrypted and MAC-ed with a secret key) called user_id (L4-6) and either maps it to an AuthUser via its constructor (L8) or returns an error message (L7) .

Thanks to the type safety of rust, we do not need to care about malformed input in the parameters. Either they are correctly formed (but not necessarily valid) or the code is not executed. If a parameter is optional, we need to use a Option and handle it accordingly. This means, we will never encounter a null String in the function itself.

But request guards can do even more, let’s add a database connection for saving our Stay structs somewhere:

#[post("/stay", data = "<new_stay>")]

fn stay_new(new_stay: Json<NewStay>, con: DbCon) -> ... {

So if we post something to /stay , the request body is interpreted as a NewStay struct and we can save it with our DbCon .