# hub-monorepo packages the Farcaster Hub client for Node and the browser

> hub-monorepo is a Yarn and TurboRepo monorepo holding the TypeScript packages used to talk to a Farcaster Hub, plus the gRPC clients for Node and the browser and a shuttle that streams Hubble events into Postgres. The npm packages are published per package from a private root, so the monorepo is the build system rather than the deliverable.

**farcasterxyz/hub-monorepo** — Implementation of the Farcaster Hub specification and supporting libraries for building applications on Farcaster

- Repository: https://github.com/farcasterxyz/hub-monorepo
- Stars: 827 · Forks: 518
- Language: TypeScript
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/farcasterxyz-hub-monorepo

## Four packages, one private root

The repository description calls it an implementation of the Farcaster Hub specification with supporting libraries for building applications on Farcaster. The README is more specific about scope: it contains packages used to communicate with a Farcaster Hub.

The package table lists four published packages.

| Package Name                                  | Description                                            |
| --------------------------------------------- | ------------------------------------------------------ |
| @farcaster/shuttle | A package that streams Hubble events to Postgres       |
| @farcaster/hub-nodejs | A Node.js client library for Hubble                   |
| @farcaster/hub-web | A Browser client library for Hubble                   |
| @farcaster/core | Shared code between all packages                     |

Two things follow from that table. The first is that the browser and Node clients are separate packages rather than one library with conditional exports, which is a deliberate choice for a gRPC codebase where the transport differs between a Node process and a browser. The second is that @farcaster/core is where the shared protocol code lives, so anything you write against the types is pulling from the same place the clients use.

The shuttle is a different animal again. It streams Hubble events to Postgres, which makes it an ingestion component for teams that want the Hub's event stream in their own database rather than querying a Hub over the wire for every read.

The root package.json is private and named hubble at version 0.0.0, which is the normal shape for a monorepo root. You do not install the root. You install one or more of the four packages, and the monorepo exists so they can be developed and versioned together.

## Yarn workspaces plus TurboRepo, with Rust underneath

The code organization section describes the whole build in one paragraph: a primary application in the /apps/ folder that imports several packages from /packages/, written primarily in TypeScript, using Yarn to orchestrate tasks and TurboRepo as the build system, with some performance intensive code written in Rust and compiled with Cargo.

