connectrpc

package module
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 30, 2026 License: MIT Imports: 11 Imported by: 0

README

go-connectrpc

A lightweight Connect-RPC server factory for Go. Handles HTTP/2, CORS, panic recovery, and structured logging out of the box — so your service setup stays minimal.

Requirements

Installation

go get codeberg.org/obadness/go-connectrpc

Quick start

import (
    connectrpc "codeberg.org/obadness/go-connectrpc"
)

srv := connectrpc.NewServer(connectrpc.Config{
    Host: "0.0.0.0",
    Port: 8080,
    Registrars: []connectrpc.Registrar{
        &UserServiceHandler{},
        &OrderServiceHandler{},
    },
    CORS: connectrpc.CorsConfig{
        AllowedOrigins: []string{"https://myapp.com"},
    },
})

if err := srv.Serve(ctx); err != nil {
    log.Fatal(err)
}

Config

Field Type Description
Host string Bind address e.g. "0.0.0.0" or "localhost"
Port int Port to listen on
Registrars []Registrar Services to register on the mux
Interceptors []connect.Interceptor Additional interceptors — run innermost after built-ins
CORS CorsConfig CORS configuration — disabled if AllowedOrigins is empty
ShutdownTimeout time.Duration Graceful shutdown timeout — defaults to 10s

Registrar interface

Implement Registrar on your service handler to register it with the server:

type Registrar interface {
    Register(mux *http.ServeMux, opts ...connect.HandlerOption)
}
// example handler
type UserServiceHandler struct{}

func (h *UserServiceHandler) Register(mux *http.ServeMux, opts ...connect.HandlerOption) {
    path, handler := userv1connect.NewUserServiceHandler(h, opts...)
    mux.Handle(path, handler)
}

Built-in interceptors

Two interceptors are applied automatically to every handler, in this order:

Request → Recovery → Logging → Handler → Logging → Recovery → Response

Recovery is outermost so it catches panics from all inner interceptors and the handler. Logging sits inside Recovery so it always records duration and error code even when recovery fires.

Recovery

Catches panics in unary and streaming handlers. On panic it:

  • Logs the procedure name, panic value, and full stack trace via slog
  • Returns a Connect CodeInternal error to the client
// automatically applied — no configuration needed
interceptor.Recovery()
Logging

Logs every RPC call via slog. On success logs procedure and duration. On error also logs the Connect error code.

// automatically applied — no configuration needed
interceptor.Logging()

Example log output:

INFO unary rpc procedure=/user.v1.UserService/GetUser duration=1.2ms
INFO unary rpc procedure=/user.v1.UserService/CreateUser duration=3.4ms code=already_exists error="..."
INFO streaming rpc procedure=/user.v1.UserService/WatchUsers duration=30.1s
Custom interceptors

Additional interceptors are appended after the built-ins and run closest to the handler:

srv := connectrpc.NewServer(connectrpc.Config{
    Interceptors: []connect.Interceptor{
        authInterceptor,
        rateLimitInterceptor,
    },
})

CORS

CORS is disabled by default. Set AllowedOrigins to enable it:

CORS: connectrpc.CorsConfig{
    AllowedOrigins: []string{"https://myapp.com", "https://admin.myapp.com"},
}

When AllowedHeaders or MaxAge are not set, sensible defaults are applied automatically.

Default allowed headers
Content-Type
Connect-Protocol-Version
Connect-Timeout-Ms
Authorization
Cookie
X-Request-Id
Default MaxAge

7200 seconds (2 hours).

Exposed headers

The following headers are always exposed to browser clients so Connect trailers are readable:

Content-Type
Connect-Protocol-Version
Grpc-Status
Grpc-Message
Grpc-Status-Details-Bin
CorsConfig fields
Field Type Description
AllowedOrigins []string Allowed origins. Use "*" to allow all
AllowedHeaders []string Allowed request headers — defaults applied if empty
MaxAge int Preflight cache duration in seconds — defaults to 7200

Graceful shutdown

Serve accepts a context.Context. When the context is cancelled, the server shuts down gracefully waiting for in-flight requests to complete:

ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()

if err := srv.Serve(ctx); err != nil {
    log.Fatal(err)
}

The shutdown timeout controls how long the server waits for in-flight requests before forcefully closing:

connectrpc.Config{
    ShutdownTimeout: 30 * time.Second, // default: 10s
}

HTTP/2

The server uses h2c (HTTP/2 cleartext) so it works without TLS. Connect-RPC clients can use HTTP/2 framing over plain HTTP. To add TLS use ListenAndServeTLS via a custom http.Server wrapping the handler.


Project structure

go-connectrpc/
├── main.go              # NewServer(), Server, Config, Registrar
├── cors.go              # corsMiddleware, CorsConfig, defaultCORSConfig
└── interceptors/
    ├── logging.go       # Logging() interceptor
    └── recovery.go      # Recovery() interceptor

License

Copyright (C) 2026 Jeremy Obado. All rights reserved.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	Host            string
	Port            int
	Registrars      []Registrar
	Interceptors    []connect.Interceptor
	CORS            CorsConfig
	ShutdownTimeout time.Duration // defaults to 10s if zero
}

type CorsConfig

type CorsConfig struct {
	AllowedOrigins []string
	AllowedHeaders []string
	MaxAge         int
}

type Registrar

type Registrar interface {
	Register(mux *http.ServeMux, opts ...connect.HandlerOption)
}

type Server

type Server struct {
	// contains filtered or unexported fields
}

func NewServer

func NewServer(cfg Config) *Server

func (*Server) Serve

func (svr *Server) Serve(ctx context.Context) error

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL