# How Dioxus Server Functions Work: Full-Stack Rust Development Explained

> Learn how Dioxus Server Functions enable type-safe full-stack Rust development. Explore the #[server] macro compilation for server handlers and client stubs.

- Repository: [Dioxus Labs/dioxus](https://github.com/DioxusLabs/dioxus)
- Tags: deep-dive
- Published: 2026-07-23

---

**Dioxus Server Functions use a `#[server]` procedural macro to compile a single async function into both an Axum HTTP handler that runs on the server and a client-side stub that serializes calls over `fetch`, enabling type-safe full-stack development with automatic route registration.**

Dioxus Server Functions bridge the gap between frontend and backend code in the DioxusLabs/dioxus ecosystem, allowing developers to write server logic once and invoke it from the browser as if it were a local async function. By leveraging procedural macros and compile-time code generation, the framework eliminates the need for manual API endpoint creation while maintaining type safety across the network boundary. This architecture centers on the `#[server]` attribute, which transforms annotated functions into a coordinated server handler and client SDK.

## How the #[server] Macro Expands Your Code

When you attach the `#[server]` attribute to an async function, the procedural macro defined in [`packages/fullstack-macro/src/lib.rs`](https://github.com/DioxusLabs/dioxus/blob/main/packages/fullstack-macro/src/lib.rs) performs a compile-time transformation. It expands your single function definition into two distinct implementations: a server-side HTTP handler and a client-side invocation stub.

### Server-Side Handler Generation

On the server, the macro generates an `axum::MethodRouter` that acts as the HTTP endpoint. According to the source in [`packages/fullstack-server/src/serverfn.rs`](https://github.com/DioxusLabs/dioxus/blob/main/packages/fullstack-server/src/serverfn.rs), the generated code creates a `ServerFunction` struct instance that captures the route path, HTTP method, and handler closure.

The handler performs three critical operations:

1. **Context Extraction**: It builds a `FullstackContext` from the incoming request parts using `FullstackContext::new(parts)`, allowing access to cookies, headers, and other server-specific resources.
2. **Scoped Execution**: It runs your original function body inside `FullstackContext::scope`, which provides a structured way to extract typed arguments using `FullstackContext::extract`.
3. **Response Construction**: It serializes the return value to JSON and merges any response headers set during execution, with special handling for HTML form POSTs that may trigger a `302 Found` redirect to the `Referer`.

The handler runs inside a **Tokio `LocalPool`** specifically to support non-`Send` futures, meaning you can use thread-local state like `use_signal` inside server functions without requiring `Send` bounds across the entire server.

### Client-Side Stub Generation

On the client, the macro generates a stub function with the identical signature to your original. This stub:

- Serializes arguments to JSON using `serde_json`
- Issues an HTTP `fetch` request to the generated endpoint (mounted under `/__dioxus_server/<path>` by default)
- Deserializes the JSON response and returns it as a `Future` that resolves to `Result<T, ServerFnError>`

This creates the illusion of a local function call while performing network communication under the hood.

## The ServerFunction Registry and Route Mounting

The macro automatically registers every server function using the `inventory` crate. As shown in [`packages/fullstack-server/src/serverfn.rs`](https://github.com/DioxusLabs/dioxus/blob/main/packages/fullstack-server/src/serverfn.rs), the `ServerFunction` struct provides a constant constructor and a collection mechanism:

```rust
pub struct ServerFunction {
    path: &'static str,
    method: Method,
    handler: fn() -> MethodRouter<FullstackState>,
}

impl ServerFunction {
    pub const fn new(method: Method, path: &'static str, handler: fn() -> MethodRouter<FullstackState>) -> Self { … }
    
    pub fn collect() -> Vec<&'static ServerFunction> {
        inventory::iter::<ServerFunction>().collect()
    }
}

```

When the fullstack server starts (implemented in [`packages/fullstack-server/src/server.rs`](https://github.com/DioxusLabs/dioxus/blob/main/packages/fullstack-server/src/server.rs)), it calls `ServerFunction::collect()` to retrieve all registered functions and mounts each `MethodRouter` onto the main `axum::Router`. This eliminates manual route registration—simply defining a server function makes it available as an HTTP endpoint.

## Request Handling Inside FullstackContext

During request processing, the generated handler utilizes `FullstackContext` to manage the execution environment. The context wraps the request parts and provides a scoped execution model where your function code runs. When you call `FullstackContext::extract`, the system deserializes the request body (typically JSON) into your function's argument types.

The handler also manages response modifications. If your server function sets headers or returns specific data, these merge into the final `axum::Response`. For traditional server-rendered form submissions, the system supports automatic redirects back to the referring page, maintaining compatibility with classic web form behavior while still providing the modern RPC-style API.

## Why Local Pool Execution Matters

A distinctive feature of Dioxus Server Functions is their use of a **local task pool** via `LocalPool`. This design choice allows server functions to contain `!Send` futures—futures that are not thread-safe or contain thread-local data. In practice, this means you can use Dioxus signals and other non-`Send` types directly within server function bodies without encountering compilation errors about `Send` bounds, which would typically be required for standard Axum handlers that must be `Send`.

## Practical Implementation Example

Consider a simple greeting function defined using the `#[server]` macro:

```rust
#[server]
async fn greet(name: String) -> String {
    format!("Hello, {name}!")
}

```

The macro expansion generates the server-side registration:

```rust
pub const __ENDPOINT_PATH: &str = "/greet";

pub fn __router() -> MethodRouter<FullstackState> {
    ServerFunction::make_handler(Method::POST, |state, req| {
        Box::pin(async move {
            let (name,) = FullstackContext::extract::<(String,)>(state).await?;
            let result = greet(name).await;
            Response::builder()
                .status(StatusCode::OK)
                .header("content-type", "application/json")
                .body(Body::from(serde_json::to_string(&result)?))
                .unwrap()
        })
    })
}

inventory::submit! {
    ServerFunction::new(Method::POST, __ENDPOINT_PATH, __router)
}

```

And the client-side stub:

```rust
pub async fn greet(name: String) -> Result<String, ServerFnError> {
    let payload = serde_json::to_string(&(name,))?;
    let resp = dioxus_fullstack::fetch(__ENDPOINT_PATH, payload).await?;
    let json: String = serde_json::from_slice(&resp.body)?;
    Ok(json)
}

```

You can then call this function directly from a Dioxus component:

```rust
#[component]
fn Hello() -> Element {
    let greeting = use_resource(|| async move { greet("World".into()).await });
    rsx! {
        match *greeting.read() {
            Some(Ok(msg)) => p { "{msg}" },
            Some(Err(e)) => p { "Error: {e}" },
            None => p { "Loading…" },
        }
    }
}

```

## Summary

- **Dioxus Server Functions** compile a single `#[server]`-annotated function into both a server handler and client stub.
- The **server side** uses `axum::MethodRouter` and `FullstackContext` to handle HTTP requests, running user code in a `LocalPool` to support `!Send` futures.
- The **client side** generates a type-safe stub that serializes arguments to JSON and deserializes responses over HTTP.
- **Automatic registration** via `inventory::collect!` eliminates manual route setup—functions self-register and mount when the server starts.
- **FullstackContext** provides scoped access to request data, cookies, and headers, with built-in support for form redirects and response header manipulation.

## Frequently Asked Questions

### What crate implements the #[server] macro in Dioxus?

The `#[server]` procedural macro is implemented in [`packages/fullstack-macro/src/lib.rs`](https://github.com/DioxusLabs/dioxus/blob/main/packages/fullstack-macro/src/lib.rs). This crate handles the compile-time code generation that creates the server-side handler registration and the client-side fetch stub.

### How does Dioxus serialize arguments for server functions?

Arguments are serialized to JSON using `serde_json` on the client side and deserialized within the `FullstackContext::extract` method on the server. This occurs transparently through the generated stub code, maintaining type safety across the network boundary.

### Can server functions access request headers and cookies?

Yes. Server functions run inside a `FullstackContext` which provides access to the raw request parts. You can extract headers, cookies, query parameters, and other HTTP-specific data using `FullstackContext::extract` within the scope of the server function.

### Why must server functions run in a LocalPool instead of a standard Tokio runtime?

The `LocalPool` allows server functions to execute `!Send` futures—futures that contain thread-local data or are not thread-safe. This enables the use of Dioxus signals and other non-`Send` types directly inside server functions, which would otherwise violate the `Send` bounds required by standard Axum handlers.