Hey! Today we're going to have a quick look at migrating a project that uses the Tokio runtime with Axum, to Shuttle.
Setup
Pre-requisites
Before we start, make sure you have the latest version of cargo-shuttle installed.
For this example, we'll assume you are migrating an Axum project that has a database.
Migrating your project
Add your dependencies
The first step is to add shuttle-runtime
and shuttle-axum
to your dependencies to be able to use Shuttle's runtime with the Axum framework.
cargo add shuttle-runtime shuttle-axum
Any secrets you need to use will be kept in a Secrets.toml
file (dev secrets in Secrets.dev.toml
) which will be placed at the Cargo.toml
level.
You can also easily get a provisioned database like so (this example will be for a provisioned PostgreSQL instance specifically):
cargo add shuttle-shared-db --features postgres
If you have any database records you'd like to keep, it would be a good idea to export them so that they can be migrated to the new database. You will not need a secrets file if you only need a provisioned Postgres database - this will be automatically be provisioned and given to you in the form of a connection string or an sqlx pool.
Migrating your code
To be able to run your project on Shuttle, you need to make a few changes to your code.
Instead of the tokio::main
macro, you will use the shuttle_runtime::main
macro and swap out dotenvy
for Shuttle's Postgres annotation:
This is what your main.rs
file might look like before:
#[tokio::main]
async fn main() {
dotenvy::dotenv().ok();
let url = dotenvy::var("DATABASE_URL").expect("No database URL was set!");
let pool = sqlx::Pool::connect(&url).await.unwrap();
sqlx::migrate!()
.run(&pool)
.await
.expect("Migrations failed :(");
let router = create_api_router(pool);
let addr = SocketAddr::from(([0, 0, 0, 0], 8000));
Server::bind(&addr)
.serve(router.into_make_service())
.await
.unwrap()
}
And this is what it looks like after:
#[shuttle_runtime::main]
pub async fn axum (
#[shuttle_shared_db::Postgres] pool: PgPool,
#[shuttle_runtime::Secrets] secrets: shuttle_runtime::SecretStore,
) -> shuttle_axum::ShuttleAxum {
sqlx::migrate!()
.run(&pool)
.await
.expect("Migrations failed :(");
// Use secrets for anything that needs them
let router = create_api_router(pool);
Ok(router.into())
}
If you need more than a simple router, you'll want to create a custom struct that holds all of your required app state information inside and then create an impl
for the struct - you can find more about that here. Anything outside of your entry point function (the function that uses the shuttle_runtime::main
macro) doesn't need to be changed. If you are using secrets as well as a database connection, you may wish to create a struct that holds both of these values and then pass it into the function that generates the router. Interested? We also have some additional docs on how to do this
Deploying
To ensure that you get a unique project name, create a Shuttle.toml
file at the Cargo.toml
level to name your project to whatever you like.
name = "my-unique-app-name-here"
Now all you need to do is to run the following commands:
shuttle project start
shuttle project deploy
Your project should now be deployed!
Finishing up
Thanks for reading! Hopefully this article has helped you learn what it takes to migrate to Shuttle.
Further reading: