Framework
BersisSe/feather avatar
BersisSe/feather

Feather: a Rust web framework built on coroutines instead of async

Feather🪶: A Rust web framework that does not use async

764 stars24 forksRustMIT

At a glance

What is it?
Feather is a Rust web framework that handles each request on a coroutine instead of an async task, so handlers are plain synchronous functions with no.await anywhere. It ships routing, middleware, a Context state API, logging and optional JWT in one package, and and its concurrency depends on a fork of the may coroutine library hosted under the same GitHub account.
Who is it for?
Adopt Feather if you want Express-style handlers in Rust without writing.await, particularly for a service that is thread-bound rather than IO-bound, since every handler is an ordinary synchronous function running on a coroutine. Step back if your stack needs a broad ecosystem of battle-tested middleware, or if you are uneasy about a git dependency, because the workspace pins may to a git fork rather than a published crate.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 85 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Handlers are ordinary functions, and that is the whole idea

Feather is described as a lightweight, DX-first web framework for Rust, inspired by the simplicity of Express.js but designed for Rust performance and safety. The distinguishing claim is on the README's own banner: a Rust web framework that does not use async.

Look at what a handler looks like:

rust
use feather::{App, middleware_fn, next};

#[middleware_fn]
fn hello() -> feather::Outcome {
    res.send_text("Hello, world!");
    next!()
}

fn main() {
    let mut app = App::new();
    app.get("/", hello);
    app.listen("127.0.0.1:5050");
}

There is no async keyword, no .await, and no Future in the return type. The README's summary of it is that is all, no async. A handler takes no arguments, mutates a response through res, and signals that it is done with the next! macro.

The mechanics underneath are in Feather-Runtime, described as a custom HTTP engine built for high concurrency and low latency without using Rust's async and await. Each request is handled by a lightweight coroutine using the may library, which is what allows thousands of concurrent connections with synchronous code.

That coroutine library is the thing to look at closely. In the workspace dependency list, may is pinned to version 0.3.51 from a git repository at github.com/BersisSe/may rather than from crates.io. So the concurrency model of the framework depends on a fork maintained by the same author, which is worth weighing before you adopt the framework for something long lived.

Middleware as a function, a struct, or a chain

The README calls middleware the heart of Feather, and the point is that it can be written in three shapes. A closure, using the middleware! macro, a struct, or a chain of them.

The function form looks like this:

rust
use feather::{App, middleware_fn, next};

#[middleware_fn]
fn log_middleware() -> feather::Outcome {
    println!("Custom global middleware!");
    next!()
}

#[middleware_fn]
fn hello() -> feather::Outcome {
    res.send_text("Hello, world!");
    next!()
}

fn main() {
    let mut app = App::new();
    app.use_middleware(log_middleware);
    app.get("/", hello);
    app.listen("127.0.0.1:5050");
}

The struct form implements a trait, which is what you reach for when the middleware carries data:

rust
use feather::{middleware_fn, Request, Response, AppContext, Middleware, next, info};""

struct CustomMiddleware(String);

impl Middleware for CustomMiddleware {
    fn handle(&self, _request: &mut Request, _response: &mut Response, _ctx: &AppContext) -> feather::Outcome {
        info!("Hii I am a Struct Middleware and this is my data: {}", self.0);
        next!()
    }
}

Two things in the struct version are worth noticing. The handle method receives the request, the response and the context, all as mutable or borrowed references, and the trait is implemented directly rather than derived from a macro. And the import line in the README as printed ends with a stray double quote character, which suggests the code block was not re-checked when the README was written. Small, but it is the kind of detail that tells you the documentation is maintained by hand.

The macro attribute middleware_fn is what turns a plain function into a middleware, and the next! macro is the continuation. This is the shape that makes the framework feel like Express, where a request flows through a chain of functions and each one decides whether to continue.

Context holds application state, without extractors or macros

