ohkami - [狼] means wolf in Japanese - is simple and macro free web framework for Rust.
Features
- simple: Less things to learn / Less code to write / Less time to hesitate.
- macro free: No need for using macros.
- async handlers
- easy error handling
Quick start
- Add dependencies:
[dependencies]
ohkami = "0.4"
- Write your first code with ohkami:
use ohkami::prelude::*;
fn main() -> Result<()> {
Server::setup()
.GET("/", || async {
Response::OK("Hello, world!")
})
.serve_on(":3000")
}
- If you're interested in ohkami, learn more by examples and documentation !
0.3 → 0.4
Added experimental support for middlewares:
fn main() -> Result<()> {
let config = Config {
log_subscribe: Some(
tracing_subscriber::fmt()
.with_max_level(tracing::Level::TRACE)
),
..Default::default()
};
let middleware = Middleware::init()
.ANY("/*", || async {
tracing::info!("Hello, middleware!")
});
let thirdparty_middleware = some_crate::x;
Server::setup_with(config.and(middleware).and(x))
.GET("/", || async {
Response::OK("Hello!")
})
.serve_on("localhost:3000")
}
- "Middleware function" is just a function that takes one of nothing,
&Context or &mut Context as its argument and returns (). You can register such functions with their routes ( where they should work ) in a Middleware.
- Middleware funcs is inserted before handler works, so middleware funcs are executed only when the handler exists ( e.g. In the code above for example,
tracing::info("Hello, middleware")! will NOT executed for a request POST / because no handler for this is found ).
- Current design may be changed in future version.
Snippets
handle query params
let name = ctx.req.query::<&str>("name")?;
let count = ctx.req..query::<usize>("count")?;
handle request body
let body = ctx.req.body::<D>()?;
handle path params
fn main() -> Result<()> {
Server::setup()
.GET("/sleepy/:time/:name", sleepy_hello)
.serve_on(":3000")
}
async fn sleepy_hello(time: u64, name: String) -> Result<Response> {
(time < 30)
._else(|| Response::BadRequest("sleeping time (sec) must be less than 30."))?;
std::thread::sleep(
std::time::Duration::from_secs(time)
);
Response::OK(format!("Hello {name}, I'm extremely sleepy..."))
}
return OK response with text/plain
Response::OK("Hello, world!")
ctx.text("Hello, world!")
return OK response with application/json
Response::OK(JSON("Hello, world!"))
ctx.json(JSON("Hello, world!"))
Response::OK(json!("ok": true))
ctx.json(json!("ok": true))
Response::OK(json(user)?)
ctx.json(json(user)?)
handle errors
let user = ctx.req.body::<User>()?;
let user = ctx.req.body::<User>()
._else(|e| e.error_context("failed to get user data"))?;
let user = ctx.req.body::<User>()
._else(|_| Response::InternalServerError("can't get user"))?;
._else(|_| Response::InternalServerError(None))?;
handle Option values
let handler = self.handler.as_ref()
._else(|| Response::NotFound("handler not found"))?;
._else(|| Response::NotFound(None))?;
assert boolean conditions
(count < 10)
._else(|| Response::BadRequest("`count` must be less than 10"))?;
._else(|| Response::BadRequest(None))?;
log config
fn main() -> Result<()> {
let config = Config {
log_subscribe: Some(
tracing_subscriber::fmt()
.with_max_level(tracing::Level::TRACE)
),
..Default::default()
};
Server::setup_with(config)
.GET("/", || async {Response::OK("Hello!")})
}
DB config
let config = Config {
db_profile: DBprofile {
pool_options: PgPoolOptions::new().max_connections(20),
url: DB_URL.as_str(),
},
..Default::default()
};
use sqlx
let user = sqlx::query_as::<_, User>(
"SELECT id, name FROM users WHERE id = $1"
).bind(1)
.fetch_one(ctx.pool())
.await?;
use middlewares
fn main() -> Result<()> {
let middleware = Middleware::init()
.ANY("/*", || async {
tracing::info!("Hello, middleware!")
});
Server::setup_with(middleware)
.GET("/", || async {
Response::OK("Hello!")
})
.serve_on("localhost:3000")
}
fn main() -> Result<()> {
let config = Config {
log_subscribe: Some(
tracing_subscriber::fmt()
.with_max_level(tracing::Level::TRACE)
),
..Default::default()
};
let middleware = Middleware::init()
.ANY("/*", || async {
tracing::info!("Hello, middleware!")
});
let thirdparty_middleware = some_external_crate::x;
Server::setup_with(config.and(middleware).and(x))
.GET("/", || async {
Response::OK("Hello!")
})
.serve_on("localhost:3000")
}
test
- split setup process from
main function:
fn server() -> Server {
Server::setup()
.GET("/", || async {Response::OK("Hello!")})
}
fn main() -> Result<()> {
server().serve_on(":3000")
}
- import
test::Test and others, and write tests using assert_to_res , assert_not_to_res:
#[cfg(test)]
mod test {
use ohkami::{server::Server, response::Response, test::{Test, Request, Method}};
use once_cell::sync::Lazy;
static SERVER: Lazy<Server> = Lazy::new(|| super::server());
#[test]
fn test_hello() {
let request = Request::new(Method::GET, "/");
(*SERVER).assert_to_res(&request, Response::OK("Hello!"));
(*SERVER).assert_not_to_res(&request, Response::BadRequest(None));
}
}
Development
ohkami is on early stage now and not for producntion use.
Please give me your feedback! → GetHub issue
License
This project is under MIT LICENSE (LICENSE-MIT or https://opensource.org/licenses/MIT).