seanmonstar/warp: a Filter-based HTTP framework for Rust
A super-easy, composable, web server framework for warp speeds.
At a glance
- What is it?
- warp composes request handling out of small Filter values, and hyper supplies the HTTP/1 and HTTP/2 plumbing underneath. It suits Rust teams who want typed routing without a router macro, and it is a poor fit for anyone expecting batteries-included middleware.
- Who is it for?
- Adopt warp if you already write async Rust and want routing, extraction and rejection handling expressed as typed values you can combine in ordinary functions. Do not adopt it if you need a large middleware ecosystem, an opinionated project layout, or a framework that hides hyper from you.
- 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 64 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 September 25, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What warp solves, and who ends up using it
Most Rust HTTP frameworks ask you to describe routes in a declarative block and then attach handlers. warp takes the opposite route. The README states that "the fundamental building block of warp is the Filter", and a Filter is a value that either rejects a request or produces some typed output from it. A path segment, a header requirement, a JSON body parse and a query string deserialization are all Filters with different output types. You build an application by combining them with combinators such as and, or and map.
The practical consequence is that routing is ordinary Rust code. If you want a route that only exists when a header is present, you write that as a value and compose it, rather than registering a handler and validating inside it. The output type of the composed Filter is what your handler receives, so the compiler checks that a handler expecting a String is only wired to a Filter that produces a String.
The audience is narrower than a general-purpose framework's. warp assumes you are comfortable with Rust's type system, futures, and the idea that a request pipeline is a value rather than a configuration file. The repository layout reflects this: src/ holds the implementation, tests/ holds integration tests, and examples/ holds twenty-odd standalone programs, from hello.rs to tls.rs and unix_socket.rs. Those examples are the closest thing to documentation of intent.
How a Filter pipeline actually runs
A Filter is generic over its output, and composition produces new Filters rather than mutating a router. warp::path!("hello" / String) matches a two-segment path and extracts the second segment as a String. Adding .and(warp::header::header("x-api-key")) requires that header and changes the output to a tuple. Adding .or(...) introduces a branch, and warp tracks the rejection type of each branch so that a request matching neither produces a structured rejection rather than an opaque 404.
The rejection model is the part that separates warp from frameworks that return Result from a handler. When no Filter matches, warp does not simply return 404. It carries a Rejection value that records why each branch declined, and you decide how to convert that into a response. The README does not document a default policy in detail; examples/rejections.rs exists specifically to show the conversion. This is a real design decision with a cost: correct error responses are something you build, not something you inherit.
Underneath, the Cargo.toml dependencies show hyper 1 and hyper-util 0.1.12 with the server, server-graceful, server-auto, http1 and http2 features. That is where HTTP/1 and HTTP/2 support, asynchrony and the connection handling come from, and the README attributes them to building on hyper rather than to warp itself. warp's own additions are the Filter layer plus optional pieces: multer 3 for multipart, tokio-tungstenite 0.29 for websockets, async-compression 0.4.5 for gzip, deflate and Brotli.
Installing warp and serving a first route
warp is published on crates.io, so installation is a Cargo dependency. The README gives this exact pair of entries, and the server feature is what brings in the hyper-based serving path:
tokio = { version = "1", features = ["full"] }
warp = { version = "0.4", features = ["server"] }With those in place, the README's example is a complete program. It matches GET /hello/<name> and formats a greeting, binding to 127.0.0.1 on port 3030:
use warp::Filter;
#[tokio::main]
async fn main() {
// GET /hello/warp => 200 OK with body "Hello, warp!"
let hello = warp::path!("hello" / String)
.map(|name| format!("Hello, {}!", name));
warp::serve(hello)
.run(([127, 0, 0, 1], 3030))
.await;
}Run it with cargo run and request http://127.0.0.1:3030/hello/warp. The expected response is 200 OK with the body "Hello, warp!". Note what is absent: no attribute macro, no route table, no application struct. The route is a local variable and warp::serve consumes it.
From there the examples directory is the practical next step. examples/routing.rs covers composition and ordering, examples/query_string.rs covers deserialization, examples/body.rs covers JSON and form bodies, and examples/returning.rs covers returning values from handlers. If you need TLS, examples/tls.rs and the examples/tls/ directory are the reference, and Cargo.toml shows tokio-rustls and rustls-pemfile commented out, so TLS is configured through those crates rather than enabled by a warp feature flag.
Where warp gets awkward
The Filter model scales unevenly. Composition reads well for a handful of routes. Once a service has dozens of endpoints, sharing cross-cutting concerns means either wrapping Filters or threading them through every branch, and the type of a deeply composed Filter becomes long enough that compiler errors are hard to read. Frameworks that keep handlers as separate functions with a shared extractor type avoid that, at the cost of the composability warp offers.
The rejection system is the second friction point. Because warp does not impose a response for unmatched requests, a service that never converts rejections will return responses that do not distinguish a missing route from a malformed body. The README states that access logging is available out of the box, but it does not describe a default error format, and examples/rejections.rs is where that gap is addressed in practice.
warp is also the wrong tool if you want the framework to own your application structure. There is no CLI, no project generator and no convention for where handlers live. The repository contains src/ and tests/ and examples/, and nothing that suggests a scaffolding story. Teams that want a framework to make those decisions for them will spend their first week making them by hand instead.
warp against Axum, and why the split exists
The comparison people reach for is Axum, and the difference is structural rather than cosmetic. Both sit on hyper, and both are maintained in the same corner of the Rust ecosystem, but Axum handlers are async functions whose arguments implement extractor traits, registered against a Router. warp has no Router type in the README's example; the composed Filter is the router.
That changes how you reason about a request. In Axum, an extractor that fails produces a rejection that the framework can turn into a response, and the handler signature declares what it needs. In warp, the Filter declares what it needs and what it produces, and the handler is a closure applied with map or and_then. The practical difference shows up in two places: sharing state across many routes, and producing consistent error responses. Axum's model makes both more uniform. warp's model makes conditional routing, where a route exists only under certain request conditions, more direct, because the condition is part of the Filter rather than a check inside a handler.
Neither is a strict improvement. If your service is a straightforward CRUD API with a large route table, Axum's shape will require less code. If your service has unusual routing conditions or you want routing expressed as values you can pass around, test and recombine, warp's Filter is the more natural fit.
Maintenance, features and licence cost
The repository is not archived, and the last push was on 2026-07-28. Recent releases are v0.4.1 on 2025-08-06, v0.4.0 on 2025-08-05 and v0.3.7 on 2025-08-01. Cargo.toml on the default branch lists version 0.4.3, so the crate has moved past the most recent tagged release listed here. The CHANGELOG.md at the repository root is the file to read before upgrading, since the 0.4 line followed a 0.3 line and the README's dependency example pins 0.4.
The upgrade cost is dominated by optional features, not by the core API. Cargo.toml marks async-compression, futures-channel, hyper, hyper-util, multer and tokio-tungstenite as optional, so a build that uses websockets, multipart or compression pulls in more than a plain server does. Disabling a feature you no longer need is a real reduction in dependency surface, and enabling one can change your dependency graph enough to matter for build times and audit scope.
warp is MIT licensed, the same identifier the badge in the README points to and the LICENSE file at the root carries. MIT is permissive: it allows use in closed-source products provided the copyright notice and permission notice are retained. That is a summary of the licence text, not legal advice, and if you redistribute warp inside a product you should read LICENSE and your own obligations rather than relying on this paragraph.
Editorial conclusion
Adopt warp if you already write async Rust and want routing, extraction and rejection handling expressed as typed values you can combine in ordinary functions. Do not adopt it if you need a large middleware ecosystem, an opinionated project layout, or a framework that hides hyper from you. Before committing, check the CHANGELOG for the 0.4.x line, confirm which optional features your build needs (server, websocket, multipart, compression), and read examples/rejections.rs, because how you turn a Rejection into a response is the part of warp that decides whether your API returns useful errors.
Frequently asked questions
What is warp?
warp is a web server framework for Rust whose fundamental building block is the Filter, a value that either rejects a request or produces typed output from it. It builds on hyper, so HTTP/1, HTTP/2 and asynchronous connection handling come from that layer.
Is warp the best web framework for Rust?
There is no single answer, and the project does not rank frameworks. warp's distinguishing choice is that routing and extraction are composed Filter values rather than a router with separate handler functions, which suits services with conditional routing and costs more code for large, uniform route tables.
How do I install warp in a Rust project?
Add it to Cargo.toml as a dependency. The README's example pairs tokio with the full feature set and warp 0.4 with the server feature enabled, which is what brings in the hyper-based serving path.
Does warp support websockets, multipart and compression?
The README lists websockets, multipart form data, and gzip, deflate and Brotli compression among what the Filter system provides out of the box. In Cargo.toml these are optional dependencies: tokio-tungstenite for websockets, multer for multipart and async-compression for compression.
What happens in warp when no route matches a request?
warp produces a Rejection rather than a fixed 404 response, and you convert it into a response yourself. The repository includes examples/rejections.rs specifically to show that conversion.
Official sources
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.
[](https://hysenlabs.com/projects/seanmonstar-warp)