The Context API is the answer to the state question, and the README is explicit that it manages application-wide state without extractors or macros. The example is a counter held in the app context:

rust
use feather::{App, middleware_fn, next};
#[derive(Debug)]
struct Counter { pub count: i32 }

#[middleware_fn]
fn count() -> feather::Outcome {
    let counter = ctx.get_state::<State<Counter>>().unwrap();
    counter.lock().count += 1;
    res.send_text(format!("Counted! {}", counter.count));
    next!()
}

fn main() {
    let mut app = App::new();
    app.context().set_state(State::new(Counter { count: 0 }));
    app.get("/", count);
    app.listen("127.0.0.1:5050");
}

The state is set once at startup with set_state, then retrieved inside a handler with get_state and a type parameter, and the returned handle is locked before the count is incremented. The README notes that context is especially useful when needing to access databases and files, which is the case that motivates the pattern.

The alternative here is the usual one: a global, a thread-local, or a dependency injection container with extractors. Feather's pitch is that a single typed lookup from the context replaces all three, and that the type parameter means a handler asks for the exact type it needs rather than a string key.

The dependency list gives a hint about what state looks like in practice. r2d2 and r2d2_sqlite are both there, which is a connection pool for SQLite, so a database handle in the context is a natural fit, and parking_lot provides the locking primitives that the counter example relies on.

JWT authentication is a feature flag away

Feather has a native JWT module that is switched on with a cargo feature, so the dependency stays small when you do not want it:

toml
[dependencies]
feather = { version = "*", features = ["jwt"] }

The module is built around a manager you install in the context and a claims type you read in a protected handler:

rust
use feather::{App, jwt_required, middleware_fn, next};
use feather::jwt::{JwtManager, SimpleClaims};

#[middleware_fn]
fn token_route() -> feather::Outcome {
    let token = ctx.jwt().generate_simple("user", 1)?;
    res.send_text(format!("Token: {}", token));
    next!()
}

#[jwt_required]
#[middleware_fn]
fn protected(claims: SimpleClaims) -> feather::Outcome {
    res.send_text(format!("Hello, {}!", claims.sub));
    next!()
}

fn main() {
    let mut app = App::new();
    let manager = JwtManager::new("secretcode");
    app.context().set_jwt(manager);
    app.get("/token", token_route);
    app.get("/auth", protected);
    app.listen("127.0.0.1:8080");
}

The mechanism is straightforward. A JwtManager is created with a secret and stored in the context, a route generates a token with a subject and an identifier, and a second route is guarded by the jwt_required attribute, which injects SimpleClaims as an argument. The claims struct exposes the subject as claims.sub.

The signing is handled by the jsonwebtoken crate at version 9.3.1, which is a widely used implementation, so the cryptography is not the homegrown part. What is homegrown is the plumbing: the manager lives in the context, the attribute guard injects the claims, and the tokens are read straight off the request.

One thing to notice in that example is the hardcoded secret, secretcode, which is fine as documentation and obviously wrong in production, and the version field in the dependency is a wildcard. Pinning that to a real version is the first thing to change when you copy it.

Logging on by default, and the repository layout

Creating a new Feather application initializes the logger by default, and the README shows it in the entry point:

rust
fn main() {
    let mut app = App::new();
    info!("Feather app ready to serve requests!");
    // Your app setup here
    app.listen("127.0.1:5050");
    
}

If you do not want that, you can create the App with the without_logger method instead. The logging is the log crate at version 0.4.33, and the structured logging macro the JWT and middleware examples use, info!, is that crate's macro rather than one Feather defines, which means it composes with whatever logger you already configure.

The repository is a Cargo workspace with resolver 3, its members being the crates directory and the examples directory. The workspace dependencies give the shape of the project: feather at about 0.8, feather-runtime at about 0.6, feather-macros at about 0.2, and then the usual stack of http, httparse, serde, serde_json, bytes, log, serde_urlencoded, thiserror, jsonwebtoken, r2d2, r2d2_sqlite, urlencoding, parking_lot, may, itoa and time.

