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

twirp-ts

v2.5.0

Published

Typescript implementation of the Twirp protocol

Downloads

343,043

Readme

Twirp-TS

A complete server and client implementation of the awesome Twirp Specification written in typescript.

Supported spec v7 and v8


npm version Coverage Status

Table of Contents:

Getting Started


Installation

Run the following to install the package

npm i twirp-ts @protobuf-ts/plugin@next -S

or

yarn add twirp-ts @protobuf-ts/plugin@next

Install ts-proto instead if you prefer it over @protobuf-ts

Install Protoc

Make sure you have protoc or buf installed.

Mac:

brew install protobuf

Linux:

apt-get install protobuf

Optional: This plugin works with buf too, follow the link to see how to install it

Code Generation

twirp-ts relies on either protobuf-ts or ts-proto to generate protobuf message definitions

The protoc-gen-twirp_ts is instead used to generate server and client code for twirp-ts

It is as simple as adding the following options in your protoc command

PROTOC_GEN_TWIRP_BIN="./node_modules/.bin/protoc-gen-twirp_ts"

--plugin=protoc-gen-twirp_ts=${PROTOC_GEN_TWIRP_BIN} \
--twirp_ts_out=$(OUT_DIR)

Here's an example working command with the recomended flags:

PROTOC_GEN_TWIRP_BIN="./node_modules/.bin/protoc-gen-twirp_ts"
PROTOC_GEN_TS_BIN="./node_modules/.bin/protoc-gen-ts_proto"

OUT_DIR="./generated"

