npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

typed-rpc

v5.0.2

Published

Lightweight JSON-RPC solution for TypeScript projects

Downloads

6,105

Readme

typed-rpc

npm bundle size

Lightweight JSON-RPC solution for TypeScript projects that comes with the following features and non-features:

  • 👩‍🔧 Service definition via TypeScript types
  • 📜 JSON-RPC 2.0 protocol
  • 🕵️ Full IDE autocompletion
  • 🪶 Tiny footprint (< 1kB)
  • 🚚 Support for custom transports
  • 🌎 Support for Deno and edge runtimes
  • 🚫 No code generation step
  • 🚫 No dependencies
  • 🚫 No batch requests
  • 🚫 No runtime type-checking
  • 🚫 No IE11 support
  • 🥱 No fancy project page, just this README

Philosophy

The philosophy of typed-rpc is to strictly focus on the core functionality and keep things as simple as possible. The whole library basically consists of two files, one for the client and one for the server.

You won't find any unnecessarily complex concepts like middlewares, adapters, resolvers, transformers, queries or mutations.

(If you want queries and mutations though, we've got you covered.)

And instead of having adapters for all the different servers, meta frameworks or edge runtimes, we provide a generic package that is request/response agnostic and leave the wiring up to the user.

Basic Usage

Create a service in your backend and export its type, so that the frontend can access type information:

// server/myService.ts

export const myService = {
  hello(name: string) {
    return `Hello ${name}!`;
  },
};

export type MyService = typeof myService;

[!TIP] The functions in your service can also be async.

Create a server with a route to handle the API requests:

// server/index.ts

import express from "express";
import { rpcHandler } from "typed-rpc/express";
import { myService } from "./myService.ts";

const app = express();
app.use(express.json());
app.post("/api", rpcHandler(myService));
app.listen(3000);

[!NOTE] You can also use typed-rpc in servers other than Express. Check out the docs below for examples.

On the client-side, import the shared type and create a typed rpcClient with it:

// client/index.ts

import { rpcClient } from "typed-rpc";

// Import the type (not the implementation!)
import type { MyService } from "../server/myService";

// Create a typed client:
const client = rpcClient<MyService>("http://localhost:3000/api");

// Call a remote method:
console.log(await client.hello("world"));

That's all it takes to create a type-safe JSON-RPC API. 🎉

Demo

You can play with a live example over at StackBlitz:

Open in StackBlitz

Advanced Usage

Accessing the incoming request

Sometimes it's necessary to access the request object inside the service. A common pattern is to define the service as class and create a new instance for each request:

export class MyServiceImpl {
  /**
   * Create a new service instance for the given request headers.
   */
  constructor(private headers: Record<string, string | string[]>) {}

  /**
   * Echo the request header with the specified name.
   */
  async echoHeader(name: string) {
    return this.headers[name.toLowerCase()];
  }
}

export type MyService = typeof MyServiceImpl;

Then, in your server, pass a function to rpcHandler that creates a service instance with the headers taken from the incoming request:

app.post(
  "/api",
  rpcHandler((req) => new MyService(req.headers))
);

Sending custom headers

A client can send custom request headers by providing a getHeaders function:

const client = rpcClient<MyService>({
  url: "http://localhost:3000/api",
  getHeaders() {
    return {
      Authorization: auth,
    };
  },
});

[!TIP] The getHeaders function can also be async.

Aborting requests

You can abort requests by passing the Promise to .$abort() like this:

const client = rpcClient<HelloService>(url);

const res = client.hello("world");
client.$abort(res);

Error handling

In case of an error, the client will throw an RpcError instance that has a message, code and optionally a data property.

When the service throws an error, these properties will be serialized to the client.

For internal errors (invalid request, method not found) the error code is set according to the specs.

CORS credentials

To include credentials in cross-origin requests, pass credentials: 'include' as option.

Custom transport

By default, the client uses the global fetch implementation to perform requests. If you want to use a different mechanism, you can specify custom transport:

const client = rpcClient<MyService>({
  transport: async (req: JsonRpcRequest, abortSignal: AbortSignal) => {
    return {
      error: null,
      result: {
        /* ... */
      },
    };
  },
});

Support for other runtimes

The generic typed-rpc/server package can be used with any server framework or (edge-) runtime.

Fastify

With Fastify, you would use typed-rpc like this:

import { handleRpc, isJsonRpcRequest } from "typed-rpc/server";

fastify.post("/api", async (req, reply) => {
  if (isJsonRpcRequest(req.body)) {
    const res = await handleRpc(req.body, new Service(req.headers));
    reply.send(res);
  }
});

Open in StackBlitz

Deno

🦕 You can also use typed-rpc in Deno like in this example.

[!NOTE] This package is also published under https://deno.land/x/typed_rpc

Next.js

Here's an example that uses typed-rpc in a Next.js project:

Open in StackBlitz

Clodflare Workers

In a Cloudflare Worker you can use typed-rpc like this:

import { handleRpc } from "typed-rpc/server";
import { myService } from "./myService";

export default {
  async fetch(request: Request) {
    const json = await request.json();
    const data = await handleRpc(json, myService);
    return new Response(JSON.stringify(data), {
      headers: {
        "content-type": "application/json;charset=UTF-8",
      },
    });
  },
};

Runtime type checking

[!WARNING] Keep in mind that typed-rpc does not perform any runtime type checks.

This is usually not an issue, as long as your service can handle this gracefully. If you want, you can use a library like type-assurance to make sure that the arguments you receive match the expected type.

React hooks

While typed-rpc itself does not provide any built-in UI framework integrations, you can pair it with react-api-query, a thin wrapper around TanStack Query. A type-safe match made in heaven. 💕

License

MIT