How to Build a REST API in Rust A step-by-step guide

Hello, I’m gonna show you how to create a simple REST API in Rust. For that, we gonna use the Rocket framework for the API and Diesel ORM framework for persisting features.

This framework will cover all the things mentioned below. So it will be much easier than implementing from the stretch.

  • Start a web server and open a PORT
  • Listen to requests on this PORT
  • If a request comes in, look at the Path in the HTTP header
  • Route the request to the handler according to the Path
  • Help you extract the information out of the request
  • Pack the generated data (created from you) and form a response
  • Send the response back to the sender

Installing Rust nightly

Cause Rocket makes abundant use of Rust’s syntax extensions and other advanced, unstable features, we have to install nightly.

rustup default nightly

If you prefer to install nightly only in your project directory. You can use the following.

rustup override set nightly

These are the dependencies are gonna used.

[dependencies]
rocket = "0.4.4"
rocket_codegen = "0.4.4"
diesel = { version = "1.4.0", features = ["postgres"] }
dotenv = "0.9.0"
r2d2-diesel = "1.0"
r2d2 = "0.8"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
custom_derive ="0.1.7"

[dependencies.rocket_contrib]
version = "*"
default-features = false
features = ["json"]

Setting up diesel.

So the next thing um gonna do is setting up diesel. Diesel provides its own CLI.So we have to install it first. (assuming you are using PostgreSQL)

cargo install diesel_cli — no-default-features — features postgre

Then you need to tell what are your DB credentials to diesel. This command will generate a .env file.

echo DATABASE_URL=postgres://username:password@localhost:port/diesel_demo > .env

After that run this command.

diesel setup

This will create our database (if it didn’t already exist), and create an empty migrations directory that we can use to manage our schema (more on that later).

There might be several errors you may have when running the above code.

= note: LINK : fatal error LNK1181: cannot open input file ‘libpq.lib’

you can fix that easily by adding the PG lib folder path to environment variables.

setx PQ_LIB_DIR “[path to pg lib folder]”

I’m surprised why those errors are not mentioned in diesel docs. I highly recommend running these commands in cmd or Powershell. If you are using IDE’s terminal you won’t get any errors like this and you will end up wasting 2 hours trying to figure out what the heck is going on.

To fix that, you can add PG’s bin folder path to the Path variables. Problem solved? Good!

Let’s create a user table and create a migration for that:

diesel migration generate users

After running that command, you will see that there are two files generated inside the migration folder.

Next, we’ll write the SQL for migrations:

up.sql

CREATE TABLE users
(
    id SERIAL PRIMARY KEY,
    username VARCHAR NOT NULL,
    password VARCHAR NOT NULL,
    first_name VARCHAR NOT NULL
)

down.sql

DROP TABLE users

To apply our migration you can use:

diesel migration run

It’s good to make sure that down.sql is correct. You can quickly confirm that your down.sql rolls back your migration correctly by redoing the migration:

diesel migration redo

You can see that there’s a user table in your DB. Right!

I forgot to mention, if you’ve noticed, there’s a file that is generated after you run the Diesel setup named schema.rs. It should look like this.

table! {
users (id) {
id -> Int4,
username -> Varchar,
password -> Varchar,
first_name -> Varchar,
}
}

Cool, Here Comes the Rust Part

Since we are going to use the ORM, obviously we have to map the user table to something in Rust. In Java, we use Class to map tables. In Javawe usually call them Beans. In Rust, we use structs. Let’s create a struct.

use diesel;
use diesel::pg::PgConnection;
use diesel::prelude::*;
use super::schema::users;
use super::schema::users::dsl::users as all_users;// this is to get users from the database
#[derive(Serialize, Queryable)]


pub struct User {
    pub id: i32,
    pub username: String,
    pub password: String,
    pub first_name: String,
}

Now, you may wonder what these annotations are, like this above struct definition.

They are called derives. So, that line will derive serialize and queryable traits. #[derive(Serialize)] and #[derive(Deserialize)] are used to map data to response and request.

