Specta LogoSpecta
0.3.0 (deprecated)Alpha (1.0.0-rc.x)Server

This documentation is deprecated. It covers rspc 0.3.0, which is no longer maintained.

Read the latest docs

Basics

Read the archived alpha notes without mixing incompatible APIs.

These alpha pages were early drafts. Some examples used a proposed Rspc API that is not exported by the released 0.3.0 crate. For an application pinned to 0.3.0, use the API below and the 0.3.0 quickstart.

Router and bindings

use rspc::{Config, Router};

fn router() -> Router<()> {
    Router::new()
        .config(Config::new().export_ts_bindings("./bindings.ts"))
        .query("version", |t| t(|_: (), _: ()| env!("CARGO_PKG_VERSION")))
        .build()
}

The configuration exports bindings when the router is built in debug mode. Build the router in a test to catch naming and export failures before starting the server.

Transports

The 0.3.0 Axum integration accepts an Arc<Router<TCtx>>. It creates the request context through a callback. Do not pass this router to the experimental rewrite's adapter, which accepts Procedures<TCtx> instead.

Context

Context holds shared resources and request-specific state. It does not have to be an immutable reference. An owned struct with Arc fields is a common choice:

#[derive(Clone)]
struct Ctx {
    message: std::sync::Arc<String>,
}

let router = Router::<Ctx>::new()
    .query("message", |t| t(|ctx: Ctx, _: ()| (*ctx.message).clone()))
    .build();

Construct it in the transport callback. Use interior mutability only for state that actually needs mutation.

Queries and mutations

Queries read data. Mutations perform actions with side effects. Both take a context and one input value:

let router = Router::<()>::new()
    .query("echo", |t| t(|_: (), input: String| input))
    .mutation("greet", |t| t(|_: (), name: String| async move {
        format!("Hello, {name}!")
    }))
    .build();

Use a struct for several input fields. Inputs need Serde Deserialize and Specta Type; outputs need Serialize and Type.

Subscriptions

A subscription resolver returns a Stream. Add futures for this example:

let router = Router::<()>::new()
    .subscription("numbers", |t| {
        t(|_: (), _: ()| futures::stream::iter([1_u32, 2, 3]))
    })
    .build();

The adapter and frontend transport must support subscriptions. For Axum, enable the adapter's ws feature and use WebsocketTransport.

Merge routers

let users = Router::<()>::new()
    .query("list", |t| t(|_: (), _: ()| Vec::<String>::new()));

let router = Router::<()>::new()
    .merge("users.", users)
    .build();

Call the merged procedure as users.list. Merge the child builder before calling build on the parent.

Custom types and errors

Derive the Serde and Specta traits on payload structs. For fallible resolvers, return Result<T, rspc::Error> and map application failures to an ErrorCode and a safe public message. See error handling.

Middleware

Version 0.3.0 attaches middleware to the router. Middleware affects procedures registered after it and can change their context type. See middleware for authentication and logging examples.

Avoid captured resources

Prefer reading a database handle from context to capturing it in a resolver closure. This keeps router construction independent of external services and makes it possible to validate and export the router in a test.

Edit on GitHub

Last updated on

On this page