🐙 tako
Getting started

Quickstart

Add the tako-rs dependency, write a handler, register a GET route on Router, and serve it with tako::serve and a TcpListener.

Quickstart

This walks through installing Tako, writing your first handler, and running it on a local TCP listener. It mirrors the examples/hello-world crate in the repository.

1. Add the dependency

[dependencies]
tako-rs = "2"
tokio = { version = "1", features = ["full"] }
anyhow = "1"

The package is published as tako-rs; everything is re-exported under the tako::* path. The default feature set covers HTTP/1.1, WebSocket, SSE, raw TCP/UDP, Unix sockets, and PROXY protocol on Tokio. See Installation for the runtime choice and opt-in feature flags.

2. Write the server

use anyhow::Result;
use tako::Method;
use tako::responder::Responder;
use tako::router::Router;
use tokio::net::TcpListener;

async fn hello_world() -> impl Responder {
  "Hello, World!".into_response()
}

#[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, "/", hello_world);

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

  Ok(())
}

3. Run it

cargo run

Then, in another terminal:

curl http://127.0.0.1:8080/
# Hello, World!

What the code does

A handler is any async fn whose arguments implement FromRequest / FromRequestParts and whose return type implements Responder. The hello-world handler takes zero arguments, so it extracts nothing from the request:

async fn hello_world() -> impl Responder {
  "Hello, World!".into_response()
}

Router::new() produces an empty router. route(Method::GET, "/", handler) registers a handler against a (method, path) pair. Convenience shorthands exist too — router.get(path, handler), plus .post, .put, .patch, .delete, .head, and .options:

let mut router = Router::new();
router.route(Method::GET, "/", hello_world);
// equivalently: router.get("/", hello_world);

tako::serve(listener, router) is the simplest server entry point: it builds a default Server and drives it until the listener stops accepting. For finer control — graceful shutdown, drain timeouts, HTTP/2, or TLS — use Server::builder() directly.

Adding extractors

Handlers compose by taking more arguments. Tako runs the extractor for each argument before invoking the handler:

use serde::Deserialize;
use tako::Method;
use tako::extractors::json::Json;
use tako::extractors::path::Path;
use tako::responder::Responder;
use tako::router::Router;

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

#[derive(Deserialize)]
struct CreateUser { name: String }

async fn get_user(Path(p): Path<UserPath>) -> impl Responder {
  format!("user_id={}", p.id)
}

async fn create_user(Json(body): Json<CreateUser>) -> impl Responder {
  format!("created: {}", body.name)
}

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

Return types

Anything implementing Responder can be returned from a handler. The blanket impls cover the common shapes:

  • &'static str / Stringtext/plain body
  • Bytes / Vec<u8> — raw body
  • Json<T> — JSON body with Content-Type: application/json
  • (StatusCode, T) — status plus body
  • (StatusCode, HeaderMap, T) — status, headers, and body
  • StatusCode alone — empty body, just the status line
  • Result<T, E> where both arms implement Responder — the preferred shape for fallible handlers

Next steps

  • Routing — path parameters, nesting, scopes, typed slots.
  • Extractors — the full catalog of request extractors.
  • State — share configuration and dependencies with handlers.
  • Middleware — auth, CORS, compression, metrics, rate limiting.

On this page