Overview

rspc

rspc is a typesafe router which allows you to build APIs in Rust which have end to end typesafey. You define your logic in a Rust function on your backend and use the React hooks or Typescript client to call it. Your rspc router is transport agnostic which means you can serve it from any HTTP server of your choice such as Axum (opens in a new tab) or even from Tauri (opens in a new tab).

Why rspc?

  • Typesafety - allows your team to move faster and eliminate a whole class of common bugs
  • Developer experience - you define a function in Rust and can call it from the frontend with no extra magic
  • Minimal runtime - small runtime footprint so your can get the full potential of Rust's speed
  • Middleware - For easily extending your procedures with auth, logging and more
  • 🚧 Work in progress - rspc is still early in development. It's stable but you may find use cases which aren't well supported.

You get the type safety of GraphQL (opens in a new tab) without the complexity of it.

Let's skip the talk and get to the code. Let's start with the Rust side.

src/main.rs
use rspc::Router;
 
// We define the rspc Router
let router = Router::<MyCtx>::new()
    // Then we define a new query called "version" which takes no arguments (`()`) and returns "1.0.0"
    .query("version", |t| t(|ctx, args: ()| "1.0.0"))
    // Finally we define a mutation called "createUser" which takes a user's name as a `String` and returns a `User`.
    .mutation("createUser", |t| {
        t(|ctx, name: String| async move {
            ctx.db
                .post()
                .find_many(vec![])
                .exec()
                .await?
                .map_err(Into::into) // Result<User, Error>
        })
    })
    // You can stack as many procedures as you want!
    .build();
 
// Finally we export the typescript bindings from the router.
router.export_ts("./bindings.ts").unwrap();

When you run this code it will create a bindings.ts file which will look similar to the following.

bindings.ts
// This file was generated by [rspc](https://github.com/oscartbeaumont/rspc). Do not edit this file manually.
 
export type Procedures = {
  queries: { key: "version"; input: never; result: string };
  mutations: { key: "createUser"; input: string; result: User };
  subscriptions: never;
};
 
export interface User {
  name: string;
}

Now that we have these Typescript bindings we can use them to call the API on the frontend in a typesafe manner.

src/App.tsx
import type { Procedures } from "./bindings.ts"; // These are the bindings exported from your Rust code!
 
const rspc = createReactQueryHooks<Procedures>();
 
function MyReactApp() {
  // If you've used Tanstack Query this will look familiar because it's using it under the hood.
  const { data: version } = rspc.useQuery("version");
  const { mutate: createUser } = rspc.useMutation("createUser");
 
  // This would throw a Typescript error because the type of version is inferred to be a string from the Rust code.
  let x: number = version;
 
  return (
    <div>
      <h1>rspc</h1>
      <p>Version: {version}</p>
 
      <button onClick={() => createUser("Monty Beaumont")}>Create User!</button>
    </div>
  );
}

If you still confused about what rspc is check out Brendan (opens in a new tab) explain it in caveman terms in this clip (opens in a new tab).

Production users

Inspiration

This project is based off trpc (opens in a new tab) and was inspired by the bridge system Jamie Pine (opens in a new tab) designed for Spacedrive (opens in a new tab). A huge thanks to everyone who helped inspire this project!