Building a RESTful API with Rust and Crates.run

Are you looking to build a high-performance, low-latency RESTful API? Do you want a stack that’s both efficient and modern? Look no further than Rust and Crates.run – the perfect combination for building world-class software.

Why Rust?

Rust is a programming language that’s been gaining in popularity in recent years. It was designed by Mozilla as an alternative to C++, but it’s more modern and easier to work with. Rust combines the low-level control of C/C++ with the memory safety of managed languages like Java or Python.

One of Rust’s main strengths is its performance. Rust is fast – really fast. It compiles natively to machine code, giving it an edge over interpreted languages like Python or JavaScript. Rust is also thread-safe by default, meaning you don’t have to worry about race conditions or deadlocks.

Another advantage of Rust is its syntax. Rust has a concise, yet expressive syntax that’s easy to read and write. It also has a powerful macro system that allows you to write your own syntax extensions.

But perhaps the most compelling reason to use Rust is its safety. Rust has a unique ownership model that ensures memory safety at compile time. Simply put, Rust will catch any memory errors before your program even runs. This makes it nearly impossible to have errors like null pointers, dangling pointers, buffer overflows, or use-after-frees.

Why Crates.run?

Crates.run is a platform that lets you run Rust applications and servers without worrying about infrastructure. It provides a seamless experience for deploying and scaling Rust applications. If you’re looking to build a RESTful API with Rust, Crates.run is the perfect platform to host and manage it.

With Crates.run, you don’t need to set up any infrastructure yourself. You don’t need to worry about provisioning servers, installing operating systems, or configuring firewalls. Crates.run takes care of all of that for you. All you need to do is provide your Rust code and let Crates.run handle the rest.

Crates.run also makes scaling your application simple. You can scale your application up or down depending on your traffic patterns. Simply adjust the number of replicas you want to run, and Crates.run will take care of the rest. Crates.run also provides automatic load balancing and failover, so you don’t have to worry about downtime or service disruptions.

Building a RESTful API with Rust and Crates.run

To build a RESTful API with Rust and Crates.run, we’ll be using the following tools:

We’ll be building a simple RESTful API that allows users to create and retrieve blog posts. Let’s get started.

Setting Up the Project

The first thing we need to do is set up the project. We’ll be using Cargo, Rust’s package manager, to manage our dependencies and build our application.

First, let’s create a new Rust project using Cargo:

$ cargo new rust-api

This will create a new Rust project called rust-api. Now we need to add our dependencies to the Cargo.toml file:

[dependencies]
rocket = "0.4.10"
rocket_codegen = "0.4.10"
diesel = { version = "1.4.5", features = ["postgres", "r2d2"] }

We’re using rocket and rocket_codegen for our web framework, and diesel for our database ORM. We’re also using postgresql as our database.

Let’s install our dependencies using cargo:

$ cargo update

Defining the Database Schema

Before we can start building our API, we need to define our database schema. We’ll be using PostgreSQL as our database, so we’ll need to define our tables in SQL. We’ll be using diesel to interact with our database, so we’ll be defining our tables using its schema DSL.

First, let’s create a new file called schema.rs in our project root directory. This file will contain our database schema definitions.

table! {
    posts (id) {
        id -> Integer,
        title -> Varchar,
        body -> Text,
        published -> Bool,
    }
}

This defines our posts table with the following columns:

Setting up the Database Connection

Now that we have our database schema defined, we need to set up our database connection. We’ll be using diesel to connect to our database, so we’ll need to define a connection URL in our .env file.

Create a new file called .env in your project root directory with the following contents:

DATABASE_URL=postgres://localhost/rust_api

This sets our database URL to postgres://localhost/rust_api. We need to create this database before we can run our application. To create the database, run the following command in your terminal:

$ createdb rust_api

This will create a new database called rust_api. Now we can run our migrations to create our posts table in the database. Run the following command in your terminal:

$ diesel migration run

This will create the posts table in the rust_api database.

Defining Models

Now that we have our database set up, we can define our models. We’ll be using diesel to define our models, which will map to our database schema.

First, let’s create a new file called models.rs in our project root directory. This file will contain the definition of our Post model.

use diesel::prelude::*;

use crate::schema::posts;

#[derive(Queryable, Serialize)]
pub struct Post {
    pub id: i32,
    pub title: String,
    pub body: String,
    pub published: bool,
}

#[derive(Insertable)]
#[table_name="posts"]
pub struct NewPost<'a> {
    pub title: &'a str,
    pub body: &'a str,
}

This defines our Post model with the following fields:

We’re also defining a NewPost struct that can be used to create new posts. This struct has the following fields:

Defining the Routes

Now that we have our models defined, we can define our routes. We’ll be using rocket to define our routes, which allows us to define routes using simple functions.

First, let’s create a new file called routes.rs in our project root directory. This file will define our routes.

use diesel::prelude::*;
use rocket_contrib::json::{Json, JsonValue};
use serde::{Deserialize, Serialize};

use crate::models::{NewPost, Post};
use crate::schema::posts::dsl::*;

