# How to Use the #[procedure] Macro for Async Server Functions in Tokio Topcoat

> Learn to use the #[procedure] macro in Tokio Topcoat to easily expose async Rust functions as server endpoints callable from client applications.

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

---

**The `#[procedure]` macro transforms async Rust functions into server-side endpoints that client-side runtime expressions can invoke over the network.**

The `#[procedure]` macro in the `tokio-rs/topcoat` repository bridges the gap between server-side Rust logic and browser-based interactions. By annotating an async function with this macro, you expose it as a callable route on the application's `Router`, enabling seamless client-server communication through Topcoat's reactive view layer.

## Understanding the #[procedure] Macro

In [`crates/topcoat-runtime/src/procedure.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-runtime/src/procedure.rs), the `#[procedure]` macro generates the boilerplate necessary to register async functions as HTTP endpoints. When applied to a function, it implements the `Procedure` trait and creates the serialization glue required to translate between JavaScript values and Rust types.

The macro integrates with Topcoat's router system defined in [`crates/topcoat-router/src/router.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-router/src/router.rs). Procedures become accessible to the client-side `expr!` runtime, which handles the network request and deserialization automatically.

## Defining Server Procedures

### Basic Function Signatures

Procedure functions must be async and return a `Result<T, E>` where the `Ok` variant contains types compatible with Topcoat's shared vocabulary. According to the documentation in [`crates/topcoat-runtime/macro/docs/procedure.md`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-runtime/macro/docs/procedure.md), supported types include primitives, strings, and collections that serialize to JavaScript.

```rust
use topcoat::{Result, runtime::procedure};

#[procedure]
async fn double(value: f64) -> Result<f64> {
    Ok(value * 2.0)
}

```

### The Request Context (cx) Parameter

You can add a special `cx` parameter of type `&Cx` to access request-scoped data. This argument is injected by the server and excluded from the client-side signature. The `Cx` type provides access to authentication state, session data, and application context.

```rust
use topcoat::{Result, context::Cx, runtime::procedure};

#[procedure]
async fn search(cx: &Cx, query: String) -> Result<String> {
    // Access database or auth info through cx
    Ok(query)
}

```

## Registering Procedures with the Router

### Automatic Discovery

The router discovers procedures automatically when you call `.discover()` on the `Router` builder. This method walks the binary to find all linked procedures marked with `#[procedure]` and mounts them as routes.

### Manual Registration

For explicit control, use `RouterBuilderProcedureExt::procedure` to mount individual functions. In [`crates/topcoat-router/src/router.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-router/src/router.rs), this extension trait adds the `.procedure()` method to the builder pattern.

```rust
use topcoat::{Result, router::Router, runtime::{procedure, RouterBuilderProcedureExt}};

#[procedure]
async fn greet(name: String) -> Result<String> {
    Ok(format!("Hello, {name}!"))
}

let router = Router::builder()
    .procedure(greet)
    .build();

```

## Calling Procedures from the Client

### Runtime Expressions (expr!)

Client-side code invokes procedures inside runtime expressions defined by the `expr!` macro (documented in [`crates/topcoat-runtime/macro/docs/expr.md`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-runtime/macro/docs/expr.md)). These expressions execute in an async context, allowing you to `.await` the procedure call.

```rust
use topcoat::{Result, view::*, runtime::procedure};

#[component]
async fn counter() -> Result {
    view! {
        signal count = 1.0;

        <button @click=$(async |_e| {
            let doubled = double(count.get()).await;
            count.set(doubled);
        })>
            "double it"
        </button>

        $(count.get())
    }
}

```

### Handling Async Calls

Because procedure calls traverse the network, they must occur within an async block. The `view!` macro (documented in [`crates/topcoat-view/macro/docs/view.md`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-view/macro/docs/view.md)) handles the reactive updates when the awaited call resolves.

## Error Handling and Security

### Result Types and Error Propagation

When a procedure returns `Ok(v)`, the awaiting expression resolves to `v`. If it returns `Err(e)`, the server responds with an error status and the client-side expression aborts without returning a value. To expose errors to the UI, return a `Result<T, E>` as the success type instead of propagating errors through the `?` operator.

### Security Considerations with cx

Never trust procedure arguments directly, as clients can spoof them. Always validate inputs and use the `cx` parameter to verify authentication or authorization before performing privileged operations. The request context provides the trusted source of identity and session state.

## Summary

- The `#[procedure]` macro in `tokio-rs/topcoat` exposes async Rust functions as HTTP endpoints callable from browser-side code.
- Functions must return `Result<T, E>` with serializable types, and may accept an optional `cx: &Cx` parameter for request context.
- Register procedures via automatic `.discover()` or manual `.procedure()` on the `Router` builder.
- Call procedures from within `expr!` runtime expressions using standard `.await` syntax inside async blocks.
- Handle errors by returning them as data rather than using `?`, and always verify security through the `cx` context rather than trusting client arguments.

## Frequently Asked Questions

### What types can I use as arguments to a #[procedure] function?

Arguments and return values must belong to Topcoat's shared vocabulary that serializes to JavaScript. This includes primitives like `f64`, `String`, and standard collections. Complex types must implement the necessary serialization traits to cross the JavaScript/WebAssembly boundary.

### How do I access database connections inside a procedure?

Use the `cx: &Cx` parameter to access application context. Inside the function, call methods on `cx` to retrieve request-scoped resources like database pools or connection handles. This parameter is automatically injected by the server and is not part of the client-side call signature.

### What happens if the procedure returns an error?

If the procedure returns `Err(e)`, the server sends an error response, and the client-side `await` aborts without producing a value. The expression stops executing. To handle errors gracefully in the UI, return a `Result<T, E>` as the `Ok` variant and pattern match on the client side.

### Can I use #[procedure] with synchronous functions?

No. The `#[procedure]` macro requires async functions because it handles concurrent request processing and non-blocking I/O. If you have synchronous logic, wrap it in an async block or function when defining the procedure endpoint.