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.
- 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.
First, we'll setup the python environment and dependencies.
=== "ASGI"
```bash
uv init
uv add connect-python uvicorn
```
=== "WSGI"
```bash
uv init
uv add connect-python gunicorn
```
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.protoOpen 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.
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:
- FILEWe 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.yamlversion: 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 generateIn 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.
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 respectively. Since the interface is so small, we can do everything
in one Python file. touch server.py and add:
=== "ASGI"
```python
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())
```
=== "WSGI"
```python
from greet.v1.greet_connect import GreetServiceSync, GreetServiceWSGIApplication
from greet.v1.greet_pb2 import GreetResponse
class Greeter(GreetServiceSync):
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 = GreetServiceWSGIApplication(Greeter())
```
In a separate terminal window, you can now start your server:
=== "ASGI"
```bash
uv run uvicorn server:app
```
=== "WSGI"
```bash
uv run gunicorn server:app
```
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/GreetThis responds:
{
"greeting": "Hello, Jane!"
}We can also make requests using Connect's generated client. touch client.py and add:
=== "Async"
```python
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())
```
=== "Sync"
```python
from greet.v1.greet_connect import GreetServiceClientSync
from greet.v1.greet_pb2 import GreetRequest
def main():
client = GreetServiceClientSync("http://localhost:8000")
res = client.greet(GreetRequest(name="Jane"))
print(res.greeting)
if __name__ == "__main__":
main()
```
With your server still running in a separate terminal window, you can now run your client:
uv run python client.pyCongratulations — you've built your first Connect service! 🎉
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.
- Learn about Usage patterns
- Explore the API Reference
- Check out Examples