protoc \
    -I ./protos \
    --plugin=protoc-gen-ts_proto=${PROTOC_GEN_TS_BIN} \
    --plugin=protoc-gen-twirp_ts=${PROTOC_GEN_TWIRP_BIN} \
    --ts_proto_opt=esModuleInterop=true \
    --ts_proto_opt=outputClientImpl=false \
    --ts_proto_out=${OUT_DIR} \
    --twirp_ts_opt="ts_proto" \
    --twirp_ts_out=${OUT_DIR} \
    ./protos/*.proto
PROTOC_GEN_TWIRP_BIN="./node_modules/.bin/protoc-gen-twirp_ts"
PROTOC_GEN_TS_BIN="./node_modules/.bin/protoc-gen-ts"

OUT_DIR="./generated"

protoc \
  -I ./protos \
  --plugin=protoc-gen-ts=$(PROTOC_GEN_TS_BIN) \
  --plugin=protoc-gen-twirp_ts=$(PROTOC_GEN_TWIRP_BIN) \
  --ts_opt=client_none \
  --ts_opt=generate_dependencies \
  --ts_out=$(OUT_DIR) \
  --twirp_ts_out=$(OUT_DIR) \
  ./protos/*.proto
OUT_DIR="./generated"

protoc \
  -I ./protos \
  --plugin=protoc-gen-ts=.\\node_modules\\.bin\\protoc-gen-ts.cmd \
  --plugin=protoc-gen-twirp_ts=.\\node_modules\\.bin\\protoc-gen-twirp_ts.cmd \
  --ts_opt=client_none \
  --ts_opt=generate_dependencies \
  --ts_out=${OUT_DIR} \
  --twirp_ts_out=${OUT_DIR} \
  ./protos/*.proto

If you'd like the plugin to generate an index.ts file exporting all your generated code simply add --twirp_ts_opt="index_file"

Server

Once you've generated the server code you can simply start a server as following:

import * as http from "http";
import {TwirpContext} from "twirp-ts";
import {createHaberdasherServer} from "./generated/haberdasher.twirp";
import {Hat, Size} from "./generated/service";

const server = createHaberdasherServer({
    async MakeHat(ctx: TwirpContext, request: Size): Promise<Hat> {
        // Your implementation
    },
});

http.createServer(server.httpHandler())
    .listen(8080);

Path prefix

By default the server uses the /twirp prefix for every request. You can change or remove the prefix passing the prefix option to the handler

const server = createHaberdasherServer({
    async MakeHat(ctx: TwirpContext, request: Size): Promise<Hat> {
        // Your implementation
    },
});

server.withPrefix("/custom-prefix") // or false to remove it

http.createServer(server.httpHandler())
  .listen(8080);

or you can pass it to the handler directly:

http.createServer(server.httpHandler({
    prefix: "/custom-prefix", 
})).listen(8080);

Integrating with express

If you'd like to use express as your drop in solution to add more routes, or middlewares you can do as following:

const server = createHaberdasherServer({
    async MakeHat(ctx: TwirpContext, request: Size): Promise<Hat> {
        // ... implementation
    },
});

const app = express();

app.post(server.matchingPath(), server.httpHandler());

app.listen(8000);

Note: if you want to change the default prefix use server.withPrefix()

Server Hooks & Interceptors

Link to Spec

Interceptors are a form of middleware for Twirp requests. Interceptors can mutate the request and responses, which can enable some powerful integrations, but in most cases, it is better to use Hooks for observability at key points during a request. Mutating the request adds complexity to the request lifecycle.

Be mindful to not hide too much behind interceptors as with every middleware alike implementation is easy to increase complexity making it harder to reason about.

Example:

const server = createHaberdasherServer({
    // ...
});

async function exampleInterceptor(ctx: TwirpContext, req: any, next: Next) {
    console.log("Before response");

    const response = await next(ctx, req);

    console.log("After response");

    return response;
}

server.use(exampleInterceptor)

Server Hooks They provide callbacks for before and after the request is handled. The Error hook is called only if an error was returned by the handler.

A great place for metrics and logging

const server = createHaberdasherServer({
    // ...
});

const serverHooks: ServerHooks = {
    requestReceived: (ctx) => {
        console.log("Received");
    },
    requestRouted: (ctx) => {
        console.log("Requested");
    },
    responsePrepared: (ctx) => {
        console.log("Prepared");
    },
    responseSent: (ctx) => {
        console.log("Sent");
    },
    error: (ctx, err) => {
        console.log(err);
    }
};

server.use(serverHooks);

Errors

Link to Spec

The library comes with a built in TwirpError which is the default and standard error for all of your errors.

You can certainly create custom errors that extend a TwirpError

For Example:

import {TwirpError, TwirpErrorCode} from "twirp-ts";

class UnauthenticatedError extends TwirpError {
    constructor(traceId: string) {
        super(TwirpErrorCode.Unauthenticated, "you must login");
        this.withMeta("trace-id", traceId)
    }
}

Gateway

The gateway allows to expose custom http endpoints that automatically maps to your twirp handlers.

The mapping is done in your proto file using the google.api.http annotations spec.

Add the annotation

service Haberdasher {
  // MakeHat produces a hat of mysterious, randomly-selected color!
  rpc MakeHat(Size) returns (Hat) {
    option (google.api.http) = {
      post: "/hat"
      body: "*"
    };
  };
}

Generating the gateway

add the following option in your protoc command:

--twirp_ts_opt=gateway

Don't forget to regenerate your proto files.

Gateway Reverse Proxy

Once we generated the gateway we can use it as a stand-alone reverse-proxy server or as a request rewriter.

The following example creates a stand-alone reverse proxy:

import express from 'express';
import {createGateway} from './generated/gateway.twirp.ts';

const app = express();
const gateway = createGateway();

app.use(gateway.reverseProxy({
  baseUrl: "http://localhost:8000/twirp",
}));

app.listen(8001);

Gateway rewriter

If you prefer to have the gateway in the same server as your twirp endpoint and save a round-trip, you'd want to use the rewriter

The rewriter will automatically rewrite the request (once it finds a match) to the corresponded twirp handler

import express from 'express';
import {createGateway} from './generated/gateway.twirp.ts';

const app = express();
const gateway = createGateway();

app.use(gateway.twirpRewrite());

// All your twirp handlers
app.post(server.matchingPath(), server.httpHandler());

app.listen(8001);

Note: make sure the middleware is register before your twirp handlers

Twirp Client

As well as the server you've also got generated client code, ready for you to use. You can choose between JSON client and Protobuf client.

The generated code doesn't include an actual library to make http requests, but it gives you an interface to implement the one that you like the most.

Alternatively you can use the provided implementation based on node http and https package.

For example:

const jsonClient = new HaberdasherClientJSON(NodeHttpRPC({
    baseUrl: "http://localhost:8000/twirp",
}));

const protobufClient = new HaberdasherClientProtobuf(NodeHttpRPC({
    baseUrl: "http://localhost:8000/twirp",
}));

For us in the browser, you can use the provided fetch based implementation,

For example:

export const jsonClient = new HaberdasherClientJSON(FetchRPC({
  baseUrl: "http://localhost:8000/twirp",
}));
export const protobufClient = new HaberdasherClientProtobuf(FetchRPC({
  baseUrl: "http://localhost:8000/twirp",
}));

Alternatively provided your own implementation.

You can check the full example on how to integrate the client with axios.

Open API V3

You can now generate automatically an OpenAPI V3 compliant spec out of your twirp protobuf definitions!

We support the Gateway too!

Add the following options to your protoc command:

--twirp_ts_opt="openapi_twirp" 
--twirp_ts_opt="openapi_gateway"

Enjoy!

Migrate to V2

The v2 offers new functionalities and stability improvements, a few simple to migrate breaking changes have been made during the upgrade.

  • ts-proto & @protobuf-ts are now peerDepedencies which means that you can now update them at your pace.

    • Install either one of the 2 libraries (refer to Getting Started)
  • The twirp generator now uses protobuf-ts as the default generator. pass the --twirp_ts_opt="ts_proto" to use ts-proto

  • We now generate a single *.twirp.ts per .proto file instead of 1 file per service definition. if you have multiple services in one file you'd simply need to fix the imports

How to upgrade

The package uses Semver Versioning system. However, keep in mind that the code-generation plugin is tightly coupled to the twirp-ts library.

Make sure that whenever you update twirp-ts you re-generate the server and client code. This make sure that the generated code will be using the updated library

Licence

MIT <3