Learning Rust By Porting an Express.js Server to Rocket.rs — Part 1

Daniel Voigt
The Startup
Published in
5 min readJan 24, 2021

It’s important to stay up to date with new developments in the world of thousands of programming languages. Especially as Full Stack developers, we can lose touch to the underlying processes and internal workings when we continue to only develop in very high-level languages such as Typescript.

I kept an eye on rust for a bit over two years now and while rust is still very niche, the community is very passionate about it.

For five years running, Rust has taken the top spot as the most loved programming language. TypeScript is second surpassing Python compared to last year. We also see big gains in Go, moving up to 5th from 10th last year.

https://insights.stackoverflow.com/survey/2020#technology-programming-scripting-and-markup-languages-all-respondents

Coming from Full Stack development let’s port a few basic express.js routes to rust with the help of rocket.rs. I chose rocket.rs because I think it’s a lot more straight forward than for example actix.

Keep in mind that I won’t be covering everything you need to know in order to use rust and rocket. This is more like a guided tour with links to different resources and guides. Today we prepare our environment and get a peek on how rust looks in the context of a web service.

Preparation

To install rust let’s use rustup. Rustup is a helper to manage your rust installations and versions. We need to install the rust compiler and the package manager which is called Cargo. You can think of Cargo as your NPM or Yarn.

Note: If you are on Windows and you would like to use MSVC instead of MinGW don’t forget to install the Visual Studio Build Tools!

Next let’s create a new project, which is pretty straight forward. We tell cargo to create a new project with the name rocket_port and specify that we would like to create a binary --bin instead of a library --lib .

cargo new cargo new rocket_port --bin

Looking at the newly created directory we can see a Cargo.lock and a Cargo.toml file. You can think of them like counterparts to package.json and package-lock.json. Since we are building a server based on the rocket library let’s add this dependency to our Cargo.toml

[dependencies]
rocket = "0.5.0-dev"

Note: For this project we are using the 0.5 version which is not yet final and not added to the crates.io directory. But no worries, adding the dependency ourselves is very straightforward. We just need to clone the rocket repository into our project folder and tell cargo where to look for this dependency:

[dependencies]
rocket = { path= "Rocket/core/lib" }

Hello Rocket, Hello Express

To start, let’s grab a slightly modified version of the Hello World example from the Rocket docs, paste it into our main.rs and compare it to the hello world example of express.js. I changed the express version to reflect the rocket example a bit more.

Our Hello Rocket example
Our Hello Express Example

You can test your installation by compiling and starting your project with cargo run. If everything was installed correctly your rocket server should start and by navigating to http://127.0.0.1:8000 you should be greeted by “Hello, World!”. Let’s deconstruct this hello world example bit by bit.

To start with we need to import our dependencies. The import statement is not much different in both versions. Packages in the rust ecosystem are called crates but that’s pretty much it. What’s different is the extra bit above the import statement in our rust example.

Rust has a very powerful macro system. While, for example the C preprocessor operates almost only on text, rust macros can be used for meta programming. Generating rust code by using rust code. #[macro_use] tells rust that we would like to import all macros declared in the crate “rocket”.

Second step: defining a route. In our express example we later use the app instance to attach a route to a specific HTTP method and path. Our rust example makes use of the imported macros to define both HTTP method and path. If you ever used Java before this looks pretty similar to annotations.

The way to send responses on this route is the first bigger difference.

First let’s check out the Express version where we call the function send on the response object. If we look up the source of this function we see express is checking the type of the parameter and constructs an HTTP content type and size according to the passed type.

https://github.com/expressjs/express/blob/master/lib/response.js#L141

The rust version makes use of its Traits system, which defines shared behavior between types. String for example has an implementation of the Trait “Responder”, shipped ready to use with the rocket crate, which tells rocket how to build a response based on its type. Looking at the implementation we can see that we also set the content type and size.

https://github.com/SergioBenitez/Rocket/blob/master/core/lib/src/response/responder.rs#L209

Every type can implement the Responder trait and then be used as a rocket response.

In rust we define our behavior around our type while in JavaScript we define one behavior for all types. Which makes sense in this scenario. JavaScript itself is a loosely typed language with a limited number of types. We pretty much just use objects. Typescript is a tool we use to be more specific about our objects. Rust on the other hand is a strongly typed language, so it makes a lot more sense to add behavior to types.

Another thing to note is the absence of a return statement in the rust example even though we clearly declare a return type -> String. Rust will use the final expression in the function as the return value if it is not closed with a semicolon.

#[launch] is another macro imported from rocket and is used to mark our entry point. This time our return type is rocket::Rocket I.e. the type Rocket defined in the namespace rocket. Our function is doing nothing more than calling the ignite function of rocket and mounting our route to the
base of /.

To pass the routes we use yet another macro. This time a function macro denoted by routes! which transforms our functions to routes by adding a bit more metadata to it.

Note: This pattern, of calling functions on types which in turn return themselves is called the builder pattern, and you will see this a lot in the rust world.

In the next part we will start to implement a login feature, defining a guard for protected routes and finally manage our session.

--

--

Daniel Voigt
The Startup

Software developer, language enthusiast and Jazz musician.