The root package.json confirms the details. The package manager is pinned to yarn@1.22.19, and the workspaces field covers both apps/* and packages/*. The scripts run everything through the local turbo binary.

```bash
    "build": "./node_modules/.bin/turbo run build",
    "clean": "./node_modules/.bin/turbo run clean",
    "dev": "./node_modules/.bin/turbo run dev --parallel",
    "test": "./node_modules/.bin/turbo run test",
    "lint": "./node_modules/.bin/turbo run lint --parallel",
```

The dev and lint scripts use --parallel while build and test do not, which is the usual pattern: watch mode and linting want everything at once, build and test want a defined order.

The Rust part is worth calling out because it changes what a contributor needs installed. The lint-staged configuration runs rustfmt on Rust files, so the pre-commit hook expects a Rust toolchain.

```bash
  "lint-staged": {
    "*.ts": ["biome check"],
    "*.rs": ["rustfmt --check"]
  },
```

Formatting for TypeScript is handled by Biome, configured in biome.json at the root, and the hooks themselves are installed by husky through the prepare script. There is also a codecov.yml, so coverage is tracked for the CI runs.

The engine requirements are explicit: npm 8.0.0 or newer and node 20.11 or newer. The Node floor of 20.11 is the number to check before anything else, since it rules out several long-term support lines that are still common in production.

## The protoc script is where the browser story is decided

Generating the gRPC clients is not a single command, because the browser package needs a different transport than the Node package. The protoc script chains through core and hub-nodejs and hub-web, then rewrites the generated file.

```bash
    "protoc": "cd packages/core && yarn protoc && cd ../hub-nodejs && yarn protoc && cd ../hub-web && yarn protoc && sed -i '' 's/{ grpc }/grpcWeb/g; s/grpc\\./grpcWeb.grpc./g' src/generated/rpc.ts",
```

Two things are worth noticing in that line. The sed expression is the macOS form, with the empty argument after the -i flag, which is why the repository carries a generate-protos.sh script at the root as well.

The more interesting part is the rewrite itself. The generated client is transformed from grpc to grpcWeb by replacing the grpc import and the grpc. namespace prefix with grpcWeb.grpc., meaning the browser package ends up speaking gRPC over Web rather than native gRPC over HTTP/2. That is the standard approach for calling a gRPC service from a browser, and it also explains why the browser client is a separate package rather than a build variant of the Node one.

If you are consuming the browser client, this matters for your own infrastructure. The Hub endpoint you point it at has to serve gRPC-Web, not plain gRPC, and a proxy in front of it has to understand the protocol. There is no mention in the README of a proxy, so this is something you have to arrange yourself.

## Releasing four packages with changesets

The versioning story is the changesets workflow, and it is worth understanding because the four packages move independently.

The.changeset directory at the root is where the CLI of the same name reads pending release notes from, and the version-packages and release-packages scripts do the two halves of the work.

```bash
    "version-packages": "changeset version",
    "release-packages": "yarn build && changeset publish",
    "release-all": "yarn build && changeset publish && changeset tag"
```

release-all adds the changeset tag step, which is what creates the git tags after publishing.

Looking at the actual releases shows how this plays out. @farcaster/shuttle reached 1.0.3, @farcaster/hub-web reached 0.13.0 and @farcaster/hub-nodejs reached 0.17.0, and all three were published within seconds of each other on 2026-07-29. That co-publishing pattern is what a changesets monorepo does when several packages are affected by one change, and it tells you the packages are versioned together in practice even though their version numbers are independent.

It also tells you something about stability. The Node client is at 0.17.0 and the browser client at 0.13.0, both below 1.0, while the shuttle is at 1.0.3. Under conventional semver pre-1.0 expectations, a zero major version carries no compatibility promise. If you are pinning these versions, plan for breaking changes.

The three most recent releases date to 2026-07-29, and the last push was on 2026-08-04.

## Where hub-monorepo is the wrong choice

The clearest case is a team that wants to run a Farcaster Hub node. This repository holds client packages and a shuttle for streaming events to Postgres, not a Hub node implementation. Reading the package table, nothing here serves the Hub protocol itself, so if your requirement is to be a Hub operator rather than a Hub consumer, this is the wrong repository.

The second case is a team that wants raw protocol access without gRPC. The whole client surface is gRPC generated code, and the browser path is specifically gRPC-Web. If you need to talk to a Hub over a plain HTTP JSON API, or you want to avoid the protobuf dependency entirely, the packages do not serve that need.

The third is infrastructure cost. The shuttle is the piece that requires you to operate a Postgres database and keep it fed. For a small application that needs a user's profile or a handful of casts, running a shuttle and a database to answer those queries is a heavier commitment than calling a Hub directly. The trade runs the other way when you need a local, queryable copy of the event stream.

The fourth is the contribution friction. The stack is wide: TypeScript with Jest and SWC, Rust with Cargo and rustfmt, Yarn 1 workspaces, TurboRepo, Biome, changesets, husky, and protobuf generation with a macOS-flavoured sed. CONTRIBUTING.md is where the developer environment setup lives, and the README sends you there for it rather than spelling it out, which tells you the setup is non-trivial enough to deserve its own document.

## How it compares with talking to a Hub over raw HTTP

The alternative is to skip the client libraries and speak the Hub protocol directly over its HTTP API, decoding protobuf responses yourself.

That approach has one real advantage: no dependency on the shape of these particular packages. You are bound by the protocol rather than by a client library's release cadence, and with the Node client still at 0.17.0 that is not a hypothetical concern.

The cost is everything the client libraries do for you. The @farcaster/core package is the shared code between all packages, so it holds the protocol types and message definitions that the clients use, and you would be reimplementing the encoding of those messages, the handling of hub events, and the client behaviour. In exchange for that work, the client gives you a typed API and, for the browser case, the gRPC-Web transport already sorted out.

The shuttle is the clearest illustration of the difference. Writing a program that consumes the Hubble event stream and writes it into Postgres correctly, with the right ordering and the right schema, is a real piece of engineering. The shuttle is that program, packaged.

So the choice is between writing protocol code yourself and accepting a dependency on a pre-1.0 client that is published together with a browser variant and a shared core. For most teams building on Farcaster, taking the packages is the obvious trade, and the maintenance signal to watch is the version numbers, not the star count.

## Conclusion

hub-monorepo fits a team building on Farcaster that needs a Hub client rather than a run-your-own-Hub stack: the Node.js client for server side work, the browser client for web apps, the shared core for the protocol types, and the shuttle if you want Hubble events landing in Postgres. It does not fit a team that wants to run a Hub node itself, because this repository holds the client packages, not the node. Before adopting, read CONTRIBUTING.md for the developer environment setup, confirm your Node version satisfies the engines requirement of node 20.11 or newer and that the package manager is Yarn 1.22.19, and check whether the gRPC code you depend on is served as grpcWeb, since the protoc script rewrites the generated client for the browser package.

## FAQ

### What packages does the Farcaster hub-monorepo publish?

It publishes @farcaster/hub-nodejs as a Node.js client library for Hubble, @farcaster/hub-web as a browser client library for Hubble, @farcaster/core as shared code between all packages, and @farcaster/shuttle which streams Hubble events to Postgres.

### What does the Farcaster hub-monorepo require to build?

The root package.json pins the package manager to yarn@1.22.19 and requires npm 8.0.0 or newer and node 20.11 or newer. Builds run through TurboRepo, formatting uses Biome for TypeScript and rustfmt for Rust, and the contributor guide in CONTRIBUTING.md covers the developer environment setup.

### Why does the browser client use gRPC-Web?

The protoc script generates the clients for core, hub-nodejs and hub-web and then rewrites the browser client with a sed expression that replaces the grpc import and the grpc. namespace with grpcWeb.grpc., so the browser package speaks gRPC over Web rather than native gRPC. That is why hub-web is a separate package from hub-nodejs.

### How are releases published in the Farcaster hub-monorepo?

The project uses changesets. The version-packages script runs changeset version, release-packages runs a build followed by changeset publish, and release-all adds changeset tag to create the git tags. Recent releases of shuttle, hub-web and hub-nodejs were published together on 2026-07-29.

### What is the @farcaster/shuttle package for?

It streams Hubble events to Postgres, so a team can keep a local queryable copy of the Hub event stream in its own database rather than querying a Hub over the wire for every read. The trade is that you then have to operate and maintain that database.

## Sources

- [farcasterxyz/hub-monorepo on GitHub](https://github.com/farcasterxyz/hub-monorepo)
- [Issues](https://github.com/farcasterxyz/hub-monorepo/issues)
- [License: MIT](https://github.com/farcasterxyz/hub-monorepo/blob/main/LICENSE)
- [README](https://github.com/farcasterxyz/hub-monorepo/blob/main/README.md)
- [Releases](https://github.com/farcasterxyz/hub-monorepo/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/farcasterxyz-hub-monorepo
