Roberto Huertas
Roberto Huertas Just a humble software developer wandering through cyberspace. Organizer of bcn_rust. Currently working at Datadog.

Build and deploy a Rust backend with Shuttle

Build and deploy a Rust backend with Shuttle

Learn how to build and deploy a Rust backend with Shuttle.

Value proposition

What if I told you that you could write a Rust backend and deploy it for free in the cloud?

What if I told you that you could get a Postgres database for free, too?

What if I told you that you wouldn’t have to worry about setting the whole infrastructure up? Nor even have to deal with connection strings?

Finally, what if I told you that you could use some of the most popular web frameworks available in the Rust ecosystem?

To be honest, I couldn’t believe it myself when I first learnt about Shuttle, but I can say that they live up to their promise!

And on top of all that, their code is totally open-source!

How to use it

Check out this Axum’s Hello World example:

use axum::{routing::get, Router};
use sync_wrapper::SyncWrapper;

#[shuttle_service::main]
async fn axum() -> shuttle_service::ShuttleAxum {
    let router = build_router();
    let sync_wrapper = SyncWrapper::new(router);
    Ok(sync_wrapper)
}

fn build_router() -> Router {
    Router::new().route("/", get(hello_world))
}

async fn hello_world() -> &'static str {
    "Hello, world!"
}

And now, let’s compare it to the code that you would write by not using Shuttle at all:

use axum::{routing::get, Router};

#[tokio::main]
async fn main() {
    let router = build_router();
    axum::Server::bind(&"0.0.0.0:8000".parse().unwrap())
        .serve(router.into_make_service())
        .await
        .unwrap();
}

fn build_router() -> Router {
    Router::new().route("/", get(hello_world))
}

async fn hello_world() -> &'static str {
    "Hello, world!"
}

As you can see, both examples are pretty similar.

The only part of code that is different is the entry point of your app. The rest is the same. So, you could potentially reuse the same code to deploy it in your own infrastructure if you ever want to get away of Shuttle, thus avoiding the vendor lock-in.

Adding a Postgres database

Adding a database is a piece of cake!

Forget about having to set up a user, dealing with connection strings and all that jazz. Shuttle will inject a sqlx pool that you can use right away from your main function to work with your database.

But in order for this to happen, you will have to add a couple of dependencies to your Cargo.toml first:

shuttle-shared-db = { version = "0.8.0", features = ["postgres"] }
sqlx = { version = "0.6.2", features = ["runtime-tokio-native-tls","postgres"] }

Finally, go to your previous code and add a parameter to your entry point function (note that it is annotated with a macro):

#[shuttle_service::main]
async fn axum(#[shuttle_shared_db::Postgres] pool: PgPool) -> shuttle_service::ShuttleAxum {
  // ...
}

That’s it! You can now start working with sqlx to deal with your new Postgres database.

For the moment, PostgreSQL and MongoDB are the only supported databases.

Initializing the database

Let’s say we want to create a simple table.

Create a file in the root of your project called db/schema.sql and add these lines to it:

CREATE TABLE IF NOT EXISTS test (
  id serial PRIMARY KEY,
  txt varchar(255) NOT NULL
);

Now, let’s add a few lines to the axum function:

#[shuttle_service::main]
async fn axum(#[shuttle_shared_db::Postgres] pool: PgPool) -> shuttle_service::ShuttleAxum {
    pool.execute(include_str!("../db/schema.sql"))
        .await
        .map_err(CustomError::new)?;
}

With that, every time our service starts, it will try to create the test table if it doesn’t already exist.

The Shuttle CLI

But, wait a moment!

You may be wondering: “What if I want to connect to my database with another client (i.e. DBeaver) to inspect its contents or make a custom query?”.

Well, you can. I didn’t mention so far one of the most important Shuttle parts: its CLI.

Basically, in order to run your code locally and deploy it to the cloud, you’ll need to use a CLI tool called cargo-shuttle.

In order to install it, just run:

cargo install cargo-shuttle

It has lots of subcommands to help you set up a project from scratch and manage it.

Just by running cargo shuttle init, the CLI will guide you through a set of options that will let you choose, amongst other things, the name and location of your project, and the framework that you want to use.

cli

Just to name a few of the available frameworks:

And no, I didn’t forget about the connection string :wink:. Whenever you run or deploy your project, if cargo-shuttle detects you are using a database, it will let you know the connection string, so you can use it on your own.

shuttle axum db

Infrastructure from Code

This is the way they have chosen to describe how they provision all the resources for you (databases, caches, subdomains, static folder…).

As you already saw, they’re using annotations in your code to understand if you are going to need a database or not, and just depending on that, they will provision that for you.

The idea is that you don’t need to define infrastructure as code but that your code itself implicitly defines your infrastructure. Pretty cool and impressive, IMHO!

Many more things

There are many more things to discover.

Check out their amazing set of examples, which will get you up to speed in a blink of an eye.

On the other hand, I have prepared a small GitHub repository showcasing a simple Axum Rest API that uses a Postgres database.

This example is live, here. Feel free to use Postman to play with it. You have the request file available here.

Disclaimer

Note that I’m not in any way related to Shuttle and this is not a “marketing” post of something like that.

I was so amazed when I discovered their service that I immediately felt the need to share my excitement with the rest of the world :wink:

In any case, bear in mind that they’re in public alpha so the service is not really that stable, and they may change things or even disrupt the service at any time.

Concluding thoughts

I think Shuttle is a great project and it’s definitely good news for the Rust community.

It not only allows us to deploy Rust backends easily for free, but also enables lots of web developers coming from other languages to approach the Rust web development ecosystem and realize that it’s not as intimidating as one could think in the first place.

Let’s leverage this great opportunity to get involved and be part of this interesting project!

Cheers!