# How the llmfit Axum Server Routes Between the React Dashboard and `/api/v1/` JSON Endpoints

> Discover how the llmfit Axum server routes between the React dashboard and api v1 JSON endpoints. Learn about nesting routes and client-side routing support.

- Repository: [Alex Jones/llmfit](https://github.com/AlexsJones/llmfit)
- Tags: architecture
- Published: 2026-09-11

---

**The llmfit TUI embeds a React SPA and mounts it at the root path while nesting JSON API routes under `/api/v1/` using Axum’s `nest()` method, with a fallback handler that rewrites unknown paths to [`index.html`](https://github.com/AlexsJones/llmfit/blob/main/index.html) to support client-side routing.**

The `llmfit` project by AlexsJones demonstrates a unified web architecture where a single Rust binary serves both a React-based management dashboard and a programmatic JSON API. According to the source code in [`llmfit-tui/src/serve_api.rs`](https://github.com/AlexsJones/llmfit/blob/main/llmfit-tui/src/serve_api.rs), the application uses Axum’s composable routing to distinguish between static asset requests and API calls, eliminating the need for a separate web server or reverse proxy.

## Mounting the `/api/v1/` JSON API Routes

The programmatic interface is isolated under the `/api/v1/` prefix using **Axum’s `nest()` method**. In [`llmfit-tui/src/serve_api.rs`](https://github.com/AlexsJones/llmfit/blob/main/llmfit-tui/src/serve_api.rs), the code constructs a sub-router dedicated to API endpoints and mounts it at the specified path.

The API router defines concrete handlers for hardware inspection, model management, and execution planning:

```rust
// llmfit-tui/src/serve_api.rs
let api_router = Router::new()
    .route("/hardware", get(handlers::hardware))
    .route("/models", get(handlers::models))
    .route("/plan", post(handlers::plan));

let app = Router::new()
    .nest("/api/v1", api_router);  // Mounts API routes under /api/v1/*

```

Each handler in this sub-router invokes functions from the `llmfit-core` library and returns structured JSON via `axum::Json<T>`. This separation ensures that all programmatic endpoints share a common prefix and can apply uniform middleware, such as authentication or logging, at the `nest` boundary.

## Serving the React Dashboard with Embedded Static Assets

The React frontend is embedded directly into the binary at compile time using the `include_dir!` macro. In [`llmfit-tui/src/main.rs`](https://github.com/AlexsJones/llmfit/blob/main/llmfit-tui/src/main.rs), the static assets from `llmfit-web/dist` are compiled into the executable, allowing the Axum server to serve the dashboard without filesystem dependencies.

```rust
// llmfit-tui/src/main.rs
use include_dir::{Dir, include_dir};

static STATIC_ASSETS: Dir = include_dir!("$CARGO_MANIFEST_DIR/llmfit-web/dist");

```

To serve these assets, the router uses **tower-http’s `ServeDir`** service. A fallback route captures any unmatched paths and rewrites them to [`index.html`](https://github.com/AlexsJones/llmfit/blob/main/index.html), enabling the React router to handle client-side navigation:

```rust
// llmfit-tui/src/serve_api.rs
use tower_http::services::ServeDir;

let app = Router::new()
    .nest("/api/v1", api_router)
    .fallback_service(
        get_service(ServeDir::new(STATIC_ASSETS.path()))
            .handle_error(|e| async move {
                (StatusCode::INTERNAL_SERVER_ERROR, format!("Error: {}", e))
            })
    );

```

This configuration ensures that requests to `/dashboard` or `/settings`—which do not match any API route—return the React [`index.html`](https://github.com/AlexsJones/llmfit/blob/main/index.html) file rather than a 404 error.

## Combining Routes in the Application Router

The final router composition occurs in [`serve_api.rs`](https://github.com/AlexsJones/llmfit/blob/main/serve_api.rs), where the API sub-router and the static file service are merged into a single `axum::Router` instance. The `nest()` method prefixes all API routes, while the `fallback_service()` handles all non-API traffic.

```rust
// Simplified structure from llmfit-tui/src/serve_api.rs
let api_router = Router::new()
    .route("/hardware", get(hardware::handler))
    .route("/models", get(models::handler))
    .route("/plan", post(plan::handler));

let app = Router::new()
    .nest("/api/v1", api_router)  // JSON endpoints
    .fallback_service(
        get_service(ServeDir::new(STATIC_ASSETS.path()))
            .handle_error(|e| async move {
                (StatusCode::INTERNAL_SERVER_ERROR, format!("Server error: {}", e))
            })
    );

```

When the server starts, `axum::Server::bind()` listens on the configured address and dispatches incoming requests according to this hierarchy: exact matches for `/api/v1/*` go to the API handlers, while all other requests trigger the static file service or the fallback to [`index.html`](https://github.com/AlexsJones/llmfit/blob/main/index.html).

## Summary

- **Unified Binary**: The `llmfit-tui` crate combines a React SPA and JSON API in a single Axum server, embedding static assets with `include_dir!` to eliminate runtime file dependencies.
- **Path Prefixing**: All programmatic endpoints are mounted under `/api/v1/` using `Router::nest()`, creating a clean separation between API and UI routes.
- **Client-Side Routing Support**: A fallback service rewrites unknown paths to [`index.html`](https://github.com/AlexsJones/llmfit/blob/main/index.html), allowing the React dashboard to manage its own navigation while the server handles only data endpoints.
- **Source Locations**: Routing logic resides in [`llmfit-tui/src/serve_api.rs`](https://github.com/AlexsJones/llmfit/blob/main/llmfit-tui/src/serve_api.rs), while asset embedding occurs in [`llmfit-tui/src/main.rs`](https://github.com/AlexsJones/llmfit/blob/main/llmfit-tui/src/main.rs).

## Frequently Asked Questions

### How does the llmfit server distinguish between API requests and dashboard requests?

The server uses **Axum’s `nest()` method** to isolate all JSON endpoints under the `/api/v1/` prefix. Any request path starting with `/api/v1/` routes to the dedicated API sub-router defined in [`llmfit-tui/src/serve_api.rs`](https://github.com/AlexsJones/llmfit/blob/main/llmfit-tui/src/serve_api.rs). All other requests fall through to the `ServeDir` static file handler or the [`index.html`](https://github.com/AlexsJones/llmfit/blob/main/index.html) fallback, ensuring the React dashboard receives control of the browser routing.

### Why does the server embed the React build instead of using an external directory?

The project uses the `include_dir!` macro to embed `llmfit-web/dist` directly into the compiled binary at build time. This approach creates a **self-contained executable** that does not require external static asset directories, simplifying deployment and ensuring the dashboard version remains synchronized with the server binary.

### How does the Axum router handle React client-side routing?

The router includes a **fallback service** configured with `ServeDir` that rewrites any unmatched paths to [`index.html`](https://github.com/AlexsJones/llmfit/blob/main/index.html). When a user navigates to a client-side route like `/dashboard/models`, the server returns the React application bundle, and the browser-side React Router interprets the path and renders the appropriate component without additional server configuration.

### What endpoints are available under the `/api/v1/` prefix?

According to the router definition in [`serve_api.rs`](https://github.com/AlexsJones/llmfit/blob/main/serve_api.rs), the API exposes endpoints such as `GET /api/v1/hardware` for system specifications, `GET /api/v1/models` for available LLM configurations, and `POST /api/v1/plan` for generating execution plans. These handlers reside in the `llmfit-core` library and return JSON responses consumed by the React dashboard or external tools.