Getting started
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.
- ASGI
- WSGI
uv init
uv add connect-python uvicorn
uv init
uv add connect-python gunicorn
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:
- ASGI
- WSGI
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())
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
- WSGI
uv run uvicorn server:app
uv run gunicorn 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:
- Async
- Sync
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())
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.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.