Now I’m going to create two more structs. You will get them later.

// decode request data
#[derive(Deserialize)]
pub struct UserData {
    pub username: String,
}

// this is to insert users to database
#[derive(Serialize, Deserialize, Insertable)]
#[table_name = "users"]
pub struct NewUser {
    pub username: String,
    pub password: String,
    pub first_name: String,
}

The next thing we are going to do is implement User. So it will have some methods to do database operations.

In here, as you can see, we have passed the connection to the method and returned a Vector of Users. We are getting all the rows in the user table and map them to the User struct.

Of course, we are expecting errors. The message “error” will be printed out if panicked.

impl User {

  pub fn get_all_users(conn: &PgConnection) -> Vec<User> {
    all_users
      .order(users::id.desc())
      .load::<User>(conn)
      .expect("error!")
   }

   pub fn insert_user(user: NewUser, conn: &PgConnection) -> bool {
     diesel::insert_into(users::table)
       .values(&user)
       .execute(conn)
       .is_ok()
     }

  pub fn get_user_by_username(user: UserData, conn: &PgConnection) -> Vec<User> {
     all_users
       .filter(users::username.eq(user.username))
       .load::<User>(conn)
       .expect("error!")
    }
}

Now we have created a table and structs to map that table. The next thing we are going to do is create methods to use it. So, we are going to create a routes file. We usually call it a handler.

use super::db::Conn as DbConn;
use rocket_contrib::json::Json;
use super::models::{User, NewUser};
use serde_json::Value;
use crate::models::UserData;

#[post("/users", format = "application/json")]
pub fn get_all(conn: DbConn) -> Json<Value> {
    let users = User::get_all_users(&conn);
    Json(json!({
      "status": 200,
      "result": users,
   }))
}

#[post("/newUser", format = "application/json", data = "<new_user>")]
pub fn new_user(conn: DbConn, new_user: Json<NewUser>) -> Json<Value> {
     Json(json!({
       "status": User::insert_user(new_user.into_inner(), &conn),
       "result": User::get_all_users(&conn).first(),
     }))
}

#[post("/getUser", format = "application/json", data = "<user_data>")]
pub fn find_user(conn: DbConn, user_data: Json<UserData>) -> Json<Value> {
      Json(json!({
        "status": 200,
        "result": User::get_user_by_username(user_data.into_inner(), &conn),
     }))
}

Now, all we have to do is set up the connection pool. Here’s a brief explanation about the connection pool from the Rocket documentation.

“Rocket includes built-in, ORM-agnostic support for databases. In particular, Rocket provides a procedural macro that allows you to easily connect your Rocket application to databases through connection pools.

A database connection pool is a data structure that maintains active database connections for later use in the application.”

use diesel::pg::PgConnection;
use r2d2;
use r2d2_diesel::ConnectionManager;
use rocket::http::Status;
use rocket::request::{self, FromRequest};
use rocket::{Outcome, Request, State};
use std::ops::Deref;

pub type Pool = r2d2::Pool<ConnectionManager<PgConnection>>;

pub fn init_pool(db_url: String) -> Pool {
    let manager = ConnectionManager::<PgConnection>::new(db_url);
    r2d2::Pool::new(manager).expect("db pool failure")
}

pub struct Conn(pub r2d2::PooledConnection<ConnectionManager<PgConnection>>);

impl<'a, 'r> FromRequest<'a, 'r> for Conn {
    type Error = ();

    fn from_request(request: &'a Request<'r>) -> request::Outcome<Conn, ()> {
        let pool = request.guard::<State<Pool>>()?;
        match pool.get() {
          Ok(conn) => Outcome::Success(Conn(conn)),
          Err(_) => Outcome::Failure((Status::ServiceUnavailable, ())),
        }
    }
}

impl Deref for Conn {
     type Target = PgConnection;

     #[inline(always)]
     fn deref(&self) -> &Self::Target {
     &self.0
     }
}

Finally, we need to start our server in the main file.

