1. Examples
  2. Poem

Hello world!

Simple ‘Hello world’ app using Poem.

Create a new directory (mkdir) and move into it (cd) — afterwards, execute the following command to initialize shuttle inside with the Poem boilerplate.

cargo shuttle init --poem

Make sure that your Cargo.toml file looks like the one below — having the right dependencies is key!

Cargo.toml
[package]
name = "hello-world"
version = "0.1.0"
edition = "2021"

[lib]

[dependencies]
poem = "1.3.35"
shuttle-service = { version = "0.9.0", features = ["web-poem"] }

Your lib.rs should look like this:

lib.rs
use poem::{get, handler, Route};

#[handler]
fn hello_world() -> &'static str {
    "Hello, world!"
}

#[shuttle_service::main]
async fn main() -> shuttle_service::ShuttlePoem<impl poem::Endpoint> {
    let app = Route::new().at("/hello", get(hello_world));

    Ok(app)
}

Create a project, this will start an isolated deployer container for you under the hood:

cargo shuttle project new

Finally, to deploy your app, all you need to do is:

cargo shuttle deploy

And your app is live! 🎉🎉🎉

Todo App (MongoDB)

Simple ‘todo app’ example using MongoDB.

Create a new directory (mkdir) and move into it (cd) — afterwards, execute the following command to initialize shuttle inside with the Poem boilerplate.

cargo shuttle init --poem

Make sure that your Cargo.toml file looks like the one below — having the right dependencies is key!

Cargo.toml
[package]
name = "mongodb-poem-app"
version = "0.1.0"
edition = "2021"

[dependencies]
mongodb = "2.3.0"
poem = "1.3.35"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
shuttle-service = { version = "0.9.0", features = ["web-poem"] }
shuttle-shared-db = { version = "0.9.0", features = ["mongodb"] }

Your lib.rs should look like this:

lib.rs
use mongodb::bson::doc;
use mongodb::bson::oid::ObjectId;
use mongodb::{Collection, Database};
use poem::{
    error::{BadRequest, NotFoundError},
    get, handler,
    middleware::AddData,
    post,
    web::{Data, Json},
    EndpointExt, FromRequest, Request, RequestBody, Result, Route,
};
use serde::{Deserialize, Serialize};

struct ObjectIdGuard(ObjectId);

#[poem::async_trait]
impl<'a> FromRequest<'a> for ObjectIdGuard {
    async fn from_request(req: &'a Request, _body: &mut RequestBody) -> Result<Self> {
        let id = req.path_params::<String>()?;
        let obj_id = ObjectId::parse_str(id).map_err(BadRequest)?;
        Ok(ObjectIdGuard(obj_id))
    }
}

#[handler]
async fn retrieve(
    ObjectIdGuard(id): ObjectIdGuard,
    collection: Data<&Collection<Todo>>,
) -> Result<Json<serde_json::Value>> {
    let filter = doc! {"_id": id};
    let todo = collection
        .find_one(filter, None)
        .await
        .map_err(BadRequest)?;

    match todo {
        Some(todo) => Ok(Json(serde_json::json!(todo))),
        None => Err(NotFoundError.into()),
    }
}

#[handler]
async fn add(Json(todo): Json<Todo>, collection: Data<&Collection<Todo>>) -> Result<String> {
    let todo_id = collection
        .insert_one(todo, None)
        .await
        .map_err(BadRequest)?;

    Ok(todo_id
        .inserted_id
        .as_object_id()
        .expect("id is objectId")
        .to_string())
}

#[shuttle_service::main]
async fn main(
    #[shuttle_shared_db::MongoDb] db: Database,
) -> shuttle_service::ShuttlePoem<impl poem::Endpoint> {
    let collection = db.collection::<Todo>("todos");

    let app = Route::new()
        .at("/todo", post(add))
        .at("/todo/:id", get(retrieve))
        .with(AddData::new(collection));

    Ok(app)
}

#[derive(Debug, Serialize, Deserialize)]
struct Todo {
    pub note: String,
}

Create a project, this will start an isolated deployer container for you under the hood:

cargo shuttle project new

Finally, to deploy your app, all you need to do is:

cargo shuttle deploy

And your app is live! 🎉🎉🎉

Todo App (Postgres)

Simple ‘todo app’ example using Postgres.

Create a new directory (mkdir) and move into it (cd) — afterwards, execute the following command to initialize shuttle inside with the Poem boilerplate.

cargo shuttle init --poem

Make sure that your Cargo.toml file looks like the one below — having the right dependencies is key!

Cargo.toml
[package]
name = "postgres"
version = "0.1.0"
edition = "2021"

[lib]

[dependencies]
poem = "1.3.35"
serde = "1.0"
shuttle-service = { version = "0.9.0", features = ["web-poem"] }
shuttle-shared-db = { version = "0.9.0", features = ["postgres"] }
sqlx = { version = "0.6", features = ["runtime-tokio-native-tls", "postgres"] }

Your lib.rs should look like this:

lib.rs
use poem::{
    error::BadRequest,
    get, handler,
    middleware::AddData,
    post,
    web::{Data, Json, Path},
    EndpointExt, Result, Route,
};
use serde::{Deserialize, Serialize};
use shuttle_service::error::CustomError;
use sqlx::{Executor, FromRow, PgPool};

#[handler]
async fn retrieve(Path(id): Path<i32>, state: Data<&PgPool>) -> Result<Json<Todo>> {
    let todo = sqlx::query_as("SELECT * FROM todos WHERE id = $1")
        .bind(id)
        .fetch_one(state.0)
        .await
        .map_err(BadRequest)?;

    Ok(Json(todo))
}

#[handler]
async fn add(Json(data): Json<TodoNew>, state: Data<&PgPool>) -> Result<Json<Todo>> {
    let todo = sqlx::query_as("INSERT INTO todos(note) VALUES ($1) RETURNING id, note")
        .bind(&data.note)
        .fetch_one(state.0)
        .await
        .map_err(BadRequest)?;

    Ok(Json(todo))
}

#[shuttle_service::main]
async fn main(
    #[shuttle_shared_db::Postgres] pool: PgPool,
) -> shuttle_service::ShuttlePoem<impl poem::Endpoint> {
    pool.execute(include_str!("../schema.sql"))
        .await
        .map_err(CustomError::new)?;

    let app = Route::new()
        .at("/todo", post(add))
        .at("/todo/:id", get(retrieve))
        .with(AddData::new(pool));

    Ok(app)
}

#[derive(Deserialize)]
struct TodoNew {
    pub note: String,
}

#[derive(Serialize, FromRow)]
struct Todo {
    pub id: i32,
    pub note: String,
}

And your schema.sql like this:

DROP TABLE IF EXISTS todos;

CREATE TABLE todos (
  id serial PRIMARY KEY,
  note TEXT NOT NULL
);

Finally, to deploy your app, all you need to do is:

cargo shuttle deploy

And your app is live! 🎉🎉🎉