51 releases (14 breaking)

0.15.0 Feb 20, 2024
0.13.0 Feb 9, 2024
0.10.0 Oct 28, 2023
0.9.2 Jul 23, 2023
0.6.3 Dec 30, 2022

#56 in HTTP server

Download history 6/week @ 2024-02-01 5/week @ 2024-02-08 759/week @ 2024-02-15 264/week @ 2024-02-22 17/week @ 2024-02-29 8/week @ 2024-03-07 6/week @ 2024-03-14

348 downloads per month

MIT license

340KB
7.5K SLoC

ohkami

ohkami - [狼] wolf in Japanese - is intuitive and declarative web framework.

  • macro-less and type-safe APIs for intuitive and declarative code
  • multi runtime support:tokio, async-std
License build check status of ohkami crates.io

Quick Start

  1. Add to dependencies :
# This sample uses `tokio` runtime.
# `async-std` is available by feature "rt_async-std".

[dependencies]
ohkami = { version = "0.15", features = ["rt_tokio"] }
tokio  = { version = "1",    features = ["full"] }
  1. Write your first code with ohkami : examples/quick_start
use ohkami::prelude::*;
use ohkami::typed::status::NoContent;

async fn health_check() -> NoContent {
    NoContent
}

async fn hello(name: &str) -> String {
    format!("Hello, {name}!")
}

#[tokio::main]
async fn main() {
    Ohkami::new((
        "/healthz"
            .GET(health_check),
        "/hello/:name"
            .GET(hello),
    )).howl("localhost:3000").await
}
  1. Run and check the behavior :
$ cargo run
$ curl http://localhost:3000/healthz
$ curl http://localhost:3000/hello/your_name
Hello, your_name!

Snippets

Handle path params

use ohkami::prelude::*;

#[tokio::main]
async fn main() {
    Ohkami::new((
        "/api/hello/:name"
            .GET(hello),
    )).howl("localhost:5000").await
}

async fn hello(name: &str) -> String {
    format!("Hello, {name}!")
}

Handle request body / query params

use ohkami::prelude::*;
use ohkami::typed::status::Created;
use ohkami::typed::{Query, Payload, ResponseBody};

#[Payload(JSOND)] /* JSON + Deserialize */
struct CreateUserRequest<'req> {
    name:     &'req str,
    password: &'req str,
}

#[ResponseBody(JSONS)] /* JSON + Serialize */
struct User {
    name: String,
}

async fn create_user(body: CreateUserRequest<'_>) -> Created<User> {
    Created(User {
        name: String::from("ohkami")
    })
}

#[Query] /* Params like `?lang=rust&q=framework` */
struct SearchQuery<'q> {
    lang: &'q str,
    q:    &'q str,
}

#[ResponseBody(JSONS)]
struct SearchResult {
    title: String,
}

async fn search(condition: SearchQuery<'_>) -> Vec<SearchResult> {
    vec![
        SearchResult { title: String::from("ohkami") },
    ]
}

#[Query], #[Payload()] derives FromRequest trait impl for the struct.

( with path params : ({path params}, {FromRequest value}s...) )


Use middlewares

ohkami's middlewares are called "fangs".

use ohkami::prelude::*;

struct LogRequest;
impl FrontFang for LogRequest { /* Called before a handler */
    type Error = std::convert::Infallible;

    async fn bite(&self, req: &mut Request) -> Result<(), Self::Error> {
        println!("{req:?}");
        Ok(())
    }
}

struct SetServer;
impl BackFang for SetServer { /* Called after a handler */
    type Error = std::convert::Infallible;

    async fn bite(&self, res: &mut Response, _req: &Request) -> Result<(), Self::Error> {
        res.headers.set()
            .Server("ohkami");
        Ok(())
    }
}

#[tokio::main]
async fn main() {
    Ohkami::with((LogRequest, SetServer), (
        "/hello".GET(|| async {"Hello!"}),
    )).howl("localhost:8080").await

/* Or, you can call them for any incoming requests
   (regardless of request paths) :

    {an Ohkami}
        .howl_with(
            (LogRequest, SetServer),
            "localhost:8080"
        ).await

*/

}

Pack of Ohkamis

use ohkami::prelude::*;
use ohkami::typed::ResponseBody;
use ohkami::typed::status::{Created, NoContent};

#[ResponseBody(JSONS)]
struct User {
    name: String
}

async fn create_user() -> Created<User> {
    Created(User {
        name: "ohkami web framework".to_string()
    })
}

async fn health_check() -> NoContent {
    NoContent
}

#[tokio::main]
async fn main() {
    // ...

    let users_ohkami = Ohkami::new((
        "/".POST(create_user),
    ));

    Ohkami::new((
        "/healthz"  .GET(health_check),
        "/api/users".By(users_ohkami), // <-- nest by `By`
    )).howl("localhost:5000").await
}

Testing

use ohkami::prelude::*;
use ohkami::testing::*; // <--

fn hello_ohkami() -> Ohkami {
    Ohkami::new((
        "/hello".GET(|| async {"Hello, world!"}),
    ))
}

#[cfg(test)]
#[tokio::test]
async fn test_my_ohkami() {
    let ho = hello_ohkami();

    let req = TestRequest::GET("/");
    let res = ho.oneshot(req).await;
    assert_eq!(res.status(), Status::NotFound);

    let req = TestRequest::GET("/hello");
    let res = ho.oneshot(req).await;
    assert_eq!(res.status(), Status::OK);
    assert_eq!(res.text(), Some("Hello, world!"));
}

Supported protocols

  • HTTPS
  • HTTP/1.1
  • HTTP/2
  • HTTP/3
  • WebSocket

MSRV (Minimum Supported Rust Version)

Latest stable at the time of publication.

License

ohkami is licensed under MIT LICENSE (LICENSE or https://opensource.org/licenses/MIT).

Dependencies

~2–15MB
~184K SLoC