🐙 tako
Extractors

Extractors

Typed handler arguments that read shape out of a request — JSON, form, query, path, headers, cookies, JWT, and more.

Extractors

Extractors read shape out of a request and surface it as typed handler arguments. Any type implementing FromRequest (consumes the body) or FromRequestParts (headers / URL only) can be used as a handler parameter. A handler may take any number of extractors; Tako runs them in order before calling the handler.

use serde::{Deserialize, Serialize};
use tako::Method;
use tako::extractors::json::Json;
use tako::extractors::path::Path;
use tako::extractors::query::Query;
use tako::extractors::state::State;
use tako::responder::Responder;
use tako::router::Router;

#[derive(Deserialize)]
struct ListQuery { page: u32, per_page: u32 }

#[derive(Deserialize)]
struct UserPath { id: u64 }

#[derive(Deserialize, Serialize)]
struct CreatePost { title: String, body: String }

#[derive(Clone)]
struct Db; // imagine a real pool here

async fn list_posts(
  Path(UserPath { id }): Path<UserPath>,
  Query(q): Query<ListQuery>,
  State(_db): State<Db>,
) -> impl Responder {
  format!("user={id}, page={}, per_page={}", q.page, q.per_page)
}

async fn create_post(
  Path(UserPath { id }): Path<UserPath>,
  Json(p): Json<CreatePost>,
) -> Json<CreatePost> {
  println!("creating post for user={id}: {:?}", p.title);
  Json(p)
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
  let listener = tokio::net::TcpListener::bind("127.0.0.1:8080").await?;

  let mut router = Router::new();
  router.with_state(Db);
  router.route(Method::GET,  "/users/{id}/posts", list_posts);
  router.route(Method::POST, "/users/{id}/posts", create_post);

  tako::serve(listener, router).await;
  Ok(())
}

How binding works

Each extractor is a wrapper type you destructure in the argument position — Json(payload): Json<T>, Path(p): Path<T>, and so on. The body extractors (FromRequest) consume the request body and must come last in the argument list, because the body can only be read once. The parts extractors (FromRequestParts) only touch headers and the URI, so any number of them can run before the body extractor.

If extraction fails — a malformed JSON body, a missing required query parameter, a bad Authorization header — the extractor's error type is turned into a Responder (usually a 400 or 401) and the handler is never called. Route-level error handlers and use_problem_json() can reshape those errors before they hit the wire.

use anyhow::Result;
use serde::Deserialize;
use serde::Serialize;
use tako::Method;
use tako::extractors::json::Json;
use tako::extractors::params::Params;
use tako::extractors::query::Query;
use tako::router::Router;
use tokio::net::TcpListener;

#[derive(Deserialize)]
struct Pagination {
  page: u32,
  per_page: u32,
}

#[derive(Deserialize)]
struct UserPath {
  id: u64,
}

#[derive(Deserialize, Serialize, Clone)]
struct CreateUser {
  name: String,
  email: String,
}

#[derive(Serialize)]
struct Created {
  id: u64,
  name: String,
  email: String,
}

// GET /users/{id}/posts?per_page=10&page=2
// Demonstrates multiple extractors: Params + Query
async fn list_user_posts(Params(user): Params<UserPath>, Query(p): Query<Pagination>) -> String {
  format!(
    "user_id={}, page={}, per_page={}",
    user.id, p.page, p.per_page
  )
}

// POST /users with JSON body {"name":"...","email":"..."}
// Demonstrates a body extractor
async fn create(Json(user): Json<CreateUser>) -> Json<Created> {
  // Normally you'd persist the user; here we just echo back with an id
  Json(Created {
    id: 1,
    name: user.name,
    email: user.email,
  })
}

#[tokio::main]
async fn main() -> Result<()> {
  let listener = TcpListener::bind("127.0.0.1:8080").await?;

  let mut router = Router::new();
  router.route(Method::GET, "/users/{id}/posts", list_user_posts);
  router.route(Method::POST, "/users", create);

  tako::serve(listener, router).await;

  Ok(())
}

Catalog

Tako ships 22+ bundled extractors. They fall into five groups, each with its own reference page.

Body — /docs/extractors/body

ExtractorDescription
Json<T>JSON body (with optional SIMD acceleration)
Form<T>URL-encoded form body
BytesRaw request body stream
Protobuf<T>Protocol Buffers body (feature protobuf)
SimdJson<T>Force SIMD JSON parsing (feature simd)
MultipartMultipart form data (feature multipart)

Request metadata — /docs/extractors/request-meta

ExtractorDescription
Query<T>URL query parameters
Path<T>Typed route path parameters
Params<T>Dynamic path params map
HeaderMapFull request headers
AcceptContent negotiation
AcceptLanguageLanguage negotiation
RangeHTTP Range header
IpAddrClient IP address

Auth — /docs/extractors/auth

ExtractorDescription
BasicHTTP Basic credentials (BasicAuth)
BearerBearer token (BearerAuth)
ApiKeyAPI key from header or query
JwtClaimsUnverified<T>JWT claims, signature not checked
JwtClaimsVerified<T>Signature-verified JWT claims

Cookies — /docs/extractors/cookies

ExtractorDescription
CookieJarCookie reading / writing
CookieSignedHMAC-signed cookies (SignedCookieJar)
CookiePrivateEncrypted cookies (PrivateCookieJar)

State — /docs/state

ExtractorDescription
State<T>Shared application state, as Arc<T>

Beyond the catalog

The crate also bundles QueryMulti<T> (repeated keys), RawPath / RawQuery, MatchedPath, OriginalUri, Host, Scheme, TypedHeader<H>, Extension<T>, ConnectInfo<T>, ContentLengthLimit<T, N>, and Validated<T> (behind the validator / garde features). The zero-copy-extractors feature enables borrowing variants of the body and header extractors for hot-path handlers. See the crate rustdoc for the complete list.

On this page