#[get("/posts")]
fn all(conn: DbConn) -> Json<Vec<Post>> {
    let results = posts
        .filter(published.eq(true))
        .load::<Post>(&*conn)
        .expect("Error loading posts");

    Json(results)
}

#[get("/posts/<id>")]
fn get(id: i32, conn: DbConn) -> Option<Json<Post>> {
    posts
        .find(id)
        .filter(published.eq(true))
        .first::<Post>(&*conn)
        .map(Json)
        .ok()
}

#[post("/posts", format = "application/json", data = "<new_post>")]
fn create(new_post: Json<NewPost>, conn: DbConn) -> JsonValue {
    let insert = diesel::insert_into(posts)
        .values(&new_post.0)
        .execute(&*conn);

    match insert {
        Ok(_) => json!({"status": "success"}),
        Err(_) => json!({"status": "error", "message": "Failed to create post"}),
    }
}

This defines the following routes:

We’re using diesel to interact with our database, and rocket_contrib to serialize and deserialize JSON responses.

Setting up the Rocket Server

Now that we have our routes defined, we need to set up the Rocket server. We’ll be using the standard rocket setup, with some extra middleware to handle database connections and JSON responses.

First, let’s create a new file called main.rs, which will be our entry point for the application.

#![feature(plugin)]
#![plugin(rocket_codegen)]

#[macro_use] extern crate diesel;
#[macro_use] extern crate rocket;
extern crate rocket_contrib;
#[macro_use] extern crate serde_derive;

mod routes;
mod schema;
mod models;

use dotenv::dotenv;
use std::env;

use rocket::{Rocket, Request, Outcome};
use rocket::http::Status;
use rocket::request::{self, FromRequest};
use rocket::Outcome::Failure;
use rocket_contrib::json::JsonValue;
use diesel::pg::PgConnection;
use diesel::r2d2::{ConnectionManager, Pool, PooledConnection};
use std::ops::Deref;

type PgPool = Pool<ConnectionManager<PgConnection>>;

pub struct DbConn(pub PooledConnection<ConnectionManager<PgConnection>>);

impl Deref for DbConn {
    type Target = PgConnection;

    fn deref(&self) -> &Self::Target {
        &*self.0
    }
}

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

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

fn prepare_pool(database_url: &str) -> PgPool {
    let manager = ConnectionManager::<PgConnection>::new(database_url);
    Pool::new(manager).expect("Failed to create pool")
}

fn rocket() -> Rocket {
    dotenv().ok();
    let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
    let pool = prepare_pool(&database_url);
    rocket::ignite()
        .manage(pool)
        .mount("/", routes![routes::all, routes::get, routes::create])
}

fn main() {
    rocket().launch();
}

This sets up the rocket server, with middleware to handle database connections and JSON responses. We’re using diesel to handle database connections, and PgConnection for our database.

Running the Application

Now that we have our application set up, we can run it using cargo:

$ cargo run

This will start the rocket server, and you should be able to access the RESTful API in your browser by visiting http://localhost:8000/posts.

Wrapping Up

In this article, we’ve explored the benefits of using Rust and Crates.run to build a high-performance, low-latency RESTful API. Rust’s safety, performance, and syntax make it the perfect choice for building modern APIs. Crates.run’s seamless deployment and scaling make it easy to manage Rust applications.

We’ve also walked through building a simple RESTful API using Rust and Crates.run, using rocket for our web framework, diesel for our ORM, and PostgreSQL for our database.

With Rust and Crates.run, the possibilities are endless. Start building your next API today!

Additional Resources

learnredshift.com - learning aws redshift, database best practice
runmulti.cloud - running applications multi cloud
multicloudops.app - multi cloud cloud operations ops and management
cryptomerchant.services - crypto merchants, with reviews and guides about integrating to their apis
knowledgegraphops.dev - knowledge graph operations and deployment
valuation.dev - valuing a startup or business
lessonslearned.solutions - lessons learned in software engineering and cloud
datalog.dev - the datalog programming language and its modern applications
assetcatalog.dev - software to manage unstructured data like images, pdfs, documents, resources
dataquality.dev - analyzing, measuring, understanding and evaluating data quality
dsls.dev - domain specific languages, dsl, showcasting different dsls, and offering tutorials
container.watch - software containers, kubernetes and monitoring containers
crates.dev - curating, reviewing and improving rust crates
timeseriesdata.dev - time series data and databases like timescaledb
databaseops.dev - managing databases in CI/CD environment cloud deployments, liquibase, flyway
jimmyruska.com - Jimmy Ruska
cryptoinsights.dev - A site and app about technical analysis, alerts, charts of crypto with forecasting
newfriends.app - making new friends online
remotejobs.engineer - A job board about remote engineering jobs where people can post jobs or find jobs
zerotrustsecurity.cloud - zero trust security in the cloud


Written by AI researcher, Haskell Ruska, PhD (haskellr@mit.edu). Scientific Journal of AI 2023, Peer Reviewed