arpc

Want a framework for simple, standardized RPC across different client languages for your TypeScript project?

Supports most TS web frameworks

arpc supports the following frameworks on the server side, allowing you to use the same TypeScript codebase you did before to manage your entire application lifecycle. It deeply integrates with your code structure, so it shouldn't feel like a burden to use:

  • Next
  • Nuxt
  • SvelteKit
  • SolidStart

In any supported framework, setting up the project is as simple as running npx arpc init (or bunx for bun). After that, everything in this framework is the same regardless of the web framework you are using.

Scaffolding

arpc supports scaffolding authentication/ratelimiting easily, allowing you to build your authentication/ratelimiting files once, and then use them throughout your entire application with the CLI scaffolding new methods based on it:

$ npx arpc scaffold authentication
export async function validate(token: string, tokenType: TokenTypes) {
    // TODO: Return your user here.
    return null;
}
// on your methods...
...(arg: v.InferOutput<typeof input>, user: UserExport): ...

Consistent, Beautiful Client SDK's

arpc generates beautiful, consistent client SDK's for your application. These SDK's are generated from the same interfaces you define on your server, so they are always consistent with your local API structure:

const client = new APIV1Client();

const user = await client.users.get(1);
// ^ a user object is returned

const exists = await client.users.exists(1);
// ^ a boolean is returned
client = APIV1Client()

user = client.users.get(1)
# ^ a user object is returned, fully typed

exists = client.users.exists(1)
# ^ a boolean is returned, fully typed
$client = new APIV1Client();

$user = $client->users->get(1);
// ^ a user object is returned, fully typed

$exists = $client->users->exists(1);
// ^ a boolean is returned, fully typed

The SDK's automatically allow both basic types for when you do not need a large struct, and complex types based off your server schema when you do.

Simple CLI

arpc has a simple CLI that allows you to generate clients in many languages, control your applications structure, and even lint for breaking changes in your API version. Everything from setting up your project to bumping your API version, marking a breaking change, and creating the scaffold for a new method is done through the CLI.

$ npx arpc versions bump
✔  API bumped to v2.
$ npx arpc methods create users.get
✔  Method created.
$ npx arpc methods break users.get
✔  Method prepared for breaking change.
$ npx arpc generate typescript https://your.api.com
✔  Client generated.

When you setup arpc, a GitHub Action is created that will automatically lint for breaking changes in your pull requests against main.

Atomicity

arpc supports atomic functions and you can use hooks inside the functions to handle them:

// inside your RPC service...
const tx = useDatabaseTransaction(db.transaction);
// ^ takes a function or Promise that returns a object with a `rollback` and `commit` method.

If any of the functions inside the atomic block fail, the transaction will roll back. There are several other hooks, see the docs for more.

Atomics also have a nice to use and clean API on clients. See atomic clients:

const [, posts] = await client.atomic((client, { variable, eq, pluck }) => {
    const resultVar = variable<{ userId: string }>("resultVar");

    return [
        // Call users.changeSignature and put the result into resultVar - if this fails, it will roll back.
        resultVar.set(
            client.users.changeSignature({
                signature: "Hello, world!",
            }),
        ),

        // Call posts.getAll with the userId from resultVar
        client.posts.getAll({
            userId: pluck(resultVar, "userId"),
        }),
    ] as const;
});

Stacked APIs

When we make a breaking change to an API, we should push a new release. This is obvious, but yet so many people forget to do this. It's an easy mistake to make but with potentially application breaking consequences.

The way this works is say you have an API V1 which has a bunch of functions. You probably don't want to have to manually copy each API, so instead when you call arpc versions bump, it will go ahead and create a stack. This means that when you want to break a method within your API, you can use arpc methods break my.function, and it will go ahead and create a copy (in this example, to rpc/routes/<version>/my/function.ts).

This also makes review much easier because the expectation of when the new files will be created is very clear.