Protobuf and gRPC used to be a hard sell for teams that used Python or TypeScript. The official libraries feel like C++ and Java ports because, for the most part, they are. But it’s more than a developer ergonomics issue. They are also lacking features that are treated as baseline requirements for modern development. Generated code has no type annotations, and gRPC doesn’t work with standard web frameworks or in the browser. You can just tell that these libraries were made for a different era.

So we fixed it. Buf now maintains first-class Protobuf and gRPC libraries (via Connect) for both languages: @bufbuild/protobuf and @connectrpc/connect for TypeScript, and protobuf-py and connect-py for Python. These libraries essentially had to be rebuilt from scratch.

When an upstream runtime is already in good shape, we use it, reporting bugs and contributing fixes. For example, in Go, google.golang.org/protobuf is solid, so connect-go and Protovalidate build right on top of it. The Go library has had a few major changes in the past to keep up with the evolution of the language. Python and TypeScript are a different story. The official plugins for both languages went fifteen years with very little maintenance, missing out on type checkers, ES modules, and fetch. We couldn’t fix a fifteen-year gap just by sending a few pull requests.

TypeScript

Google built the google-protobuf JavaScript library for its internal Closure Compiler long before TypeScript existed. It outputs huge .js files with Java-style getSentence() and setSentence() methods and no TypeScript types. Some community plugins added TypeScript types, which helped, but many of them had bugs with FieldMask or 64-bit JSON integers when talking to a Go or Java service.

@bufbuild/protobuf

With Protobuf-ES (@bufbuild/protobuf), messages are just plain objects with standard TypeScript types:

import { create, toBinary } from "@bufbuild/protobuf";
import { SayRequestSchema, type SayRequest } from "./gen/eliza_pb.js";
 
const req: SayRequest = create(SayRequestSchema, {
  sentence: "Hello!",
});
const data = toBinary(SayRequestSchema, req);

There are no getters or setters to deal with. Also, as you’d expect from proper type definitions, oneof fields use normal TypeScript discriminated unions. We also made sure that the code is written so that bundlers can tree-shake unused schemas to keep bundle sizes small.

Protobuf-ES was also the first JS/TS library to pass 100% of Google’s Protobuf conformance test suite. We treated conformance as a hard requirement from day one, and it has been great to see other TypeScript libraries fix their own conformance issues since then.

@connectrpc/connect

For RPCs, @connectrpc/connect works in the browser without a proxy. Connect supports the Connect protocol alongside standard binary gRPC and gRPC-Web over HTTP/1.1 and HTTP/2. That makes it a full gRPC and gRPC-Web client and server for TypeScript. Many teams find it useful for talking to existing gRPC servers and maybe never move on to the Connect protocol.

In the browser, the client uses fetch directly. You can see all of your RPCs in the Chrome DevTools Network tab and test endpoints with curl. If you use React, @connectrpc/connect-query gives you typed TanStack Query hooks for your RPCs.

Python

Python has changed dramatically since Google’s Python Protobuf library was written in 2008. Modern Python projects rely on typing annotations and type checkers like pyright or mypy. Most Python web APIs today also use asyncio and ASGI. However, Google’s grpcio library doesn’t support WSGI or ASGI at all, so you can’t mount a gRPC service inside FastAPI or Django.

The code that protoc generates for Python (_pb2.py) is also pretty strange if you’re used to “normal” Python. You can’t open the generated file and read a class definition, because metaclasses create the actual classes that you use dynamically when the module loads. This breaks autocomplete in your IDE unless you also configure --pyi_out to output extra .pyi stub files. Quirks from C++ also leak into Python in a few places. For example, map fields behave like C++ std::map rather than a Python dict:

# Instead of raising a KeyError, this mutates msg.items and inserts a default value!
val = msg.items["missing_key"]

protobuf-py

We recently released protobuf-py (buf.build/bufbuild/py) and connect-py (buf.build/connectrpc/py) to give Python developers a stack that actually feels like modern Python.

You might be happy to hear that protobuf-py generates normal Python classes with inline type annotations. This means that static analysis tooling like pyright and mypy work right away without special flags or weird stub files. protobuf-py also passes the full conformance suite, which Google’s Python runtime still does not.

connect-py

When you pair it with connect-py, your RPC services are just standard ASGI or WSGI applications:

from connectrpc.request import RequestContext
from eliza_pb import SayRequest, SayResponse
from eliza_connect import ElizaService, ElizaServiceASGIApplication
 
class MyElizaService(ElizaService):
    async def say(self, request: SayRequest, ctx: RequestContext) -> SayResponse:
        return SayResponse(sentence=f"You said: {request.sentence}")
 
# Run directly with uvicorn, or mount inside FastAPI, Starlette, Flask, or Django
app = ElizaServiceASGIApplication(MyElizaService())

You get both async (ElizaServiceClient) and sync (ElizaServiceClientSync) clients powered by pyqwest, with support for streaming, interceptors, and compression. If you’re working in an existing codebase with Google’s _pb2.py messages, you can pass the protobuf=google plugin option to use connect-py today and migrate your messages later.

Elevating the ecosystem

Despite being decades old, the Protobuf wire format has aged well. Protobuf, the developer experience, has needed a bit more help. For years, the experience in a given language depended on how much maintenance its protoc plugin got, and Python and JavaScript got very little.

It’s tough to get a team excited about schema-driven development if the generated code feels foreign to them. When a language’s official runtime is in a good spot, like in Go, we build on it. When it’s stuck in the past, we build a proper replacement from scratch and open-source it for anyone to use, even if they don’t use the rest of the Buf toolchain. Protobuf-ES and Connect-ES made TypeScript a first-class Protobuf language, and protobuf-py and connect-py bring that same experience to Python.

This work also pushes the rest of the ecosystem forward. The Connect protocol made it possible to call RPCs from a browser without putting an Envoy proxy in front, and our focus on conformance has encouraged other libraries to fix their own spec bugs. Ultimately, we want Protobuf tooling to be better across the board so more teams can use a schema-first API workflow.

All of these libraries work alongside the Buf CLI, breaking change detection, the language server, and the Buf Schema Registry, which hosts the plugins and publishes generated SDKs for TypeScript and Python. The modern Protobuf workflow walks through how all of it fits together.

If you haven’t tried the TypeScript or Python libraries yet, start with the getting started guides: