- Examples
- Thruster
Examples
Thruster
A fast and intuitive Rust web framework.
Hello world!
Simple ‘Hello world’ app using Thruster.
Create a new directory (mkdir
) and move into it (cd
) — afterwards, execute
the following command to initialize shuttle inside with the Thruster boilerplate.
cargo shuttle init --thruster
Make sure that your cargo.toml
file looks as the one below — having the right
dependencies is key!
[package]
name = "hello-world"
version = "0.1.0"
edition = "2021"
[lib]
[dependencies]
shuttle-service = { version = "0.9.0", features = ["web-thruster"] }
thruster = { version = "1.3.0", features = ["hyper_server"] }
Your lib.rs
should look like this:
use thruster::{
context::basic_hyper_context::{generate_context, BasicHyperContext as Ctx, HyperRequest},
m, middleware_fn, App, HyperServer, MiddlewareNext, MiddlewareResult, ThrusterServer,
};
#[middleware_fn]
async fn hello(mut context: Ctx, _next: MiddlewareNext<Ctx>) -> MiddlewareResult<Ctx> {
context.body("Hello, World!");
Ok(context)
}
#[shuttle_service::main]
async fn thruster() -> shuttle_service::ShuttleThruster<HyperServer<Ctx, ()>> {
Ok(HyperServer::new(
App::<HyperRequest, Ctx, ()>::create(generate_context, ()).get("/hello", m![hello]),
))
}
Finally, to deploy your app, all you need to do is:
cargo shuttle deploy
And your app is live! 🎉🎉🎉
AWS RDS Postgres
Create a new directory (mkdir
) and move into it (cd
) — afterwards, execute
the following command to initialize shuttle inside with the Tide boilerplate.
cargo shuttle init --thruster
Make sure that your cargo.toml
file looks as the one below — having the right
dependencies is key!
[package]
name = "postgres"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
hyper = "0.14.20"
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0" }
shuttle-aws-rds = { version = "0.9.0", features = ["postgres"] }
shuttle-service = { version = "0.9.0", features = ["web-thruster"] }
sqlx = { version = "0.6", features = ["runtime-tokio-native-tls", "postgres"] }
thruster = { version = "1.3.0", features = ["hyper_server"] }
Your lib.rs
should look like this:
use serde::{Deserialize, Serialize};
use shuttle_service::error::CustomError;
use sqlx::{Executor, FromRow, PgPool};
use thruster::{
context::{hyper_request::HyperRequest, typed_hyper_context::TypedHyperContext},
errors::{ErrorSet, ThrusterError},
m, middleware_fn, App, Context, HyperServer, MiddlewareNext, MiddlewareResult, ThrusterServer,
};
type Ctx = TypedHyperContext<RequestConfig>;
#[derive(Deserialize)]
struct TodoNew {
pub note: String,
}
#[derive(Serialize, FromRow)]
struct Todo {
pub id: i32,
pub note: String,
}
struct ServerConfig {
pool: PgPool,
}
#[derive(Clone)]
struct RequestConfig {
pool: PgPool,
}
fn generate_context(request: HyperRequest, state: &ServerConfig, _path: &str) -> Ctx {
Ctx::new(
request,
RequestConfig {
pool: state.pool.clone(),
},
)
}
#[middleware_fn]
async fn retrieve(mut context: Ctx, _next: MiddlewareNext<Ctx>) -> MiddlewareResult<Ctx> {
let id: i32 = context
.query_params
.get("id")
.ok_or_else(|| {
ThrusterError::parsing_error(
Ctx::new_without_request(context.extra.clone()),
"id is required",
)
})?
.parse()
.map_err(|_e| {
ThrusterError::parsing_error(
Ctx::new_without_request(context.extra.clone()),
"id must be a number",
)
})?;
let todo: Todo = sqlx::query_as("SELECT * FROM todos WHERE id = $1")
.bind(id)
.fetch_one(&context.extra.pool)
.await
.map_err(|_e| {
ThrusterError::not_found_error(Ctx::new_without_request(context.extra.clone()))
})?;
context.set_body(serde_json::to_vec(&todo).unwrap());
Ok(context)
}
#[middleware_fn]
async fn add(context: Ctx, _next: MiddlewareNext<Ctx>) -> MiddlewareResult<Ctx> {
let extra = context.extra.clone();
let (body, mut context) = context
.get_body()
.await
.map_err(|_e| ThrusterError::generic_error(Ctx::new_without_request(extra)))?;
let data: TodoNew = serde_json::from_str(&body).map_err(|_e| {
ThrusterError::generic_error(Ctx::new_without_request(context.extra.clone()))
})?;
let todo: Todo = sqlx::query_as("INSERT INTO todos(note) VALUES ($1) RETURNING id, note")
.bind(&data.note)
.fetch_one(&context.extra.pool)
.await
.map_err(|_e| {
ThrusterError::generic_error(Ctx::new_without_request(context.extra.clone()))
})?;
context.set_body(serde_json::to_vec(&todo).unwrap());
Ok(context)
}
#[shuttle_service::main]
async fn thruster(
#[shuttle_aws_rds::Postgres] pool: PgPool,
) -> shuttle_service::ShuttleThruster<HyperServer<Ctx, ServerConfig>> {
pool.execute(include_str!("../schema.sql"))
.await
.map_err(CustomError::new)?;
Ok(HyperServer::new(
App::<HyperRequest, Ctx, ServerConfig>::create(generate_context, ServerConfig { pool })
.post("/todos", m![add])
.get("/todos/:id", m![retrieve]),
))
}
And your schema.sql
should look 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! 🎉🎉🎉