🐙 tako
Reference

Migrating from 1.x to 2.0

Every breaking change between the tako 1.x line on crates.io and the 2.0.0 release, with before/after code for each.

Status: released. Covers every breaking change between the released 1.x line on crates.io and the 2.0.0 release.

This guide explains what to change in your code to move from 1.x to 2.0.

At a glance

Area1.x2.0
Per-router stateGLOBAL_STATE, one per typeRouter::with_state
Handler returnsResponder onlyResult<R, E> where E: Responder
Sub-routingRouter::mergenest(), scope()
Wrong method404 Not Found405 with an Allow header
Errorserror_handler (5xx only)plus client_error_handler and use_problem_json()
Macros{id: u64} only, always a Params struct{id} and {id: u64}; no Params struct unless a typed slot exists
Server bootstrapserve_*, serve_tls, …Server::builder()
TLSPEM onlyPEM, DER, Resolver, ReloadableResolver, and ClientAuth (mTLS)
Connection infoSocketAddr / UnixPeerAddrConnInfo (unified)
tako-core-localseparate !Send routerremoved

1. Per-router typed state

1.x

let cfg = Config { db, secret };
Router::state(cfg);                    // one slot per TypeId, global
let router = Router::new();

2.0

let router = Router::new()
    .with_state(Config { db, secret });  // instance-local
// `State<T>` extractor reads from the per-router store first, then falls
// back to GLOBAL_STATE for backward compat.

Two routers in the same process can hold distinct T values without newtype wrappers. Hot-path overhead is one AtomicBool::Acquire when the feature is unused.

2. Handler return types

1.x

async fn handler() -> impl Responder { ... }

2.0 — Result<T, E: IntoResponse> is supported natively:

async fn handler() -> Result<Json<User>, ApiError> { ... }

IntoResponse is a re-export of Responder. New blanket impls were added for Bytes, Vec<u8>, Cow<'static, str>, serde_json::Value, (StatusCode, HeaderMap, TakoBody), (StatusCode, HeaderMap), HeaderMap, and StatusCode.

3. Sub-routing — nest and scope

1.x

let api = Router::new();
let main = Router::new();
main.merge(api);   // mutates shared Arc<Route>; double-merging stacks
                   // middleware twice

2.0

let main = Router::new()
    .nest("/v1", v1_router)
    .nest("/v2", v2_router)
    .scope("/admin", |r| {
        r.layer(admin_auth).get("/", dashboard);
    });

nest clones routes via Route::cloned_with_path so re-nesting can never double-stack middleware. scope carries a pending prefix consumed by every method shorthand inside the closure.

4. 405 with Allow instead of 404

1.x returned 404 Not Found when the path matched but the method did not. 2.0 returns 405 Method Not Allowed with a comma-separated Allow header.

If you have tests asserting 404 on a path-match-method-miss, update them to expect 405 plus the appropriate Allow value.

5. RFC 7807 application/problem+json

1.x had Router::error_handler(...) that fired only on 5xx.

2.0 adds:

  • Router::client_error_handler(...) — fires on 4xx.
  • Router::use_problem_json() — convenience that installs default_problem_responder for both 4xx and 5xx.
  • tako::problem::Problem struct with a Responder impl that emits application/problem+json.

6. Macro syntax

1.x

#[tako::route("GET", "/users/{id: u64}")]
async fn get_user(id: u64) -> ... { ... }
// Always emits `GetUserParams` struct, even on static paths.

2.0

  • {id: u64} and {id} are both accepted. The first is a typed slot, the second is matchit pass-through.
  • The *Params struct is only emitted when at least one typed slot exists.
  • For static paths with name = "...", a unit-marker struct is still emitted so Name::METHOD / Name::PATH constants stay reachable.

7. Server bootstrap

1.x

serve(router, addr).await?;
serve_tls(router, addr, cert, key).await?;
serve_h3(router, addr, cert, key).await?;
serve_unix(router, path).await?;
serve_proxy_protocol(...).await?;

2.0

let server = tako::Server::builder()
    .config(ServerConfig::default()
        .header_read_timeout(Duration::from_secs(30))
        .keep_alive(true)
        .max_concurrent_streams(100)
        .max_connections(50_000)
        .drain_timeout(Duration::from_secs(60)))
    .tls(TlsCert::pem_paths("cert.pem", "key.pem"))
    .build();

let handle = server.spawn_http(listener, router);
// .spawn_tls / .spawn_h2c / .spawn_h3 / .spawn_unix_http /
// .spawn_proxy_protocol / .spawn_tcp_raw / .spawn_udp_raw

