A production-grade, declarative HTTP client for Go.
Documentation ยท pkg.go.dev ยท Quick Start ยท Extensions ยท Tools ยท Changelog
Relay brings the ergonomics of Python's requests and the resilience of Resilience4j to Go. It provides a fluent, batteries-included API for building resilient HTTP clients: retries, circuit breaking, rate limiting, deduplication, adaptive timeouts, load balancing, and full observability - all composable via options.
The core module has zero external dependencies. Every integration (Redis, OTel, Prometheus, gRPC, slog, chaos, VCR, etc.) lives in its own optional extension module so you only pull in what you need.
go get github.com/jhonsferg/relayRequires Go 1.24 or later. WASM (js/wasm) targets are supported.
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/jhonsferg/relay"
)
type Repo struct {
ID int `json:"id"`
Name string `json:"full_name"`
}
func main() {
client := relay.New(
relay.WithBaseURL("https://api.github.com"),
relay.WithRetry(relay.RetryConfig{MaxAttempts: 3}),
relay.WithTimeout(10*time.Second),
)
var repo Repo
_, err := relay.Get[Repo](context.Background(), client, "/repos/golang/go", &repo)
if err != nil {
log.Fatal(err)
}
fmt.Println(repo.Name)
}| Feature | Description |
|---|---|
| Fluent request builder | Chain .GET(), .POST(), .Header(), .Query(), .Body() |
| Retry & backoff | Exponential backoff with jitter, configurable retry conditions |
| Circuit breaker | Automatic open/half-open/closed state machine |
| Rate limiting | Token bucket and sliding window algorithms |
| Request deduplication | In-flight singleflight to collapse concurrent identical requests |
| Retry budget | Sliding window budget to prevent retry storms |
| Client-side load balancer | Round-robin and random strategies across backends |
| Adaptive timeout | Percentile-based timeout derived from observed latency |
| Bulkhead isolation | Concurrency limits per client or endpoint group |
| Request hedging | Parallel speculative requests, use first response |
| Streaming | io.Reader and channel-based streaming for large payloads |
| Hooks | OnBeforeRequest / OnAfterResponse for auth, logging, metrics |
| Generic decode | relay.Get[T], relay.Post[T] with zero-alloc JSON decoding |
| Error classification | Distinguish transient / permanent / rate-limited errors |
| ETag & idempotency | Built-in idempotency key generation and ETag support |
| TLS & certificates | Dynamic cert reloading, mTLS, custom CA bundles |
| Feature | Description |
|---|---|
| Bearer / Basic auth | WithBearerToken, WithBasicAuth options |
| HMAC-SHA256 signing | HMACRequestSigner sets X-Timestamp + X-Signature automatically |
| Multi-signer | NewMultiSigner chains multiple signers in order |
| Credential rotation | RotatingTokenProvider refreshes tokens before expiry with a configurable threshold |
| Custom signer | Implement the RequestSigner interface for any auth scheme |
| mTLS | Mutual TLS with client certificates |
| Feature | Description |
|---|---|
| Unix domain socket | WithUnixSocket - connect to local services via socket path (Linux/macOS) |
| DNS SRV discovery | WithSRVDiscovery - resolve service endpoints via DNS SRV records with TTL caching |
| HTTP/2 push promises | WithHTTP2PushHandler - handle server push promises and cache pushed responses |
| WASM/js | Builds on js/wasm; WithUnixSocket is a no-op on that target for portability |
| Feature | Description |
|---|---|
| Gzip / Zstd | WithCompression(relay.Gzip) or WithCompression(relay.Zstd) for response decompression |
| Request compression | WithRequestCompression compresses outgoing request bodies |
| Dictionary Zstd | ext/compress - ZstdDictionaryCompressor for pre-shared dictionary compression |
| Feature | Description |
|---|---|
| HAR export | HARRecorder captures all traffic in HAR format; HARRecorder.All() returns a Go 1.23 iter.Seq[HAREntry] iterator |
| OpenTelemetry | ext/otel - unified tracing + metrics via WithOtel(tracer, meter) |
| Prometheus | ext/prometheus - Prometheus metrics exporter |
| Feature | Description |
|---|---|
| Response schema validation | WithSchemaValidator - validate decoded responses against struct tags or a JSON Schema |
| Struct validator | NewStructValidator - validates required fields and rules via struct tags |
| JSON Schema validator | NewJSONSchemaValidator - validates against an inline JSON Schema definition |
Full feature documentation: jhonsferg.github.io/relay
Each extension is a standalone Go module - add only what you use:
| Module | Import path | Description |
|---|---|---|
ext/compress |
github.com/jhonsferg/relay/ext/compress |
Dictionary-based Zstd compression (ZstdDictionaryCompressor) |
ext/oidc |
github.com/jhonsferg/relay/ext/oidc |
OIDC/JWT bearer token provider |
ext/otel |
github.com/jhonsferg/relay/ext/otel |
OpenTelemetry tracing + metrics in one option (WithOtel) |
ext/tracing |
github.com/jhonsferg/relay/ext/tracing |
OpenTelemetry distributed tracing (standalone) |
ext/metrics |
github.com/jhonsferg/relay/ext/metrics |
OpenTelemetry metrics (standalone) |
ext/prometheus |
github.com/jhonsferg/relay/ext/prometheus |
Prometheus metrics exporter |
ext/slog |
github.com/jhonsferg/relay/ext/slog |
Structured logging via log/slog |
ext/zap |
github.com/jhonsferg/relay/ext/zap |
Zap logging integration |
ext/chaos |
github.com/jhonsferg/relay/ext/chaos |
Fault injection for resilience testing |
ext/vcr |
github.com/jhonsferg/relay/ext/vcr |
HTTP cassette recording and playback |
ext/mock |
github.com/jhonsferg/relay/ext/mock |
Mock transport for unit tests |
ext/oauth |
github.com/jhonsferg/relay/ext/oauth |
OAuth2 token management |
ext/sigv4 |
github.com/jhonsferg/relay/ext/sigv4 |
AWS SigV4 request signing |
ext/openapi |
github.com/jhonsferg/relay/ext/openapi |
OpenAPI request/response validation |
ext/redis |
github.com/jhonsferg/relay/ext/redis |
Redis-backed cache and rate limiting |
ext/http3 |
github.com/jhonsferg/relay/ext/http3 |
HTTP/3 QUIC transport |
ext/websocket |
github.com/jhonsferg/relay/ext/websocket |
WebSocket upgrade |
ext/grpc |
github.com/jhonsferg/relay/ext/grpc |
gRPC bridge transport |
ext/graphql |
github.com/jhonsferg/relay/ext/graphql |
GraphQL query support |
ext/sentry |
github.com/jhonsferg/relay/ext/sentry |
Sentry error reporting |
ext/brotli |
github.com/jhonsferg/relay/ext/brotli |
Brotli compression support |
ext/breaker/gobreaker |
github.com/jhonsferg/relay/ext/breaker/gobreaker |
Circuit breaker backed by gobreaker |
ext/cache/lru |
github.com/jhonsferg/relay/ext/cache/lru |
In-process LRU response cache |
ext/cache/twolevel |
github.com/jhonsferg/relay/ext/cache/twolevel |
Two-level (L1+L2) response cache |
ext/ratelimit/distributed |
github.com/jhonsferg/relay/ext/ratelimit/distributed |
Distributed rate limiting |
ext/memcached |
github.com/jhonsferg/relay/ext/memcached |
Memcached-backed cache |
ext/jitterbug |
github.com/jhonsferg/relay/ext/jitterbug |
Pluggable jitter strategies for retry backoff |
Extension documentation: jhonsferg.github.io/relay/extensions
relay-gen reads an OpenAPI 3.x spec and generates a type-safe Go client using relay:
go install github.com/jhonsferg/relay/cmd/relay-gen@latest
relay-gen -spec openapi.json -pkg acme -out ./acme/client.goThe generated client exposes one method per operation with strongly-typed request/response structs and full relay middleware support.
go install github.com/jhonsferg/relay/cmd/relay-probe@latest
relay-probe https://api.example.com/healthgo install github.com/jhonsferg/relay/cmd/relay-bench@latest
relay-bench -url https://api.example.com/ping -n 1000Connect to services exposed via Unix domain sockets (Linux/macOS):
client := relay.New(
relay.WithBaseURL("http://localhost"),
relay.WithUnixSocket("/var/run/myapp.sock"),
)
resp, err := client.Execute(relay.NewRequest().GET("/status"))Note: On
js/wasmtargetsWithUnixSocketis accepted but silently ignored, keeping call sites portable.
Resolve backends dynamically from DNS SRV records:
resolver := relay.NewSRVResolver("myservice", "tcp", "example.com", "https",
relay.WithSRVTTL(30*time.Second),
relay.WithSRVBalancer(relay.SRVRoundRobin),
)
client := relay.New(relay.WithSRVDiscovery(resolver))Handle server-pushed resources and cache them for subsequent requests:
cache := relay.NewPushedResponseCache()
client := relay.New(
relay.WithBaseURL("https://api.example.com"),
relay.WithHTTP2PushHandler(cache),
)Capture all traffic in HAR format for debugging or test fixtures:
rec := relay.NewHARRecorder()
client := relay.New(relay.WithMiddleware(rec.Middleware()))
// iterate with a Go 1.23 range-over-func loop
for entry := range rec.All() {
fmt.Println(entry.Request.URL, entry.Response.Status)
}
data, _ := rec.Export()
os.WriteFile("traffic.har", data, 0o644)HMAC-SHA256 signing - sets X-Timestamp and X-Signature headers automatically:
client := relay.New(
relay.WithRequestSigner(&relay.HMACRequestSigner{Key: []byte(secret)}),
)Rotating token provider - refreshes credentials before expiry:
provider := relay.NewRotatingTokenProvider(fetchTokenFunc, 5*time.Minute)
client := relay.New(relay.WithCredentialProvider(provider))Multiple signers - chain signers in order with NewMultiSigner:
client := relay.New(
relay.WithRequestSigner(relay.NewMultiSigner(
&relay.HMACRequestSigner{Key: signingKey},
relay.RequestSignerFunc(func(r *http.Request) error {
r.Header.Set("X-Tenant", tenantID)
return nil
}),
)),
)Validate decoded responses against struct tags or a JSON Schema:
// struct-tag validation
validator := relay.NewStructValidator(MyResponse{})
client := relay.New(relay.WithSchemaValidator(validator))
// JSON Schema validation
validator, err := relay.NewJSONSchemaValidator(`{"type":"object","required":["id"]}`)
client := relay.New(relay.WithSchemaValidator(validator))Relay's CI pipeline runs across 6 OS/Go version combinations and includes:
- Unit & integration tests -
ci.yml - CodeQL static analysis -
codeql.yml - Vulnerability scanning - Trivy (
trivy.yml) - Benchmark regression detection (on-demand) -
benchstat.ymlcompares benchmarks against master using benchstat; run manually viaworkflow_dispatchwhen needed - Benchmark dashboard (on-demand) -
benchmark-dashboard.ymlregenerates the ๐ live dashboard; triggered manually viaworkflow_dispatch
import "github.com/jhonsferg/relay/testutil"
srv := testutil.NewMockServer()
defer srv.Close()
srv.Enqueue(testutil.Response{Status: 200, Body: `{"id":1}`})
client := relay.New(relay.WithBaseURL(srv.URL))
resp, err := client.Execute(relay.NewRequest().GET("/items/1"))Testing guide: jhonsferg.github.io/relay/guides/testing
The full documentation is at jhonsferg.github.io/relay:
- Getting Started
- All Features
- Extension Modules
- API Reference on pkg.go.dev
- ๐ Live Benchmark Dashboard
MIT - see LICENSE.