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

@thegecko/typed-rpc

v6.0.0

Published

Lightweight JSON-RPC solution for TypeScript projects

Downloads

101

Readme

typed-rpc

npm bundle size

Lightweight JSON-RPC solution for TypeScript projects with the following features:

  • 👩‍🔧 Service definition via TypeScript types
  • 📜 JSON-RPC 2.0 protocol
  • 🕵️ Full IDE autocompletion
  • 🪶 Tiny footprint (< 1kB)
  • 🚚 Support for custom transports
  • 🏝️ Optional support for non-JSON types
  • 🌎 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

typed-rpc focuses on core functionality, keeping things as simple as possible. The library consists of just two files: one for the client and one for the server.

You'll find no unnecessary complexities like middlewares, adapters, resolvers, queries, or mutations. Instead, we offer a generic package that is request/response agnostic, leaving the wiring up to the user.

Basic Usage

Server-Side

First, define your typed service. This example shows a simple service with a single method:

// server/myService.ts

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

export type MyService = typeof myService;

Tip: Functions in your service can also be async.

Create a server route to handle API requests:

// server/index.ts

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

const app = express();
app.use(express.json());
app.post("/api", (req, res, next) => {
  handleRpc(req.body, myService)
    .then((result) => res.json(result))
    .catch(next);
});
app.listen(3000);

Note: typed-rpc can be used with servers other than Express. Check out the docs below for examples.

Client-Side

Import the shared type and create a typed rpcClient:

// client/index.ts

import { rpcClient } from "typed-rpc";
import type { MyService } from "../server/myService";

const client = rpcClient<MyService>("/api");

console.log(await client.hello("world"));

Once you start typing client. in your IDE, you'll see all your service methods and their signatures suggested for auto-completion. 🎉

Demo

Play with a live example on StackBlitz:

Open in StackBlitz

Advanced Usage

Accessing the Incoming Request

Define the service as a class to access request headers:

export class MyServiceImpl {
  constructor(private headers: Record<string, string | string[]>) {}

  async echoHeader(name: string) {
    return this.headers[name.toLowerCase()];
  }
}

export type MyService = typeof MyServiceImpl;

Create a new service instance for each request:

app.post("/api", (req, res, next) => {
  handleRpc(req.body, new MyService(req.headers))
    .then((result) => res.json(result))
    .catch(next);
});

Sending Custom Headers

Clients can send custom headers using a getHeaders function:

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

Tip: The getHeaders function can also be async.

Aborting Requests

Abort requests by passing the Promise to client.$abort():

const client = rpcClient<HelloService>(url);

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

Error Handling

In case of an error, the client throws an RpcError with message, code, and optionally data. Customize errors with RpcHandlerOptions or provide an onError handler for logging.

For internal errors (invalid request, method not found), the error code follows the specs.

CORS Credentials

Include credentials in cross-origin requests with credentials: 'include'.

Custom Transport

Use a different transport mechanism by specifying custom transport:

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

Support for Other Runtimes

typed-rpc/server can be used with any server framework or edge runtime.

Fastify

Example with Fastify:

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

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

Open in StackBlitz

Deno

Example with Deno in this repository.

Next.js

Example with Next.js:

Open in StackBlitz

Cloudflare Workers

Example with Cloudflare Workers:

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" },
    });
  },
};

Support for Non-JSON Types

Configure a transcoder like superjson for non-JSON types.

On the client:

import { serialize, deserialize } from "superjson";
const transcoder = { serialize, deserialize };

const client = rpcClient<DateService>({
  url: "/my-date-api",
  transcoder,
});

On the server:

import { serialize, deserialize } from "superjson";
const transcoder = { serialize, deserialize };

handleRpc(json, dateService, { transcoder });

Runtime Type Checks

typed-rpc does not perform runtime type checks. Consider pairing it with type-assurance for added safety.

React Hooks

Pair typed-rpc with react-api-query for UI framework integration.

What's new in v6

  • Services can now expose APIs with non-JSON types like Dates, Maps, Sets, etc. by plugging in a transcoder like superjson.
  • Previously, typed-rpc only shipped a CommonJS build in /lib and Deno users would directily consume the TypeScript code in /src. We now use pkgroll to create a hybrid module in /dist with both .mjs and .cjs files.
  • We removed the previously included express adapter to align with the core philosopy of keeping things as simple as possible.

License

MIT