handle.shutdown(Duration::from_secs(30)).await;

Changes from the original v2 shape:

  • The listener is handed to spawn_*, not the builder, so a single Server instance can fan out to multiple listeners.
  • ServerConfig is one flat struct instead of HttpConfig + TlsConfig + H3Config + Limits.
  • ServerHandle::shutdown returns () and is runtime-agnostic (Notify-based) so the same type comes back from both tokio and compio paths.

8. TLS

1.x supported TlsCert::PemPaths. 2.0 adds:

  • TlsCert::Der { certs, key, client_auth }
  • TlsCert::Resolver { resolver, client_auth }
  • ClientAuth::{Optional(roots), Required(roots)} for mTLS, threaded through every TCP/TLS, compio-TLS, and HTTP/3 spawn path.
  • ReloadableResolver for hot-reload without a listener restart (callers wire their own file-watcher / signal trigger).
  • New entry points serve_tls_with_rustls_config_and_shutdown and serve_h3_with_rustls_config_and_shutdown that take a fully-built Arc<rustls::ServerConfig> for advanced cases.

ACME (TlsCert::Acme { ... }) is deferred.

9. Trust store: webpki-roots vs OS

2.0 adds an opt-in native-certs feature that swaps the bundled webpki-roots snapshot for rustls-native-certs (operating-system trust store). Default behavior is unchanged — webpki-roots is still used unless native-certs is enabled.

tako-rs = { version = "2", features = ["client", "native-certs"] }

The client and native-certs features and tako::client were removed in 2.3.0. Use reqwest or ureq for outbound HTTP.

10. Unified ConnInfo

1.x inserted a different connection-info type per transport: SocketAddr (TCP/TLS), UnixPeerAddr (Unix), something else for H3.

2.0 unifies on:

struct ConnInfo {
    peer: PeerAddr,        // Ip / Unix / Other
    local: PeerAddr,
    transport: Transport,  // Http1 / Http2 / Http3 / Unix / Tcp
    tls: Option<TlsInfo>,  // alpn, sni, version
}

Legacy types (SocketAddr, UnixPeerAddr, ProxyHeader) remain in extensions for backward compatibility, alongside ConnInfo.

11. Plugin / middleware updates

Plugin / middleware2.0 change
sessionIdle vs absolute TTL, rolling cookie refresh, Session::rotate(), configurable SameSite/Domain, bulk revocation
rate_limiterComposite-key support, IETF RateLimit-* headers, Algorithm::Gcra, UnkeyedBehavior choice
idempotencyVerified TTL = 86_400 s, compio inflight_wait_timeout_ms honored
jwt_authIss/aud/leeway constraints, MultiKeyVerifier, runtime rotation/revocation, optional remote introspection
csrfToken bound to Session, Origin/Referer allow-list, configurable SameSite
compressionContentTypePolicy enum replaces substring filter
corsOriginMatcher::{Exact, Suffix, Custom}, allow_private_network
metricsLatency histogram with with_buckets(..) override
security_headersCSP + nonce, COOP/COEP/CORP, Permissions-Policy, HSTS preload toggle, X-XSS-Protection removed
request_idNow focused on X-Request-ID; traceparent parsing moved to a new middleware
NEW: timeoutPer-request deadline, dynamic-per-request override
NEW: traceparentW3C Trace Context parser/emitter, TraceContext extension
NEW: access_logStructured one-line access log; default sink tracing INFO
NEW: problem+jsonRewrites non-JSON 4xx/5xx into application/problem+json
NEW: circuit_breakerClosed/open/half-open with rolling counter
NEW: ip_filterCIDR allow/deny lists
NEW: healthcheck/live, /ready, /__drain with async readiness probes
NEW: etagSHA-1 strong validator, conditional GET
NEW: tenantX-Tenant-ID / subdomain / path-segment / custom strategies
NEW: hmac_signatureHMAC-SHA256 signature verification
NEW: json_schemaRequest/response validator

12. Backend traits

tako_rs_plugins::stores adds five traits:

  • SessionStore
  • RateLimitStore
  • IdempotencyStore
  • JwksProvider
  • CsrfTokenStore

Built-in middleware still defaults to in-memory stores. Implement these traits to back middleware with Redis / Postgres / external services. The .store(backend) builder connections and fallible, atomic store contracts are implemented in 2.1.

Companion crates tako-stores-redis and tako-stores-postgres are on the follow-up list and intentionally not part of the framework dependency surface.

