An opinionated batteries-included approach to a rust web framework.
The idea is to take the best parts of the rust ecosystem and combine them into a framework that is easy to use and provides a good developer experience.
Add the following to your Cargo.toml
file.
[dependencies]
swim = "0.2"
- Go blazingly fast with hyper and tokio
- Powerful routing with routerify
- CLI tooling with cargo-swim (coming soon)
- Database support with SeaORM (planned)
- Templating with Tera (planned)
- Dependency injection (planned)
You define a project by defining a struct that implements the Project
trait. It is the highest-level abstraction in the framework. It is responsible for defining the settings, apps, and middleware for your project.
use swim::prelude::*;
struct MyProject;
impl Project for MyProject {
fn settings(&self) -> Settings {
Settings::builder()
.extend_ron(relative!("settings.ron"))
.build()
.unwrap()
}
fn apps(&self) -> Vec<Box<dyn App>> {
vec! [
MyApp.into()
]
}
fn middleware(&self) -> Vec<Box<dyn Middleware>> {
vec! [
MyMiddleware.into()
]
}
}
You define an app by defining a struct that implements the App
trait. It is responsible for defining the routes and views for your app.
use swim::prelude::*;
struct MyApp;
impl App for MyApp {
fn mount(&self) -> &'static str {
"/"
}
fn config(&self) -> AppConfig {
AppConfig::with_name("MyApp")
}
fn models(&self) -> Vec<Box<dyn Model>> {
vec! []
}
fn routes(&self) -> Vec<Route> {
vec! [
Route::new("/", IndexView),
Route::new("/hello", HelloView),
Route::new("/greeting/:name", GreetingView),
]
}
}
You define a view by defining a struct that implements the View
trait. It is responsible for handling the request and returning a response. You can implement the specific HTTP methods you want to handle.
#[derive(Debug)]
pub struct HelloView;
#[async_trait::async_trait]
impl View for HelloView {
async fn get(&self, request: Request<Body>) -> Result<Response<Body>> {
Ok(Response::builder()
.status(StatusCode::OK)
.body(Body::from("Say hello to Swim! "))
.unwrap())
}
async fn post(&self, request: Request<Body>) -> Result<Response<Body>> {
Ok(Response::builder()
.status(StatusCode::OK)
.body(Body::from("It's a post request! "))
.unwrap())
}
}
You define a middleware by defining a struct that implements the Middleware
trait. You may hook into the pre
and post
methods which are capable of modifying the upcoming request and leaving response respectively (or you could simply use these for monitoring traffic).
#[derive(Debug)]
pub struct Logger;
#[async_trait::async_trait]
impl Middleware for Logger {
async fn pre(&self, request: Request<Body>) -> Result<Request<Body>> {
println! ("New request: {:?}", request.uri());
Ok(request)
}
async fn post(&self, response: Response<Body>) -> Result<Response<Body>> {
println! ("Response: {:?}", response.status());
Ok(response)
}
}
You may use the elegant swim macro to run your project.
#[tokio::main(flavor = "multi_thread")]
async fn main() {
swim! (MyProject, host = "localhost", port = 8000);
}
The device has been built, but the batteries are not yet included.
Feel free to open an issue or a PR if you have any ideas or suggestions. This project is all about new ideas and making the developer experience better.