Skip to content

Getting started with Connect RPC

This page walks from an empty project to a working Connect server and client. The running example is the connectrpc.eliza.v1.ElizaService used by the test suite — a unary Say, a server-streaming Introduce, a bidirectional Converse, and a client-streaming Aggregate.

wireform-connect sits on top of three in-tree packages, all of which must be on the build path (they ship together in this monorepo):

Add wireform-connect (plus wireform-grpc and wireform-proto) to your cabal build-depends. It targets GHC 9.6 / 9.8.

The service description is shared with gRPC. One .proto, two splices — in a module with {-# LANGUAGE DataKinds #-} (the IDL bridge emits HasField instances with type-level string literals):

{-# LANGUAGE DataKinds, TemplateHaskell, FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses, TypeFamilies, UndecidableInstances #-}
module Eliza where
import Network.GRPC.Protobuf.TH (loadProtoServices)
import Proto.TH (loadProto)
$(loadProto "proto/eliza.proto")
$(loadProtoServices "proto/eliza.proto")

loadProto emits the message records (with proto3-JSON aeson instances and the wire codec); loadProtoServices emits the protocol-agnostic Protobuf ElizaService "<meth>" tags that both gRPC and Connect consume. Every method tag — Say, Introduce, Converse, Aggregate — is now usable from both transports.

A Connect server is one HTTP handler built from a service implementation: one method per RPC (the handler shape follows from the method’s streaming kind), bundled with service — order-insensitive and completeness-checked at compile time — then adapted with connectHandlers:

import Network.Connect.Server
import Network.HTTP.Server (defaultServerConfig, ServerConfig (..))
import Network.HTTP.VersionRange (preferHttp20)
import Network.GRPC.Spec (Proto (..))
import Eliza
main :: IO ()
main = runConnectServer defaultConnectServerConfig serverCfg (connectHandlers eliza)
where
serverCfg = defaultServerConfig
{ serverPort = "8080", serverVersionRange = preferHttp20 }
eliza :: Service ElizaService ConnectServerM
eliza =
service
( method @Say say
:& method @Introduce introduce
:& method @Converse converse
:& Done
)
where
-- Unary: Input -> ConnectServerM Output
say (Proto req) = pure (Proto defaultSayResponse
{ sayResponseSentence = "Hello, " <> sayRequestSentence req })
-- Server streaming: take the request, call `send` for each output.
introduce (Proto req) send = do
send (Proto defaultIntroduceResponse
{ introduceResponseSentence = "Hi " <> introduceRequestName req })
send (Proto defaultIntroduceResponse { introduceResponseSentence = "..." })
-- Bidirectional: `recv` for inputs, `send` for outputs, in any order.
converse recv send = ...

Run it. serverVersionRange = preferHttp20 negotiates HTTP/2 over TLS and falls back to HTTP/1.1 on plaintext, so the same server speaks both. See Serving Connect RPCs for the handler shapes, metadata accessors, and error handling.

A client is a connection plus codec settings, bracketed by withConnectClient:

import Network.Connect.Client
import Network.Connect.Protocol (Codec (..))
import Network.HTTP.Client
(defaultConnectionConfig, ConnectionConfig (..))
import Network.GRPC.Spec (Proto (..))
import Data.Proxy (Proxy (..))
import Eliza
main :: IO ()
main = do
let connCfg = defaultConnectionConfig
{ connectionHost = "localhost", connectionPort = "8080" }
withConnectClient defaultConnectClientConfig connCfg $ \cl -> do
-- Unary POST; throws ConnectException on an error response.
Proto resp <- nonStreaming cl (Proxy @Say)
(Proto defaultSayRequest { sayRequestSentence = "Hi" })
print (sayResponseSentence resp)

Want JSON instead of binary Protobuf so you can read it with curl? Set the codec:

defaultConnectClientConfig { cccCodec = CodecJSON }

See Calling Connect RPCs for the streaming calls, TLS, and per-call config.

The package’s test suite is an in-process loopback (server + client over an ephemeral port) covering every RPC kind for both codecs, plus unary GET, the error path, gzip, and metadata propagation:

Terminal window
cabal test wireform-connect:wireform-connect-test

There is also opt-in interop against the public Connect reference server. demo.connectrpc.com runs the same ElizaService; set one environment variable and the Test.Interop suite calls it over TLS HTTP/2 for both codecs (skipped silently otherwise):

Terminal window
CONNECT_DEMO=1 cabal test wireform-connect:wireform-connect-test

A green CONNECT_DEMO run is the strongest end-to-end signal that the implementation matches the protocol — it talks to a server written by the protocol’s authors.