Build Your First Rust API

Take a hands on approach to learning Rust by building a real world API using Rocket.rs and Diesel.rs

Uxío García Andrade
Better Programming

--

Photo by Paweł Czerwiński on Unsplash

In this tutorial I’ll describe how to build a simple API using the popular Rust web framework Rocket and Diesel as our ORM for Postgres. I initially built this little project to learn both Rust and the aforementioned technologies, so I thought it would be a nice idea to try to build something that I could use in one way or another. That’s why I decided to build this little API to help me manage the links to my Medium posts in my portfolio.

Structure of Our API

First of all, let’s take a look at how our API will be structured. It will have three main route handlers:

  • /user: creates users and then validates user’s access tokens
  • /auth: group used to get user’s access tokens
  • /posts: creates and retrieves posts

At the end of this tutorial, we’ll have the following endpoints in our API (this is the output that we get when we run our server):

Mounting /user:=> POST /user application/json (create)=> GET /user/info (info)=> GET /user/info [2] (info_error)Mounting /auth:=> POST /auth/login (login)Mounting /posts:=> POST /posts (create)=> GET /posts (read)=> POST /posts [2] (create_error)Rocket has launched from http://0.0.0.0:8000

Setting Everything Up

In this tutorial it’s assumed that you have downloaded Rust and have an instance of Postgres running.

First, we’ll create a new Rust project with the following command (from now on we’ll take blog-backend as the name of our project):

cargo new blog-backend

As Rocket makes use of Rust’s advanced features, we’ll need to use the nightly version of Rust. In order to only use the nightly version for our project, we navigate to the directory of our project (blog-backend) and run the following command:

rustup override set nightly

Note: Rocket requires the latest version of Rust nightly, so if any problem comes up while building, just update both the toolchain and dependencies:

rustup update && cargo update

Now we will install the diesel_cli package, which will help us manage our database schema. Again, as we are using Postgres, we can avoid downloading all the other default features:

cargo install diesel_cli --no-default-features --features "postgres"

Then we indicate to Diesel the URL for connecting to our database by setting the following environment variable. In this case, blog is the name of the schema we’re using:

export DATABASE_URL=postgres//user:pass@localhost/blog

Now the Diesel CLI will handle both the database and the migrations directory creation with this command:

diesel setup

Now, if we recall our API definition, we’re clearly working with two different data types: users and posts, so we will have to create tables for both entities.

First, we create a table to store our users. We do this by defining a migration strategy:

diesel migration generate users

This command generates two files, up.sql and down.sql, placed in the directory migrations/date_of_creation_users/. The first file defines how the migration will be applied, the second one defines how it will be reverted.

up.sql for the users migration
down.sql for the users migration

And we apply the migration:

diesel migration run

We’ll repeat the process for the posts table, with the following definition files:

up.sql for the posts migration
down.sql for the posts migration

Finally, we add all the dependencies that we need in our Cargo.toml file:

Getting into the Code

You might have noticed that a file called schema.rs has been generated at some point during the set up. This file is autogenerated by Diesel and is updated when we run our migrations. It contains table! macros which represent all of the tables and columns from the database schema.

schema.rs

In order to establish a database connection, we’ll use the following file to create a database connection pool so that we can reuse the database connections in future requests:

Database Connection Pool

Now we add a main function, where we start the rocket server, initialize the database connection pool, and mount both the users and posts module:

main.rs

Users

Now we get into the users module. In this module we not only include everything related to users, but also to authentication.

First, we take a look at our model.rs file, where we define the User structure and implement the methods that interact with the database:

Model for the Users module

Note that we have two different user structures. The naming of both structures explains the reasoning for this: as the user ID is created by the database, the user struct that we use for inserting will be the same as the regular user struct, but dropping the ID field.

We’re using the bcrypt crate to encrypt user’s passwords. When inserting a new user, we update the password field of the user struct by hashing the previous value. On the other hand, when trying to retrieve a user by their username and password, we have to verify that both passwords match by using the verify function from the aforementioned crate.

Authentication

We’re using a JWT based authentication. I discovered a Rocket implementation in the following repository, so we can see that’s what we are using:

Authentication

In this file, we are essentially checking every request that requires a user to be authenticated. If the requests include a header with a valid token, it’s accepted, but if the token is missing or invalid, the request is forwarded.

Finally, we define the handler functions for the user and authentication routes. The only handler that’s not straightforward is the login one, where we check whether the credentials are right and then generate a valid token for the user.

Posts

The posts model will be similar to the users one. As posts also have a database generated ID, we’re also taking the two structs approach:

Posts model

Finally, we will also define two routes for the POST method — one to handle the requests which include a valid token, and the other to send an unauthorized error:

Posts mod

Now that we have everything we need, we can just run the following command and we’ll have everything up and running:

cargo run

You may find all the files described in this tutorial in the repository.

--

--