Deploying an Axum URL Shortener on Shuttle |Rust.
Published: 2022-12-16 14:47:59

---
title: Deploying an Axum URL Shortener on Shuttle |Rust.
published: true
date: 2022-08-22 22:12:08 UTC
tags: 
canonical_url: https://carlosmv.hashnode.dev/deploying-an-axum-url-shortener-on-shuttle-rust
---

I found Shuttle weeks ago while I was scrolling through Reddit, I found it really cool, and I want to try it immediately, and I thought it would be a great opportunity to use Axum, that which is a web framework I started to use recently. I don't have much experience with Rust, and it is my first time using Shuttle. Be patient with me, please.

If you don't know what Shuttle is, this is what its Github page says:

> Shuttle is a serverless platform for Rust which makes it really easy to deploy your web-apps.
> 
> Shuttle is built for productivity, reliability, and performance:
> 
> - Zero-Configuration support for Rust using annotations
> - Automatic resource provisioning (databases, caches, subdomains, etc.) via Infrastructure-From-Code
> - First-class support for popular Rust frameworks (Rocket, Axum, Tide, and Tower)
> - Scalable hosting (with optional self-hosting)

First, we need to install the `cargo-shuttle` subcommand:

```
cargo install cargo-shuttle

```

After the subcommand is installed, we are ready to create our project. We will build a URL Shortener, like the example on Shuttle's web page, but their example is built with Rocket, you can see them [here](https://www.shuttle.rs/#examples). Our URL Shortener will be built with Axum.

```
cargo shuttle init url-shrtnr

```

This is our project's directory structure:

```
url-shortener/
    --src/  
        -- lib.rs
    -- Cargo.toml
    -- .gitignore

```

Let's add the dependencies to our project:

Cargo.toml

```
[package]
name = "url-shrtnr"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
shuttle-service = { version = "0.4.2", features = ["web-axum", "sqlx-postgres"] }
axum = "0.5.15"
serde = "1.0.137"
sqlx = { version = "0.5.13", features = ["runtime-tokio-native-tls", "postgres"] }
sync_wrapper = "0.1.1"

```

Now, let's write a Hello World example.

### Hello World example

lib.rs

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

#[shuttle_service::main]
async fn main() -> ShuttleAxum {

    let router = Router::new()
        .route("/", get(root));

    let sync_wrapper = SyncWrapper::new(router);

    Ok(sync_wrapper)

}

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

```

Now we run this command in our terminal:

```
cargo shuttle run

```

We should see this message in our terminal:

```
Starting url-shrtnr on http://127.0.0.1:8000

```

If we use our browser or curl and paste the URL, we will see the message "Hello, World!".

Now, to deploy the app we need to log in first, let's go to this [page](https://www.shuttle.rs/) and get our API key.

Then in our terminal, we write this command:

```
cargo shuttle login --api-key <your api-key>


cargo shuttle deploy

```

If everything is ok, we will see this message:

```
 Project: url-shrtnr
        Deployment Id: 9ebd7ed5-0fa2-4d43-a1ef-3168c1ad22e6
        Deployment Status: DEPLOYED
        Host: https://url-shrtnr.shuttleapp.rs
        Created At: 2022-06-29 15:01:37.557012238 UTC

```

We will see the message "Hello, World!" in our browser if we go to the URL "https://url-shrtnr.shuttleapp.rs".

If we have an error message saying that there's another app with the same name, we have to create Shuttle.toml in the same location Cargo.toml is. Inside Shuttle.toml we write the name we want for our app.

Shuttle.toml

```
name = "<your app's name>"

```

According to the documentation:

> If the name key is not specified, the services name will be the same as the crates name.
> 
> Alternatively, you can override the project name on the command-line, by passing the name argument:
> 
> ```
> cargo shuttle deploy --name=$PROJECT_NAME
> 
> ```

Now, let's write our URL shortener. We will use Sqlx for our database and sqlx-cli to generate our migrations folder. If you haven't installed sqlx-cli, you can install it with the next command:

```
cargo install sqlx-cli


sqlx database create --database-url postgres://<your user>:<your password>@localhost:<port>/<your database name>


sqlx migrate add <database>

```

We should see this message in our terminal:

```
Creating migrations\20220629154910_url.sql

Congratulations on creating your first migration!

Did you know you can embed your migrations in your application binary?
On startup, after creating your database connection or pool, add:

sqlx::migrate!().run(<&your_pool OR &mut your_connection>).await?;

Note that the compiler won't pick up new migrations if no Rust source files have changed.
You can create a Cargo build script to work around this with `sqlx migrate build-script`.

See: https://docs.rs/sqlx/0.5/sqlx/macro.migrate.html

```

After that we go to `migrations/<timestap>-url.sql` and there we add our table.

```
CREATE TABLE url (
  id VARCHAR(6) PRIMARY KEY,
  url VARCHAR NOT NULL
);

```

Then we need to run migrations.

```
sqlx migrate run --database-url postgres://<your user>:<your password>@localhost:<port>/<database>

