# How Module-Based Automatic Routing Works in Topcoat: A Complete Guide

> Discover how module-based automatic routing in Topcoat transforms your Rust module hierarchy into a type-safe URL routing table. Learn about kebab-case conversion and segment overrides.

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

---

**TLDR:** Topcoat's `module_router!` macro transforms your Rust module hierarchy into a type-safe URL routing table by mapping compile-time module paths to route paths, automatically applying kebab-case conversion while supporting explicit overrides through the `segment!` macro.

The `tokio-rs/topcoat` framework implements **module-based automatic routing** to eliminate boilerplate configuration by deriving your application's URL structure directly from its module tree. This convention-over-configuration approach uses compile-time introspection via `module_path!()` and procedural macros to discover handlers, converting filesystem organization into deterministic route definitions. Understanding this pipeline is essential for building scalable Topcoat applications without manual route registration.

## The ModuleRouterBuilder Pipeline

At the core of this system is the **`ModuleRouterBuilder`** struct, implemented in [`crates/topcoat-router/src/module/router.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-router/src/module/router.rs). When you invoke the **`module_router!`** macro (defined in [`crates/topcoat-router/src/module/mod.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-router/src/module/mod.rs)), it instantiates this builder and executes a deterministic five-stage pipeline to construct your routing table.

### Root Detection via module_path!()

The macro establishes the routing root by calling `ModuleRouterBuilder::new(root_module_path)`, where `root_module_path` is the compile-time string value of `module_path!()` from the module invoking the macro. This establishes the baseline prefix that all subsequent route calculations strip away, ensuring that module paths are translated relative to your application's entry point.

### Path Translation and Segment Types

For each discovered module, the **`module_path_to_path`** function processes the module path components through the following rules:

- **User overrides**: Checks for **`segment!`** macro declarations that modify the `SegmentKind`—valid kinds are `Static`, `Group`, `Param`, and `CatchAll`
- **Default heuristics**: Module names with a leading underscore become **Group** segments (preserved in the path structure but omitted from the final URL), while standard identifiers become **Static** segments
- **Kebab-case conversion**: Static segment names are automatically converted to kebab-case (e.g., `blog_posts` becomes `blog-posts`)

The resulting list of `PathSegment` structs is assembled into a `PathBuf`. For groups, this buffer generates the URL-matching pattern via the `to_matchit_path` conversion, as verified in the test suite.

### Resource Discovery via Inventory

When the `discover` feature is enabled, the builder walks the **inventory** tables populated at link time by the `#[page]`, `#[layout]`, `#[route]`, and `#[layer]` procedural macros:

1. **`discover_segments`** registers every `Segment` declared with `segment!` to ensure overrides are available before path calculation
2. **`discover_pages`**, **`discover_layouts`**, **`discover_routes`**, and **`discover_layers`** register the corresponding resources, each invoking `module_path_to_path` to compute the concrete route path

### Registration and Safety Constraints

The computed routes are handed to the underlying `RouterBuilder` (the core routing engine) via the methods `page`, `layout`, `route`, and `layer`. The builder enforces two critical safety invariants:

- **Ordering requirement**: Segment overrides must be registered *before* any pages or layouts—the `segment` method contains an `assert!` that validates this sequence
- **Uniqueness constraint**: Duplicate paths for discovered layouts or layers trigger a panic, preventing ambiguous nesting configurations

## Configuring Routes with segment! Overrides

While automatic routing handles conventional structures, the **`segment!`** macro provides granular control when you need dynamic parameters or custom naming:

```rust
// Convert a static module name into a URL parameter
segment! {
    "app::users::id" => kind: Param
}

```

With this override, the module path `app::users::id` resolves to the URL pattern `/users/{id}` instead of the static `/users/id`.

Group segments organize your code without affecting the URL structure:

```rust
// `_marketing` is a Group segment—present in the module tree but stripped from URLs
pub mod _marketing {
    pub mod pricing; // Accessible at "/pricing", not "/marketing/pricing"
}

```

## Complete Implementation Example

Define your router at the application root:

```rust
// examples/module-router/src/app.rs
use topcoat::router::module_router;
use topcoat::router::module::Segment;

pub fn app() -> topcoat::Router {
    // `module_router!` builds from the current module tree
    topcoat::router::module_router!()
        // Optional: manually inject a segment override before discovery
        .segment(Segment::new("app::admin", Some(SegmentKind::Group), None))
        .discover()    // Auto-discover all #[page], #[layout], and #[route] items
        .build()
}

```

Create pages anywhere in your module tree:

```rust
// src/pages/about.rs
#[page]  // Registers this function with the inventory system
pub async fn get(cx: &Cx) -> impl IntoResponse {
    topcoat_view::html! { <h1>"About"</h1> }
}

```

Topcoat automatically maps the module path `app::pages::about` to the route `/pages/about`, applying kebab-case formatting and respecting any `segment!` overrides encountered along the path.

## Summary

- **`module_router!`** initiates automatic routing by creating a `ModuleRouterBuilder` that uses `module_path!()` to establish the root context.
- **`module_path_to_path`** translates Rust module paths to URL segments, defaulting to kebab-case static segments while treating underscore-prefixed modules as groups.
- The **inventory system** enables link-time discovery of `#[page]`, `#[layout]`, `#[route]`, and `#[layer]` items without explicit registration.
- **`segment!`** overrides allow precise control over segment types (`Param`, `CatchAll`, etc.) and must be declared before resource discovery.
- Safety checks prevent duplicate layout paths and enforce correct macro ordering, failing at compile time rather than runtime.

## Frequently Asked Questions

### How does Topcoat convert module names to URL paths?

Topcoat uses the **`module_path_to_path`** function in [`crates/topcoat-router/src/module/router.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-router/src/module/router.rs) to strip the root module prefix, walk each `::`-separated component, and apply transformation rules. Static segments are converted to kebab-case, while group segments (marked with a leading underscore) are omitted from the final URL but preserved in the internal path structure.

### What is the difference between Static and Group segments?

**Static** segments appear directly in the URL (e.g., `blog_posts` becomes `blog-posts`). **Group** segments (created by prefixing a module name with an underscore) organize your code hierarchically but are stripped from the URL pattern during the `to_matchit_path` conversion, allowing you to nest modules without creating nested URL paths.

### Can I use dynamic parameters in module-based routing?

Yes. Use the **`segment!`** macro to override a module's default `SegmentKind` with `Param` or `CatchAll`. For example, declaring `segment! { "app::users::id" => kind: Param }` converts that module into a URL parameter `{id}`, enabling routes like `/users/123` to map to the `app::users::id` module.

### When should I use manual registration versus automatic discovery?

Use **automatic discovery** (the `.discover()` method) for convention-over-configuration applications where your file structure matches your URL structure. Use **manual registration** when you need to conditionally include modules, apply runtime logic to route construction, or when working with the `segment!` macro to inject specific `Segment` configurations before the discovery phase runs.