The examples directory is the best map of the feature surface, with directories named api, async, basic-app, context, counter, dynamic_route, error-pipeline, jwt, logging, middleware, router and serve. The presence of an async example is a little ironic given the framework's premise, and it is worth opening to see what it covers.

The workspace also excludes benches/axum and benches/feather, which tells you the project has benchmark harnesses for itself and for axum in the tree, deliberately kept out of the default workspace build so they do not affect ordinary development.

Where Feather is the wrong choice

The first wrong case is a project that needs the wider Rust web ecosystem. Feather is a single package with routing, middleware, logging, JWT and a context API, and the README describes it as all in one, which is exactly the point and also exactly the limitation. There is no catalogue of third-party middleware to draw on, and no sign in the repository of a plugin ecosystem. A team that expects to compose a mature set of community crates will find that surface small.

The second wrong case is a project that needs the stable Rust toolchain today. The workspace pins may to a git fork hosted by the framework's own author, which means a git dependency in your build, and the may crate is what the entire concurrency model rests on. The repository also has no GitHub releases, so the version you get is whatever crates.io currently holds, and the README's dependency snippet uses a wildcard version.

The third is a workload that is genuinely IO-bound and long-lived. Coroutines with synchronous handlers work well when the handler blocks briefly, and the design trades the ability to await for a simpler mental model. A handler that waits on a slow network call holds its coroutine rather than yielding an async task, so the throughput profile of a request that blocks for a second is different from an async framework where the same wait would be free. The README does not publish any benchmark numbers, and the excluded bench harnesses are not in the workspace, so there is no figure to check either way.

The fourth is maintenance risk. The dependencies are pinned to a fork, the last push was on 2026-07-30, and the repository carries a CHANGELOG.md and a ROADMAP.md but publishes no tagged releases. That combination is fine for a weekend project and uncomfortable for a service with an on-call rotation.

Editorial conclusion

Adopt Feather if you want Express-style handlers in Rust without writing.await, particularly for a service that is thread-bound rather than IO-bound, since every handler is an ordinary synchronous function running on a coroutine. Step back if your stack needs a broad ecosystem of battle-tested middleware, or if you are uneasy about a git dependency, because the workspace pins may to a git fork rather than a published crate. Before you start, read the ROADMAP.md and CHANGELOG.md in the repository, and check the version you depend on, since the manifest points at feather ~0.8 while the last push was on 2026-07-30 and the project publishes no GitHub releases.

Frequently asked questions

What is the Feather Rust web framework and how is it different from an async framework?

Feather is a lightweight web framework for Rust, MIT licensed and inspired by Express.js. It runs each request on a lightweight coroutine using the may library through its own Feather-Runtime engine, so handlers are plain synchronous functions with no .await anywhere in the signature.

How do I add Feather to a Rust project and write a first handler?

Add feather = "~0.8" to your Cargo.toml dependencies, create an App, register a route with app.get and call app.listen with an address. A handler is a function marked with the middleware_fn attribute that sends a response and then calls the next! macro to continue the chain.

How do I share state between handlers in Feather?

Use the Context API. You set state on the app context at startup with app.context().set_state, and read it inside a handler with ctx.get_state::<State<T>>(), which returns a handle you lock before mutating. The README says this avoids extractors and macros and is especially useful for database and file access.

Does Feather support JWT authentication?

Yes, through a native module enabled by the jwt cargo feature, so the dependency stays optional. You install a JwtManager in the context, generate tokens with ctx.jwt().generate_simple, and protect a route with the jwt_required attribute, which injects SimpleClaims as a handler argument.

Official sources

  1. BersisSe/feather on GitHub
  2. Issues
  3. License: MIT
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/bersisse-feather.svg)](https://hysenlabs.com/projects/bersisse-feather)

Was this useful?