Build a CRUD API with Rust

Sean Wragg
Mar 19, 2018 · 9 min read

Since my initial Node/Rust REST comparison, I’ve wanted to follow up with a comprehensive guide for getting simple CRUD operations up and running in Rust.

While Rust has its complexities, coming from Node.js, one thing that’s kept me attracted to the language is its simplicity. It’s sleek and strikes a great balance between safety and cognitive intuition.

In any event, hopefully this guide helps those that are new to Rust and encourages those who are on the fence.

Scroll down if you’d like to see how Rust compares against Java and Node.js

Major frameworks used

  • Rocketweb framework for writing fast web applications
  • Serdeframework for serializing and deserializing Rust data structures
  • Dieselsafe, extensible ORM and query builder

Creating our application in Rust

First, we’ll create a new Rust project using the commands below.

Before we move forward though, Rocket requires us to use the nightly Rust build. Thankfully, there’s a quick command we can use to switch channels.

You’ll get some output here regarding the switch but, can confirm it was successful by invoking the flag on and

Adding our first Rust dependency (Rocket)

Alright, within our directory, you’ll find a and folder. will act similar to Node’s which we’ll cover later — and I suspect is descriptive enough :)

We’ll need to add Rocket as a dependency by editing and adding the following two dependencies. There’s certainly more to add later but, this will at least get us started.

Now we can edit — we’ll just borrow the starter code provided to us by Rocket.rs to ensure everything works at this point.

The thing that I find most appealing about Rust (and Rocket) is how self descriptive the majority of the code is.

Here we expose a single endpoint, leveraging Rocket’s annotation, that takes two parameters and . We then map those parameters to our route handler and specify their types — if the types are unmatched, the route handler will not be invoked and a 404 will be returned.

Our route handler returns a formatted, using Rust’s format! macro, with the supplied and . Notice no trailing semicolon for return statements

The main method, similar to other languages, is aptly named . This is the first function that’s invoked when our application runs. Here we tell Rocket to startup and mount our route hander using as the root context. This provides us with a URL like: .

Your two files should look similar to the image below. Let’s test that everything works by starting our server using the command. This should start a server on which can be hit via browser/curl.


Building Restful endpoints using JSON (Serde)

So, one endpoint will only get us so far. Thinking about our API, we want simple CRUD operations to manage Hero data; so right off the top, we’ll want:

  • Create:
  • Read:
  • Update:
  • Delete:

Now that we have that out of the way — we’ll be using JSON as our primary means for exchanging data, so in comes our next dependency: Serde.

This will require the following additions to our Cargo.toml:

We’ll also want think about what properties a given Hero should have. Below we create a simple Hero model within — adding the and annotations from Serde so that our model can be extracted from and converted to JSON.

is Optional since consumers hitting our CREATE endpoint will not yet have an id to send. However, when we retrieve data and supply it as a response, anything from our DB will have an id established.

Let’s go ahead and create our CRUD endpoints now, using dummy data for the time being:

In the above, we include our new dependencies and reference our Hero type, we also pull out and from ; this will make it easier dealing with JSON requests/responses.

We also add our other operations (POST, PUT, DELETE). The attribute within our route annotation simply tells Rocket to expect Body Data — then map the body to a parameter. Here we say, the body expected should be in the form of a Hero but wrapped in JSON.

We should now be able to hit any of our configured endpoints using a standard rest client/curl, etc.


Persisting our data via ORM (Diesel)

I think we can all agree, having a few URLs is great but, not too helpful if the data we’re sending isn’t persisted. For this, we’ll use Diesel as it’s currently one of the most mature Rust ORM frameworks.

Doing this for the first time, I’ll admit, is certainly an involved process but once the initial bootstrap is out of the way, it works quite well. I’ve tried this with the blackbeam raw mysql driver, and while it works, the codebase becomes polluted really quick..

In fact, Sean Griffin (author of Diesel) wrote a great article illustrating this very point.

To get started, we install the Diesel CLI:

Then we tell Diesel where our database should live and run setup:

Next, we’ll generate a migration strategy — this basically allows us to keep revisions as our database evolves over time.

We’ll need to edit these two files to include the SQL for our Heroes schema

Now we can run our migration — which will execute up.sql against our DB

With any luck, that’s the last SQL we should touch for this project. Let’s go ahead and add Diesel to our dependencies in Cargo.toml. We’re also going to add r2d2-diesel which will allow us to manage db connection pooling.

We’ll create to establish our database connection and manage our pool — thankfully, most of this was provided by Rocket Connection Guard starter code

We’ll also need to make the following additions to bind our Hero model with the new table info we’ve created:

This enables us to create an auto-generated schema derived from our struct

However, we’re going to make a small change so that we can leverage the same model for Queryable and Insertable object. The Diesel author expressed fair reasoning for separating our object into two models but, I prefer the convenience and less code pollution. So for that, we edit 😅

Without the change above, we’d require two models: one to insert (without id) and a separate to retrieve (with id).

Now we can import these new dependencies by adding the following into:

Next we’ll tell Rocket to manage our db connection pool by adding the following to our chain:

Believe it not, now we’re ready to get this thing going! Here we’ll expose a few methods by creating an implementation for Hero within — save for more sophisticated error handling.

Now we can leverage the newly created methods within our route handlers

Notice, each method within our route handlers and Hero implementation now essentially accept the same/similar arguments: Some combination of an , object and database .

For more info on how to query data, refer to the Diesel Getting Started guides.

Finally, we can now create a Hero and see real results in our REST Client. Fetching, updating and deleting Heroes also work as expected.


Putting our API to the test (wrk)

Now that we have a somewhat complete API, it’s probably worth benchmarking this thing as one of the most touted aspect of Rust is its performance over other languages.

But before trying it out, I wanted to get an idea of how other popular languages and frameworks stack up.

For this, I simply used locally on my Macbook Pro with no other user applications or services running.

Java 1.8 (Spring Boot, Hibernate)

I threw together a quick example exposing the same GET Heroes method that we created in our Rust API above by following a great tutorial by Rajeev Kumar Singh (found here)

The example code used can be found here:

The results weren’t bad @ 4,584 requests per second

Node 9.8 (Restify, Sequelize)

The Node team has made significant improvements in Node 9. Now leveraging V8’s Ignition and Turbofan — Node’s performance has nearly tripled.

The example code used can be found here:

The results weren’t nearly what I was hoping @ 2,506 requests per second. It’s worth noting that using the raw package nearly doubled the handled requests per second.

Rust 1.26 (Rocket, Diesel)

Finally, to test our Rust server, we’ll want to ensure is set to , otherwise it will run in development mode.

The example code used can be found here:

Rust seriously outperformed both of the other frameworks


Conclusion

Node provided the smallest code footprint but, obviously without type safety and on the lower end of the performance scale — although, 2k req/sec is nothing to scoff at. We can improve Node’s performance at the cost of nastier code maintenance.

Java had by far the largest code footprint. It also takes the longest to start up at an average ~4seconds. Not really much else to report here, it just wasn’t.. fun to develop?

Rust is a tremendously fun language to use and it’s blazingly fast. It goes without saying, speed isn’t the only factor when considering the right tool for the job but, it’s certainly an important one.

To learn more about Rust, check out the “The Rust Programming Language

sean3z

Code, Comics, and Fhqwhgads!

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store