13. Extractors

  • tako::extractors::path::Path<T> is the new axum-style wrapper. The old zero-arg extractor was renamed RawPath (breaking). Migrate any call site that used Path as a no-argument extractor.
  • JwtClaims<T> is renamed JwtClaimsUnverified<T>. The old name remains as a #[deprecated] alias. The verifying counterpart is tako_rs_plugins::extractors::jwt::JwtClaimsVerified<C>, fed by JwtAuth<V> middleware.
  • New: TypedHeader<H> (feature typed-header), Extension<T>, MatchedPath, OriginalUri, Host, Scheme, ConnectInfo<T>, ContentLengthLimit<T, N>, QueryMulti<T>, MultipartConfig-driven BufferedUploadedFile, KeyRing-rotated cookie extractors, Validated<T> (features validator / garde).

14. tako-core-local removed

The !Send LocalRouter was removed. Per-worker isolation is fully covered by serve_per_thread / serve_per_thread_compio with the thread-safe Router. Replace any tako::local::* import with the matching thread-safe equivalent. The per-thread-local / per-thread-compio-local cargo features are gone.

15. Streams

  • Sse gained SseEvent builder, Sse::events(...), Sse::keep_alive(...), last_event_id(headers) helper.
  • TakoWs<H> builder gained protocols, max_frame_size, max_message_size, allowed_origins, upgrade_timeout, keep_alive(WsKeepAlive). The keep-alive option never scheduled pings and is deprecated in 2.1; handlers own their ping/pong policy. Per-message deflate is not implemented.
  • FileStream::with_etag(..), with_last_modified(..), with_content_type(..), evaluate_conditional(...), weak_etag_from_metadata(...). Multipart/byteranges and sendfile(2) are deferred.
  • ServeDirBuilder::precompressed(...), index_files([...]), traversal rejection at parse time. SPA fallback uses the same resolver.
  • WebTransport is currently raw QUIC and is also exported as RawQuicSession so call sites can pick the honest name. W3C WebTransport sessions landed in 2.3.0: see WebTransport.

16. gRPC

  • New: GrpcServerStream<S, T>, GrpcClientStream<T>, GrpcBidi<Req, Resp>.
  • parse_grpc_timeout(...) and read_grpc_deadline(req) insert a GrpcDeadline(Instant) extension.
  • GrpcInterceptor async trait + InterceptorChain short-circuiting on the first Err(GrpcStatus).
  • Reflection / health scaffolding (storage layer ships now; protobuf encoders deferred until consumers don't have to ship protoc).
  • gRPC-Web byte-level decoder/encoder helpers.

17. GraphQL & OpenAPI

  • APQ via PersistedQueryStore trait + MemoryPersistedQueryStore.
  • Complexity / depth / cost limits builder on async_graphql::SchemaBuilder.
  • utoipa is now the documented primary OpenAPI integration; vespera remains available behind its existing cargo feature.

18. Queue & signals

  • QueueBackend async trait. MemoryBackend ships in-tree; remote brokers go in companion crates.
  • Queue::push_dedup(name, payload, key) collapses duplicate pending jobs.
  • Cron scheduling behind the queue-cron cargo feature.
  • New signals: queue.job.queued / started / completed / failed / retrying / dead_letter. Canonical strings under tako_rs_core::queue::signal_ids.
  • signals::bus::SignalBus and LocalBus are unconnected scaffolding, not automatic cluster forwarding. They are hidden from generated docs in 2.1.

19. v2 client

V2Client + V2ClientBuilder ride on hyper_util::client::legacy::Client:

  • connection pool with idle timeout / per-host caps
  • per-request timeout
  • retry policy with exponential backoff
  • default User-Agent, traceparent-friendly request handling

The legacy TakoClient / TakoTlsClient keep working for backward compatibility but new code should use V2Client.

The whole tako::client module was removed in 2.3.0. Use reqwest or ureq instead.

Runtime selection

--all-features now compiles and selects Compio. HTTP/3, h2c, WebTransport, PROXY protocol, and Unix sockets run on both runtimes. Only the raw QUIC helper (RawQuicSession) is Tokio-only, so use a Tokio feature subset for it. See the 2.1 migration guide.

Deferred to a future 2.x release

The following are tracked separately and have not yet landed:

  • tako-stores-redis, tako-stores-postgres
  • TlsCert::Acme { ... }
  • HTTP/3 qlog
  • Multipart/byteranges responder, Linux sendfile(2) path
  • Generated gRPC stubs (reflection / health protobuf)
  • Cluster SignalBus impls (Redis, NATS, Kafka)
  • Hot-reload Arc<Router> swap (current path keeps Box::leak for per-connection performance)

When these land, this guide is updated alongside.

Last updated on

On this page