Skip to main content

Getting started

Beta software

connect-python is in beta. 1.0 will include a new Protobuf implementation built from scratch by Buf, which may introduce breaking changes. Join us on Slack if you have questions or feedback.

Connect is a slim library for building HTTP APIs consumable anywhere, including browsers. You define your service with a Protocol Buffer schema, and Connect generates type-safe server and client code. Fill in your server's business logic and you're done — no hand-written marshaling, routing, or client code required!

This fifteen-minute walkthrough helps you create a small Connect service in Python. It demonstrates what you'll be writing by hand, what Connect generates for you, and how to call your new API.

Prerequisites

  • uv installed. Any package manager, including pip, can also be used.
  • The Buf CLI installed, and include it in the $PATH.
  • We'll also use cURL. It's available from Homebrew and most Linux package managers.

Set up Python environment

First, we'll set up the Python environment and dependencies.

uv init
uv add connect-python uvicorn

Define a service

Now we're ready to write the Protocol Buffer schema that defines our service. In your shell,

mkdir -p proto/greet/v1
touch proto/greet/v1/greet.proto

Open proto/greet/v1/greet.proto in your editor and add:

syntax = "proto3";

package greet.v1;

message GreetRequest {
string name = 1;
}

message GreetResponse {
string greeting = 1;
}

service GreetService {
rpc Greet(GreetRequest) returns (GreetResponse) {}
}

This file declares the greet.v1 Protobuf package, a service called GreetService, and a single method called Greet with its request and response structures. These package, service, and method names will reappear soon in our HTTP API's URLs.

Generate code

We're going to generate our code using Buf, a modern replacement for Google's protobuf compiler.

First, scaffold a basic buf.yaml by running buf config init. Then, edit buf.yaml to use our proto directory:

version: v2
modules:
- path: proto
lint:
use:
- DEFAULT
breaking:
use:
- FILE

We will use remote plugins, a feature of the Buf Schema Registry for generating code. Tell Buf how to generate code by creating a buf.gen.yaml:

touch buf.gen.yaml
version: v2
plugins:
- remote: buf.build/protocolbuffers/python
out: .
- remote: buf.build/protocolbuffers/pyi
out: .
- remote: buf.build/connectrpc/python
out: .

With those configuration files in place, you can lint your schema and generate code:

buf lint
buf generate

In the greet package, you should now see some generated Python:

greet
└── v1
├── greet_connect.py
└── greet_pb2.py
└── greet_pb2.pyi

The package greet/v1 contains greet_pb2.py and greet_pb2.pyi which were generated by the protocolbuffers/python and protocolbuffers/pyi and contain GreetRequest and GreetResponse structs and the associated marshaling code. greet_connect.py was generated by connectrpc/python and contains the WSGI and ASGI service interfaces and client code to access a Connect server. Feel free to poke around if you're interested - greet_connect.py is standard Python code.

Implement service

The code we've generated takes care of the boring boilerplate, but we still need to implement our greeting logic. In the generated code, this is represented as the greet_connect.GreetService and greet_connect.GreetServiceSync interfaces for async ASGI and sync WSGI servers. Since the interface is so small, we can do everything in one Python file. touch server.py and add:

from greet.v1.greet_connect import GreetService, GreetServiceASGIApplication
from greet.v1.greet_pb2 import GreetResponse

class Greeter(GreetService):
async def greet(self, request, ctx):
print("Request headers: ", ctx.request_headers())
response = GreetResponse(greeting=f"Hello, {request.name}!")
ctx.response_headers()["greet-version"] = "v1"
return response

app = GreetServiceASGIApplication(Greeter())

In a separate terminal window, you can now start your server:

uv run uvicorn server:app

Make requests

The simplest way to consume your new API is an HTTP/1.1 POST with a JSON payload. If you have a recent version of cURL installed, it's a one-liner:

curl \
--header "Content-Type: application/json" \
--data '{"name": "Jane"}' \
http://localhost:8000/greet.v1.GreetService/Greet

This responds:

{
"greeting": "Hello, Jane!"
}

We can also make requests using Connect's generated client. touch client.py and add:

import asyncio

from greet.v1.greet_connect import GreetServiceClient
from greet.v1.greet_pb2 import GreetRequest

async def main():
client = GreetServiceClient("http://localhost:8000")
res = await client.greet(GreetRequest(name="Jane"))
print(res.greeting)

if __name__ == "__main__":
asyncio.run(main())

With your server still running in a separate terminal window, you can now run your client:

uv run python client.py

Congratulations — you've built your first Connect service! 🎉

So what?

With just a few lines of hand-written code, you've built a real API server that supports both the and Connect protocol. Unlike a hand-written REST service, you didn't need to design a URL hierarchy, hand-write request and response structs, manage your own marshaling, or parse typed values out of query parameters. More importantly, your users got an idiomatic, type-safe client without any extra work on your part.