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
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.
And we apply the migration:
diesel migration run
We’ll repeat the process for the posts table, with the following definition files:
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.
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:
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:
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:
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:
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:
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:
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.