A production-ready, reusable gRPC server with Echo HTTP framework integration for Go applications. This package provides a clean, configuration-driven API for setting up gRPC servers with HTTP/REST gateway support, built-in observability, health checks, and graceful shutdown capabilities.
- Echo Framework Integration: Full-featured HTTP server using Echo v4
- Dual Protocol Support: Run gRPC and HTTP services on the same port (H2C) or separate ports
- gRPC Gateway: Automatic HTTP/REST endpoints for gRPC services
- Zero Configuration: Works out-of-the-box with sensible defaults
- Production Ready: Built-in OpenTelemetry metrics, health checks, and graceful shutdown
- Highly Configurable: Extensive configuration options including CORS, rate limiting, and custom middleware
- Observability: OpenTelemetry metrics, tracing, and structured logging for both gRPC and HTTP
- Easy Integration: Clean API that works with any gRPC service implementation
go get github.com/jasoet/pkg/v2/grpcpackage main
import (
"log"
"google.golang.org/grpc"
grpcserver "github.com/jasoet/pkg/grpc"
calculatorv1 "your-module/gen/calculator/v1"
"your-module/internal/service"
)
func main() {
// Define service registrar
serviceRegistrar := func(s *grpc.Server) {
calculatorService := service.NewCalculatorService()
calculatorv1.RegisterCalculatorServiceServer(s, calculatorService)
}
// Start server with minimal configuration
log.Println("Starting gRPC server on :8080...")
if err := grpcserver.Start("8080", serviceRegistrar); err != nil {
log.Fatalf("Failed to start server: %v", err)
}
}This starts a server in H2C mode where both gRPC and HTTP endpoints are available on port 8080:
- gRPC endpoints:
localhost:8080 - HTTP gateway:
http://localhost:8080/api/v1/ - Health checks:
http://localhost:8080/health
Single port serving both gRPC and HTTP traffic using HTTP/2 cleartext protocol:
grpcserver.Start("8080", serviceRegistrar)
// or explicitly
grpcserver.StartH2C("8080", serviceRegistrar)Different ports for gRPC and HTTP services:
grpcserver.StartSeparate("9090", "9091", serviceRegistrar)package main
import (
"log"
"time"
"github.com/labstack/echo/v4"
"google.golang.org/grpc"
grpcserver "github.com/jasoet/pkg/grpc"
)
func main() {
// Create advanced configuration
config := grpcserver.DefaultConfig()
// Server Configuration
config.GRPCPort = "50051"
config.Mode = grpcserver.H2CMode
// Timeouts
config.ShutdownTimeout = 45 * time.Second
config.ReadTimeout = 10 * time.Second
config.WriteTimeout = 15 * time.Second
config.IdleTimeout = 120 * time.Second
config.MaxConnectionIdle = 30 * time.Minute
config.MaxConnectionAge = 60 * time.Minute
config.MaxConnectionAgeGrace = 10 * time.Second
// Production Features
config.EnableHealthCheck = true
config.EnableReflection = true
// Echo-specific Features
config.EnableCORS = true
config.EnableRateLimit = true
config.RateLimit = 100.0 // requests per second
// Gateway Configuration
config.GatewayBasePath = "/api/v1"
// Register gRPC services
config.ServiceRegistrar = func(srv *grpc.Server) {
// Register your gRPC services here
log.Println("Registering gRPC services...")
}
// Configure Echo with custom routes
config.EchoConfigurer = func(e *echo.Echo) {
// Add custom REST endpoints
e.GET("/status", func(c echo.Context) error {
return c.JSON(200, map[string]interface{}{
"service": "my-service",
"status": "running",
})
})
// Add custom middleware
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
log.Printf("Custom middleware: %s %s", c.Request().Method, c.Path())
return next(c)
}
})
log.Println("Custom Echo routes configured")
}
// Custom gRPC configuration
config.GRPCConfigurer = func(s *grpc.Server) {
log.Println("Applying custom gRPC configuration...")
// Add interceptors, custom options, etc.
}
// Custom shutdown handler
config.Shutdown = func() error {
log.Println("Running custom cleanup...")
// Close connections, cleanup resources
return nil
}
// Start server
if err := grpcserver.StartWithConfig(config); err != nil {
log.Fatalf("Failed to start server: %v", err)
}
}import (
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
config := grpcserver.DefaultConfig()
// Add Echo middleware via configuration
config.Middleware = []echo.MiddlewareFunc{
middleware.RequestID(),
middleware.Secure(),
middleware.Gzip(),
}
// Or configure via EchoConfigurer
config.EchoConfigurer = func(e *echo.Echo) {
e.Use(middleware.RequestID())
e.Use(middleware.Secure())
e.Use(middleware.GzipWithConfig(middleware.GzipConfig{
Level: 5,
}))
}The gRPC server package supports OpenTelemetry for comprehensive observability with distributed tracing, metrics, and structured logging. Provide OTelConfig to enable instrumentation.
package main
import (
"context"
"log"
"github.com/jasoet/pkg/logging"
"github.com/jasoet/pkg/otel"
grpcserver "github.com/jasoet/pkg/grpc"
"google.golang.org/grpc"
)
func main() {
// Create OTel config with logging (traces and metrics optional)
otelCfg := otel.NewConfig("my-grpc-service").
WithServiceVersion("1.0.0")
// Or use logging package for better log-span correlation
loggerProvider := logging.NewLoggerProvider("my-grpc-service", false)
otelCfg.WithLoggerProvider(loggerProvider)
// Start server with OTel
server, err := grpcserver.New(
grpcserver.WithGRPCPort("50051"),
grpcserver.WithOTelConfig(otelCfg),
grpcserver.WithServiceRegistrar(func(s *grpc.Server) {
// Register your services
}),
)
if err != nil {
log.Fatal(err)
}
if err := server.Start(); err != nil {
log.Fatal(err)
}
}package main
import (
"context"
"log"
"time"
"github.com/jasoet/pkg/logging"
"github.com/jasoet/pkg/otel"
grpcserver "github.com/jasoet/pkg/grpc"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
"go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.27.0"
"google.golang.org/grpc"
)
func main() {
ctx := context.Background()
// Setup resource attributes
res, err := resource.New(ctx,
resource.WithAttributes(
semconv.ServiceNameKey.String("my-grpc-service"),
semconv.ServiceVersionKey.String("1.0.0"),
),
)
if err != nil {
log.Fatal(err)
}
// Setup TracerProvider with OTLP exporter
traceExporter, err := otlptracehttp.New(ctx,
otlptracehttp.WithEndpoint("localhost:4318"),
otlptracehttp.WithInsecure(),
)
if err != nil {
log.Fatal(err)
}
tracerProvider := trace.NewTracerProvider(
trace.WithBatcher(traceExporter),
trace.WithResource(res),
)
// Setup MeterProvider
meterProvider := metric.NewMeterProvider(
metric.WithResource(res),
)
// Setup LoggerProvider with trace correlation
loggerProvider := logging.NewLoggerProvider("my-grpc-service", false)
// Create OTel config
otelCfg := &otel.Config{
ServiceName: "my-grpc-service",
ServiceVersion: "1.0.0",
TracerProvider: tracerProvider,
MeterProvider: meterProvider,
LoggerProvider: loggerProvider,
}
// Start gRPC server with full OTel instrumentation
server, err := grpcserver.New(
grpcserver.WithGRPCPort("50051"),
grpcserver.WithOTelConfig(otelCfg),
grpcserver.WithServiceRegistrar(func(s *grpc.Server) {
// Register your services
}),
grpcserver.WithShutdownHandler(func() error {
// Shutdown OTel providers
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := tracerProvider.Shutdown(ctx); err != nil {
log.Printf("Error shutting down tracer provider: %v", err)
}
if err := meterProvider.Shutdown(ctx); err != nil {
log.Printf("Error shutting down meter provider: %v", err)
}
return nil
}),
)
if err != nil {
log.Fatal(err)
}
if err := server.Start(); err != nil {
log.Fatal(err)
}
}When OTelConfig is provided, the server automatically instruments:
- Traces: Distributed tracing for all gRPC methods with semantic conventions
- Metrics:
rpc.server.request.count- Total gRPC requests by method and statusrpc.server.duration- Request duration histogramrpc.server.active_requests- Active concurrent requests
- Logs: Structured logs with automatic trace_id/span_id correlation
- Traces: HTTP request spans linked to gRPC spans
- Metrics:
http.server.request.count- Total HTTP requestshttp.server.request.duration- Request duration histogramhttp.server.active_requests- Active concurrent requests
- Logs: HTTP access logs with trace correlation
When using the logging package LoggerProvider, all logs automatically include trace_id and span_id fields, enabling you to:
- Click a span in Grafana → See all related logs
- Click a log → Jump to the trace
- Filter logs by trace ID
{
"level": "info",
"scope": "grpc.server",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"span_id": "00f067aa0ba902b7",
"rpc.system": "grpc",
"rpc.method": "/calculator.v1.CalculatorService/Add",
"rpc.grpc.status_code": 0,
"message": "gRPC /calculator.v1.CalculatorService/Add"
}When no OTelConfig is provided, the server runs without metrics or structured logging instrumentation. Health checks still work. To enable observability, provide an OTelConfig via WithOTelConfig().
GRPCPort: Port for gRPC server (required)HTTPPort: Port for HTTP gateway (required for separate mode)Mode: Server mode (H2CModeorSeparateMode)
ShutdownTimeout: Graceful shutdown timeout (default: 30s)ReadTimeout: HTTP read timeout (default: 5s)WriteTimeout: HTTP write timeout (default: 10s)IdleTimeout: HTTP idle timeout (default: 60s)MaxConnectionIdle: Max connection idle time (default: 15m)MaxConnectionAge: Max connection age (default: 30m)MaxConnectionAgeGrace: Connection age grace period (default: 5s)
EnableHealthCheck: Enable health check endpoints (default: true)HealthPath: Health check path (default: "/health")EnableReflection: Enable gRPC reflection (default: false)
EnableCORS: Enable CORS middleware (default: false)EnableRateLimit: Enable rate limiting middleware (default: false)RateLimit: Requests per second for rate limiting (default: 100.0)Middleware: Custom Echo middleware functions
GatewayBasePath: Base path for gRPC gateway routes (default: "/api/v1")
ServiceRegistrar: Function to register gRPC servicesGRPCConfigurer: Function to customize gRPC serverEchoConfigurer: Function to configure Echo server and add custom routesShutdown: Custom shutdown handler
The server provides comprehensive health check endpoints:
GET /health- Overall health statusGET /health/ready- Readiness probeGET /health/live- Liveness probe
server, err := grpcserver.New(config)
if err != nil {
log.Fatal(err)
}
healthManager := server.GetHealthManager()
// Register custom health check
healthManager.RegisterCheck("database", func() grpcserver.HealthCheckResult {
// Check database connectivity
return grpcserver.HealthCheckResult{
Status: grpcserver.HealthStatusUp,
Details: map[string]interface{}{
"connection": "active",
"latency": "5ms",
},
}
})
// Start the server
if err := server.Start(); err != nil {
log.Fatal(err)
}When OTelConfig is provided with a MeterProvider, the following OTel metrics are emitted:
rpc.server.request.count- Total gRPC requests by method and statusrpc.server.duration- Request duration histogram (ms)rpc.server.active_requests- Active concurrent gRPC requestsrpc.server.stream.count- Total gRPC streamsrpc.server.stream.duration- Stream duration histogram (ms)rpc.server.active_streams- Active concurrent streams
http.server.request.count- Total HTTP gateway requestshttp.server.request.duration- Request duration histogram (ms)http.server.active_requests- Active concurrent HTTP requests
server.uptime- Server uptime in secondsserver.start_time- Server start time as Unix timestamp
Metrics flow through the OTel pipeline (OTLP exporter → collector → backend).
Starts a server in H2C mode with default configuration.
grpcserver.Start("8080", func(s *grpc.Server) {
// Register services
})Explicitly starts a server in H2C mode.
grpcserver.StartH2C("8080", serviceRegistrar)Starts a server in separate mode with different ports for gRPC and HTTP.
grpcserver.StartSeparate("9090", "9091", serviceRegistrar)Starts a server with custom configuration.
config := grpcserver.DefaultConfig()
// Configure...
grpcserver.StartWithConfig(config)Creates a new server instance without starting it. Useful for advanced control and testing.
server, err := grpcserver.New(config)
if err != nil {
log.Fatal(err)
}
// Access managers before starting
healthManager := server.GetHealthManager()
// Start when ready
if err := server.Start(); err != nil {
log.Fatal(err)
}Returns a configuration with sensible defaults.
config := grpcserver.DefaultConfig()
config.GRPCPort = "50051"Main configuration struct with all server options. See Configuration Options section above.
Server instance with methods:
Start() error- Start the serverStop() error- Gracefully stop the serverGetHealthManager() *HealthManager- Get health check managerGetGRPCServer() *grpc.Server- Get underlying gRPC serverIsRunning() bool- Check if server is running
Server mode enumeration:
H2CMode- Single port HTTP/2 cleartext mode (gRPC + HTTP on same port)SeparateMode- Separate ports for gRPC and HTTP
Client Request → Port 8080
↓
HTTP/2 Cleartext Handler
↓
┌──────────┴──────────┐
↓ ↓
gRPC Server Echo HTTP Server
(application/grpc) (HTTP/1.1 & HTTP/2)
↓ ↓
Your Services - gRPC Gateway
- Health Checks
- Custom Routes
Client Request
↓
├─→ Port 9090 → gRPC Server → Your Services
│
└─→ Port 9091 → Echo HTTP Server → - gRPC Gateway
- Health Checks
- Custom Routes
The examples/ directory contains a complete calculator service demonstrating:
- Unary RPC: Basic request-response operations (Add, Subtract, Multiply, Divide)
- Server Streaming: Server sends multiple responses (Factorial)
- Client Streaming: Client sends multiple requests (Sum)
- Bidirectional Streaming: Both send multiple messages (RunningAverage)
- Echo Integration: Custom HTTP routes alongside gRPC
- Full Observability: Health checks and metrics
# Navigate to examples directory
cd examples
# Run the server
go run -tags examples cmd/server/main.go
# In another terminal, run the client
go run -tags examples cmd/client/main.go
# Test HTTP endpoints
curl http://localhost:50051/status
curl http://localhost:50051/health
curl http://localhost:50051/calculatorThe package includes comprehensive tests covering:
- Server lifecycle (start, stop, restart)
- Configuration validation
- Health check functionality
- OTel instrumentation
- H2C and Separate modes
- Graceful shutdown
Run tests:
go test ./...- Use H2C Mode for Development: Simplifies local testing with a single port
- Use Separate Mode for Production: Better isolation and flexibility
- Enable OTel Observability: Provide
OTelConfigwithMeterProviderandTracerProviderfor production - Configure Timeouts: Set appropriate timeouts based on your service requirements
- Use gRPC Reflection in Development: Makes testing with tools like grpcurl easier
- Disable Reflection in Production: Security best practice
- Add Custom Health Checks: Monitor critical dependencies (database, cache, etc.)
- Use Echo Middleware: Leverage Echo's rich middleware ecosystem
google.golang.org/grpc- gRPC frameworkgithub.com/grpc-ecosystem/grpc-gateway/v2- gRPC gatewaygithub.com/labstack/echo/v4- Echo HTTP frameworkgo.opentelemetry.io/otel- OpenTelemetry instrumentationgolang.org/x/net- HTTP/2 support
This package is part of the jasoet/pkg project.