```

Let's add other dependencies to our Cargo.toml:

Cargo.toml

```
[dependencies]
...

nanoid = "0.4"
url ="2.2"

```

lib.rs

```
use axum::{
    routing::{get, post},
    Router,
    response::Redirect,
    http::StatusCode,
    extract::Extension,
};
use shuttle_service::{error::CustomError, ShuttleAxum};
use sync_wrapper:: SyncWrapper;
use serde::Serialize;
use sqlx::migrate::Migrator;
use sqlx::{FromRow, PgPool};
use url::Url;

#[derive(Serialize, FromRow)]
struct StoredURL {
    pub id: String,
    pub url: String,
}


async fn shorten(url:String, Extension(pool): Extension<PgPool>) -> Result<String, StatusCode> {
    let id = &nanoid::nanoid!(6);

    let parserd_url = Url::parse(&url).map_err(|_err| {
        StatusCode::UNPROCESSABLE_ENTITY
    })?;

    sqlx::query("INSERT INTO url(id, url) VALUES ($1, $2)")
        .bind(id)
        .bind(parserd_url.as_str())
        .execute(&pool)
        .await
        .map_err(|_| {
            StatusCode::INTERNAL_SERVER_ERROR
        })?;

        Ok(format!("https://url-shrtnr.shuttleapp.rs/{id}"))
}

```

We pass a URL to the `shorten` function, then, we define a `parsed_url` to check if the string passed is a URL and return an error message if is not.

Then, we pass the SQL query to the `query` function and bind the values `id` generated by `nanoid` and the `url`. If an error is not raised, it will return and url as a string and the `id` as the shortened url.

```
async fn redirect(id: String, Extension(pool): Extension<PgPool>) -> Result<Redirect, StatusCode> {
    let stored_url: StoredURL = sqlx::query_as("SELECT * FROM url WHERE id = $1")
        .bind(id)
        .fetch_one(&pool)
        .await
        .map_err(|err| match err {
            sqlx::Error::RowNotFound => StatusCode::NOT_FOUND,
            _=> StatusCode::INTERNAL_SERVER_ERROR

        })?;

        Ok(Redirect::to(&stored_url.url))
}

```

The redirect function retrieves a URL from the database when its `id` is passed. If the `id` doesn't exist in the database, it will return a `Not Found` error.

```
static MIGRATOR: Migrator = sqlx::migrate!();

#[shuttle_service::main]
async fn axum(pgpool: PgPool) -> ShuttleAxum {

    MIGRATOR.run(&pgpool).await.map_err(CustomError::new)?;

    let router = Router::new()
        .route("/:id", get(redirect))
        .route("/:url", post(shorten))
        .layer(Extension(pgpool));

    let sync_wrapper = SyncWrapper::new(router);

    Ok(sync_wrapper)

}

```

If everything is ok, our URL-Shortener will be deployed and we will see the next message:

```
Compiling url-shrtnr v0.1.0 (/opt/shuttle/crates/url-shrtnr)
    Finished dev [unoptimized + debuginfo] target(s) in 49.18s

        Project: url-shrtnr
        Deployment Id: 771d387b-fb1c-48ce-97a5-ff3452036265
        Deployment Status: DEPLOYED
        Host: https://url-shrtnr.shuttleapp.rs
        Created At: 2022-06-29 16:37:02.890391021 UTC
        Database URI: postgres:// ***:*** @pg.shuttle.rs:5432/db-url-shrtnr

```

### Conclusion

It was really fun for me to build and deploy this app, it is the first time I deployed anything, and it was easy with Shuttle, they wrote very good documentation with examples for Rocket, Axum, Tide, and Postgres, and the examples were very helpful.

If there is anything I didn't explain or I should improve or know about Shuttle, Axum, Sqlx, or Rust, please leave a comment.

Here is the complete source [code](https://github.com/carlosm27/blog/tree/main/url-shortnr).

Thank you for taking the time to read this article.

If you have any recommendations about other packages, architectures, how to improve my code, my English, or anything; please leave a comment or contact me through [Twitter](https://twitter.com/Carlos_marcv), [LinkedIn](https://www.linkedin.com/in/carlos-marcano-a2135a134/).

### References:

- [URL-Shortener example with Rocket](https://github.com/shuttle-hq/shuttle/tree/main/examples)
- [Shuttle documentation](https://docs.rs/shuttle-service/latest/shuttle_service/)
- [Building and Deploying a URL shortener with Rust in 10 minutes or less](https://www.shuttle.rs/blog/2022/03/13/url-shortener)
- [Axum documentation](https://github.com/tokio-rs/axum)
- [Axum's examples](https://github.com/shuttle-hq/shuttle/tree/main/examples/axum)
- [Postgres' example](https://github.com/shuttle-hq/shuttle/tree/main/examples/rocket/postgres)
- [sqlx-cli](https://github.com/launchbadge/sqlx/tree/main/sqlx-cli)
- [Building an authentication system in Rust using session tokens](https://www.shuttle.rs/blog/2022/08/11/authentication-tutorial)