Twirp-TS
Twirp-TS is a comprehensive TypeScript implementation of the Twirp RPC specification (v7 and v8), designed for generating both server and client code from Protocol Buffer definitions. It operates as a plugin for `protoc`, relying on either `@protobuf-ts/plugin` or `ts-proto` for the underlying protobuf message generation. The library facilitates building performant, type-safe APIs with minimal boilerplate, offering features like automatic OpenAPI V3 spec generation, server-side hooks and interceptors, and a gateway for proxying requests. It is actively maintained, with the current stable version being 2.5.0, and receives regular updates to fix bugs and introduce new generation options, such as granular client/server-only code generation. Its primary differentiator lies in providing a full-stack TypeScript solution for Twirp, ensuring strong type consistency from schema to implementation.
Common errors
-
protoc-gen-twirp_ts: program not found
cause The `protoc` command cannot find the `protoc-gen-twirp_ts` executable, often due to it not being in PATH or an incorrect `--plugin` flag path.fixEnsure `node_modules/.bin` is in your PATH or provide the full path to the plugin using `--plugin=protoc-gen-twirp_ts=./node_modules/.bin/protoc-gen-twirp_ts` in your `protoc` command. -
Cannot find module './generated/service' or its corresponding type declarations.
cause The protobuf message or Twirp service code has not been generated, the output directory is incorrect, or TypeScript isn't configured to include the generated files.fixRun your `protoc` command with `twirp-ts` (and `protobuf-ts` or `ts-proto`) to generate the files. Verify the `OUT_DIR` matches your TypeScript configuration's `include` or `paths` settings. -
TypeError: Cannot read properties of undefined (reading 'httpHandler')
cause The Twirp server or client object was not correctly initialized, often due to an incorrect service implementation or a mismatch between generated code and runtime usage.fixEnsure the service implementation passed to `create[Service]Server` fully satisfies the generated TypeScript interface, and that all necessary generated files are imported and used correctly at runtime.
Warnings
- breaking Version 2.0.0 introduced significant breaking changes, including the new Gateway feature, modifications to how peer dependencies are handled during transpilation, and changes to the custom context API. Direct migration from v1.x will require code adjustments.
- gotcha Twirp-TS is a `protoc` plugin and requires the Protocol Buffer Compiler (`protoc`) or Buffer CLI (`buf`) to be installed and available in your system's PATH for code generation to function correctly. This is not an npm dependency.
- gotcha Users must explicitly choose and configure either `@protobuf-ts/plugin` or `ts-proto` to generate the base protobuf message definitions. `twirp-ts` only generates the service stubs, not the data types themselves.
- gotcha Beginning with v2.3.0, `twirp-ts` introduced granular code generation options (`standalone`, `client_only`, `server_only`) via `--twirp_ts_opt`. Not specifying an option will generate both client and server code, which might be undesired in monorepos or specific build pipelines.
Install
-
npm install twirp-ts -
yarn add twirp-ts -
pnpm add twirp-ts
Imports
- create[ServiceName]Server
const createHaberdasherServer = require('./generated/haberdasher.twirp');import { createHaberdasherServer } from './generated/haberdasher.twirp'; - TwirpContext
import TwirpContext from 'twirp-ts';
import { TwirpContext } from 'twirp-ts'; - TwirpError, TwirpErrorCode
import { TwirpError } from 'twirp-ts/dist/errors';import { TwirpError, TwirpErrorCode } from 'twirp-ts';
Quickstart
import * as http from "http";
import { TwirpContext, TwirpError, TwirpErrorCode } from "twirp-ts";
// Assuming these are generated from your .proto file, e.g., 'haberdasher.proto'
import { createHaberdasherServer } from "./generated/haberdasher.twirp";
import { Hat, Size, Haberdasher } from "./generated/service";
// Implement the Haberdasher service according to the generated interface
const haberdasherService: Haberdasher = {
async makeHat(ctx: TwirpContext, size: Size): Promise<Hat> {
// Validate input
if (size.inches <= 0) {
throw new TwirpError(TwirpErrorCode.InvalidArgument, "size.inches must be positive");
}
console.log(`Received request for hat of size: ${size.inches} inches`);
// Simulate work and return a hat
return {
inches: size.inches,
color: "blue",
name: "fedora"
};
}
};
// Create the Twirp server instance
const twirpServer = createHaberdasherServer(haberdasherService);
// Create a standard Node.js HTTP server and mount the Twirp handler
const server = http.createServer(twirpServer.httpHandler());
const PORT = process.env.PORT || 8080;
server.listen(PORT, () => {
console.log(`Twirp Haberdasher server listening on port ${PORT}`);
console.log("To test: curl -d '{\"inches\":12}' -H \"Content-Type: application/json\" http://localhost:8080/twirp/haberdasher.Haberdasher/MakeHat");
});