Use Warp by composing filters that match HTTP requests and chaining them to produce responses, then binding the resulting handler to a server address. You typically start by importing the core filters like path, body, and header, then combine them using the and combinator to define your route logic before calling run to start listening.
Here is a minimal example creating a JSON API endpoint that accepts a user ID and returns a greeting:
use warp::Filter;
#[tokio::main]
async fn main() {
// Define a filter that matches GET requests to /hello/<id>
// It extracts the path parameter and returns a JSON response
let hello = warp::path!("hello" / String)
.and(warp::get())
.map(|id| {
format!("Hello, {}!", id)
})
.map(|reply| warp::reply::json(&serde_json::json!({ "message": reply })));
// Combine the route with a logger and run the server on port 3030
let routes = hello
.with(warp::log("warp-example"))
.recover(warp::reject::recover_fn(|err| async move {
warp::reply::json(&serde_json::json!({ "error": "Internal Server Error" }))
}));
println!("Listening on port 3030");
warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
}
For more complex applications involving request bodies or headers, you chain additional filters. For instance, to handle a POST request with a JSON body, you use warp::body::json() to deserialize the payload directly into a struct.
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Debug)]
struct UserInput {
name: String,
}
#[derive(Serialize)]
struct UserResponse {
id: u32,
name: String,
}
let post_user = warp::path!("users")
.and(warp::post())
.and(warp::body::json()) // Automatically deserializes JSON body
.map(|input: UserInput| {
// Simulate processing
UserResponse {
id: 1,
name: input.name,
}
})
.map(|reply: UserResponse| warp::reply::json(&reply));
Key things to remember:
- Filter Composition: Every route is a chain of filters. If a filter rejects the request (e.g., wrong method or missing path), the chain stops and Warp tries the next route or returns a 404.
- Async Runtime: Warp is built on Tokio, so you must run your main function with
#[tokio::main]. - Rejection Handling: If you need custom error responses, use
.recover()to catchwarp::Rejectiontypes and return a custom reply instead of the default 400/500 errors.
Add warp = "0.3" and tokio = { version = "1", features = ["full"] } to your Cargo.toml to get started.