# How to Implement Custom Middleware and Request Handlers in Topcoat

> Learn how to implement custom middleware and request handlers in Topcoat. Discover Topcoat's functional approach with async functions and leverage the Tower ecosystem via TowerLayer and TowerRoute.

- Repository: [Tokio/topcoat](https://github.com/tokio-rs/topcoat)
- Tags: how-to-guide
- Published: 2026-07-31

---

**Topcoat adopts a functional approach where async functions accepting `&Cx` serve as request handlers, while true middleware concerns leverage the Tower ecosystem through `TowerLayer` and `TowerRoute` wrappers.**

The tokio-rs/topcoat framework eschews traditional middleware pipelines in favor of composable functions that operate on a request-scoped context. When you need cross-cutting concerns like logging, timeouts, or rate limiting, you implement custom middleware by integrating with Tower, allowing you to wrap routes or mount entire external services as documented in [`crates/topcoat-router/docs/tower.md`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-router/docs/tower.md).

## Understanding Topcoat's Functional Handler Model

Topcoat's architecture rejects hidden middleware chains in favor of explicit function signatures. According to [`crates/topcoat/docs/functions_not_middlewares.md`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat/docs/functions_not_middlewares.md), you write small, composable functions that receive a request-scoped `&Cx` and return a value implementing `IntoResponse`.

### Writing Request Handlers with `Cx`

A request handler is any async function that takes `cx: &Cx` (or `mut cx: Cx` when mutation is required) and returns a type implementing `IntoResponse`. The `Cx` context, documented in [`crates/topcoat/docs/context.md`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat/docs/context.md), provides rich extraction helpers for query parameters, path parameters, and body parsers.

```rust
use topcoat::prelude::*;

/// Returns a greeting. The function receives the request context `cx`.
#[page]
async fn hello(cx: &Cx) -> Result<impl IntoResponse> {
    // Extract a query param `name` (or default to "world").
    let name: Option<String> = cx.query().get("name").await?;
    let greeting = format!("Hello, {}", name.unwrap_or_else(|| "world".into()));
    Ok(greeting)
}

```

The handler uses `cx.query().get()` to extract optional parameters, demonstrating how request extraction works without explicit middleware.

### Registering Handlers via Macros and Builder API

You can register handlers using attribute macros or the builder API defined in [`crates/topcoat-router/src/lib.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-router/src/lib.rs). The `#[page]` macro automatically registers the function with the router. Alternatively, use `Router::builder()`:

```rust
let router = Router::builder()
    .route(MyHandler::new())
    .build();

```

For route-specific handlers, use `#[route]` or the `route()` method with the handler struct.

## Implementing Custom Middleware with Tower

When you need true middleware for concerns like timeouts, compression, or CORS, Topcoat leverages the Tower ecosystem. The implementation in [`crates/topcoat-router/src/tower.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-router/src/tower.rs) provides thin wrappers that adapt Tower layers for Topcoat's router.

### Creating a Custom Tower Layer

Implement `tower::Layer` to create reusable middleware that sees the full `http::Request`. This layer can modify requests, short-circuit responses, or add headers before the Topcoat handler runs.

```rust
use std::{task::{Context, Poll}, pin::Pin, future::Future};
use tower::{Layer, Service};
use topcoat::router::{Router, tower::TowerLayer};

/// Simple logging layer that prints the request URI.
#[derive(Clone)]
struct LoggingLayer;

impl<S> Layer<S> for LoggingLayer {
    type Service = LoggingService<S>;

    fn layer(&self, inner: S) -> Self::Service {
        LoggingService { inner }
    }
}

#[derive(Clone)]
struct LoggingService<S> {
    inner: S,
}

impl<S, ReqBody> Service<http::Request<ReqBody>> for LoggingService<S>
where
    S: Service<http::Request<ReqBody>, Response = http::Response<ReqBody>> + Clone + Send + 'static,
    S::Future: Send + 'static,
{
    type Response = S::Response;
    type Error = S::Error;
    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;

    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.inner.poll_ready(cx)
    }

    fn call(&mut self, req: http::Request<ReqBody>) -> Self::Future {
        println!("Incoming request: {}", req.uri());
        let fut = self.inner.call(req);
        Box::pin(async move { fut.await })
    }
}

```

Attach the layer to your router using `TowerLayer::new()`.

### Scoping Middleware with `TowerLayer::at`

By default, layers apply to every route. Scope them to specific paths using `TowerLayer::at()` as shown in [`crates/topcoat-router/docs/tower.md`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-router/docs/tower.md):

```rust
// Apply the middleware only to the `/api` namespace.
let router = Router::builder()
    .layer(TowerLayer::new(LoggingLayer).at("/api"))
    .build();

```

The `at("/api")` method restricts the middleware to requests matching that prefix, allowing fine-grained control over where middleware executes.

### Using Pre-built Tower Middleware

Reuse existing Tower layers like `TimeoutLayer` without custom implementation:

```rust
use std::time::Duration;
use topcoat::router::{Router, tower::TowerLayer};
use tower::timeout::TimeoutLayer;

let router = Router::builder()
    .layer(
        // Abort any request that takes longer than 5 seconds.
        TowerLayer::new(TimeoutLayer::new(Duration::from_secs(5))).at("/api")
    )
    .build();

```

This pattern applies standard Tower middleware to Topcoat routes while maintaining the functional handler model for your application logic.

## Mounting External Services with `TowerRoute`

If you have existing Axum routers, Hyper services, or any `tower::Service`, expose them as subtrees using `TowerRoute`. This facilitates incremental migration of legacy code into Topcoat while keeping the original service intact, as implemented in [`crates/topcoat-router/src/tower.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-router/src/tower.rs).

```rust
use topcoat::router::{Router, Methods, tower::TowerRoute};
use axum::Router as AxumRouter;

// Existing Axum app that serves legacy endpoints.
let legacy_axum: AxumRouter = legacy_app();

// Expose the whole Axum tree under `/legacy`.
let router = Router::builder()
    .route(TowerRoute::new(Methods::Any, "/legacy/{*rest}", legacy_axum))
    .build();

```

The `TowerRoute::new()` method takes an HTTP method (or `Methods::Any`), a path pattern including wildcards like `{*rest}`, and the service to mount. This integrates external services at the router level, distinct from function-based handlers.

## Summary

- **Write handlers as functions**: Create async functions taking `&Cx` that return `IntoResponse`, leveraging extraction methods on the context object defined in [`crates/topcoat/docs/context.md`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat/docs/context.md).
- **Implement Tower layers**: For cross-cutting concerns, implement `tower::Layer` and `Service` to process raw `http::Request` objects before they reach your handlers.
- **Apply middleware selectively**: Use `TowerLayer::new(your_layer).at("/prefix")` to scope middleware to specific route prefixes, as documented in [`crates/topcoat-router/docs/tower.md`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-router/docs/tower.md).
- **Mount legacy services**: Use `TowerRoute::new()` to integrate existing Axum or Tower services under specific paths, enabling incremental migration strategies.

## Frequently Asked Questions

### What is the difference between a handler and middleware in Topcoat?

Handlers are async functions that accept `&Cx` and return `IntoResponse`, focusing on generating responses for specific routes. Middleware, implemented via Tower layers, intercepts the raw `http::Request` before it reaches the handler and can modify the request or response. Topcoat reserves middleware for concerns like timeouts and logging, while business logic lives in functional handlers, according to [`crates/topcoat/docs/functions_not_middlewares.md`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat/docs/functions_not_middlewares.md).

### How do I access the raw HTTP request in a handler?

Access specific parts of the request through the `Cx` context extraction helpers (e.g., `cx.query()`, `cx.body_json()`). If you need the raw `http::Request`, implement a custom Tower layer that processes the request before passing it to the handler, as the `Cx` abstraction intentionally hides raw request details to encourage type-safe extraction.

### Can I use existing Axum middleware with Topcoat?

Yes. Since Axum is built on Tower, any Axum middleware that implements `tower::Layer` works with Topcoat via `TowerLayer::new()`. You can also mount an entire Axum router as a subtree using `TowerRoute::new(Methods::Any, "/path/{*rest}", axum_router)`, allowing you to reuse existing Axum ecosystems within a Topcoat application.

### Where does the `Cx` context type come from?

The `Cx` type is defined in the topcoat core and documented in [`crates/topcoat/docs/context.md`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat/docs/context.md). It provides request-scoped data extraction, body parsing, and response building capabilities. The context is passed by reference (`&Cx`) to handlers, with mutable access (`mut cx: Cx`) available when you need to consume the request body or modify response headers directly.