#![feature(plugin, const_fn, decl_macro, proc_macro_hygiene)]
#![allow(proc_macro_derive_resolution_fallback, unused_attributes)]

#[macro_use]
extern crate diesel;
extern crate dotenv;
extern crate r2d2;
extern crate r2d2_diesel;
#[macro_use]
extern crate rocket;
extern crate rocket_contrib;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate serde_json;

use dotenv::dotenv;
use std::env;
use routes::*;
use std::process::Command;

mod db;
mod models;
mod routes;
mod schema;

fn rocket() -> rocket::Rocket {
     dotenv().ok();

     let database_url = env::var("DATABASE_URL").expect("set DATABASE_URL");

     let pool = db::init_pool(database_url);
     rocket::ignite()
       .manage(pool)
       .mount(
          "/api/v1/",
          routes![get_all, new_user, find_user],
        )
     }

fn main() {
     let _output = if cfg!(target_os = "windows") {
        Command::new("cmd")
            .args(&["/C", "cd ui && npm start"])
            .spawn()
            .expect("Failed to start UI Application")
     } else {
       Command::new("sh")
            .arg("-c")
            .arg("cd ui && npm start")
            .spawn()
            .expect("Failed to start UI Application")
    };
    rocket().launch();
}

Inside my project, I have added the Angular front end too. I’ll be using our Rust back end to serve it too.

To run the application → cargo run.

16 comments on “How to Build a REST API in Rust A step-by-step guide”

Post a comment

  1. 7 kogus wrote on

    Really appreciate you sharing this article. Awesome. Maureene Giselbert Gearhart

    Reply

  2. yabanci wrote on

    Fastidious response in return of this issue with firm arguments and explaining the whole thing on the topic of that. Cathleen Antonius Brad

    Reply

  3. 720p wrote on

    I love reading through a post that can make people think. Also, many thanks for allowing for me to comment! Sallee Thayne Clarke

    Reply

  4. access wrote on

    Good article! We are linking to this particularly great content on our website. Keep up the good writing.| Dorise Ber Bride

    Reply

  5. movies wrote on

    If you are looking for the ultimate pest control company, look no more. These guys dominate! Nessie Rad Lachish

    Reply

  6. access wrote on

    Integer commodo turpis eget velit pretium ullamcorper. Maitilde Taite Showker

    Reply

  7. access wrote on

    I am regular visitor, how are you everybody? This article posted at this web site is in fact pleasant. Bidget Augustine Carboni

    Reply

  8. yabanci wrote on

    whoah this weblog is excellent i love studying your posts. Keep up the good work! You know, many individuals are looking around for this information, you could aid them greatly. | Brittni Gaspar Deb

    Reply

  9. altyazili wrote on

    Hey there. I found your web site by means of Google even as looking for a similar subject, your site came up. It appears good. I have bookmarked it in my google bookmarks to come back then. Jocelyn Dudley Goldshlag

    Reply

  10. movie online wrote on

    It is truly a nice and useful piece of info. I am happy that you just shared this helpful info with us. Please stay us informed like this. Thank you for sharing. Morena Frederich Aurelius

    Reply

  11. movies wrote on

    Appreciation to my father who told me about this blog, this website is in fact remarkable. Matty Randi Germaun Piper Zeke Hentrich

    Reply

  12. online wrote on

    being in the right place on the addition squashs. la credence demise Allison Guy Nicki

    Reply

  13. movies wrote on

    Fine way of telling, and nice paragraph to get information concerning my presentation topic, which i am going to deliver in college. Fleur Isacco Melonie

    Reply

  14. canadian paramacy viagra wrote on

    It is a very good useful article I like to read such articles Alisha Franny Hassett

    Reply

  15. Private Proxy List Download wrote on

    I like the helpful information you provide in your articles. I’ll bookmark your blog and check again here frequently. I’m quite sure I’ll learn plenty of new stuff right here! Best of luck for the next!

    Reply

  16. Socks5 Proxies wrote on

    Appreciate it for this post, I am a big big fan of this internet site would like to continue updated.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *