minato

package module
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Mar 31, 2026 License: MIT Imports: 20 Imported by: 0

README

Minato

Minato is an opinionated, feature-rich Go server framework for building production-ready HTTP and gRPC-gateway microservices with minimal boilerplate. It is built on net/http and chi, with lightweight core routing and additional overhead only when optional middleware is enabled.

Features

  • Graceful Shutdown: Built-in signal handling and dependency teardown.
  • Observability: Structured logging (log/slog), Request ID generation, and automatic Prometheus metrics.
  • Resilience: Panic recovery middleware to keep your server alive.
  • Health Checks: Automatic /healthz (liveness) and /readyz (readiness) endpoints with concurrent dependency checking.
  • Security: Highly configurable CORS middleware.
  • Routing: Clean, chi-backed routing with sub-router groups.
  • Generic Handlers: Type-safe HTTP handlers using Go generics (GenericHandler[TReq, TRes]) with auto-binding and validation.
  • gRPC Mode (Optional): Run gRPC (e.g. :9090) and HTTP/JSON gateway (e.g. :8080) in one process via grpc-gateway.
  • gRPC Error Ergonomics: Built-in merr package for HTTP-semantic gRPC errors (merr.NotFound, merr.BadRequest) and unified gateway JSON responses.
  • Pluggable Authentication: Built-in dual-protocol Auth middleware that seamlessly extracts tokens from HTTP headers and gRPC metadata into a unified validator closure.
  • Cross-Transport Middleware Plugins: Apply the same concern to HTTP middleware and gRPC interceptors with UsePlugin(...).

Installation

go get github.com/dwikynator/minato

Quick Start

Here is a minimal example of a Minato server with all the bells and whistles:

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	"github.com/dwikynator/minato"
	"github.com/dwikynator/minato/middleware"
)

func main() {
	// 1. Configure the server
	server := minato.New(
		minato.WithAddr(":8080"),
		minato.WithHealthCheck(),
		minato.WithMetrics(),
		minato.WithReadinessCheck("database", checkDatabase),
		minato.WithCloser("database", func() error {
			fmt.Println("Closing database connection...")
			return nil
		}),
	)

	// 2. Register Global Middleware
	server.Use(middleware.Recovery())
	server.Use(middleware.RequestID())
	server.Use(middleware.Logger(
		middleware.WithBodyLogging(true),
	))
	server.Use(middleware.CORS(
		middleware.WithAllowedOrigins("*"),
	))

	// 3. Register Routes
	server.Router().Get("/ping", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
	})

	// 4. Start the server (blocks until SIGINT/SIGTERM)
	if err := server.Run(); err != nil {
		panic(err)
	}
}

func checkDatabase(ctx context.Context) error {
	// Check connection here
	return nil
}

gRPC + HTTP Gateway Mode

Enable gRPC mode with WithGRPCAddr(...), register your gRPC services, and register generated gateway handlers.

package main

import (
	"log"

	"github.com/dwikynator/minato"
	greeterpb "github.com/dwikynator/minato/_example/grpc/grpc/greeter/v1"
	"github.com/dwikynator/minato/_example/grpc/handler"
	"github.com/dwikynator/minato/merr"
	"github.com/dwikynator/minato/middleware"
	"google.golang.org/grpc"
)

func main() {
	server := minato.New(
		minato.WithAddr(":8080"),     // HTTP gateway
		minato.WithGRPCAddr(":9090"), // gRPC server
		minato.WithGRPCReflection(),  // optional; useful for grpcurl/dev tooling
		minato.WithGatewayMuxOptions(merr.WithGatewayErrorHandler()), // unified JSON errors
	)

	// IMPORTANT: RecoveryPlugin MUST be registered first via UsePlugin.
	// Plugins are appended in order, and grpc-go executes interceptors
	// in registration order (first registered = outermost wrapper).
	// Recovery must be outermost to catch panics from ALL inner interceptors.
	server.UsePlugin(
		middleware.RecoveryPlugin(),
		middleware.RequestIDPlugin(),
		middleware.LoggerPlugin(),
	)
	server.Use(middleware.CORS()) // HTTP-only middleware

	// Auth interceptor protects both direct gRPC and HTTP gateway requests 
	// (the gateway automatically forwards the Authorization HTTP header to metadata)
	server.UseGRPC(middleware.AuthInterceptor(
		middleware.WithAuthSkipPaths("/greeter.v1.GreeterService/SayHello"),
		middleware.WithAuthValidator(func(ctx context.Context, token string) (context.Context, error) {
			// Inject your token verification logic here (e.g., JWT parse, Redis check)
			return ctx, nil
		}),
	))

	server.RegisterGRPC(func(s grpc.ServiceRegistrar) {
		greeterpb.RegisterGreeterServiceServer(s, handler.NewGreeterHandler())
	})
	server.RegisterGateway(greeterpb.RegisterGreeterServiceHandlerFromEndpoint)

	if err := server.Run(); err != nil {
		log.Fatal(err)
	}
}
Registering Multiple Services

Call RegisterGRPC and RegisterGateway once per service:

server.RegisterGRPC(func(s grpc.ServiceRegistrar) {
	userpb.RegisterUserServiceServer(s, userHandler)
})
server.RegisterGateway(userpb.RegisterUserServiceHandlerFromEndpoint)

server.RegisterGRPC(func(s grpc.ServiceRegistrar) {
	orderpb.RegisterOrderServiceServer(s, orderHandler)
})
server.RegisterGateway(orderpb.RegisterOrderServiceHandlerFromEndpoint)
Quick Verification
# REST via gateway
curl -s -X POST http://localhost:8080/v1/greet \
  -H "Content-Type: application/json" \
  -d '{"name":"Minato"}'

# Direct gRPC via reflection (when WithGRPCReflection is enabled)
grpcurl -plaintext \
  -d '{"name":"Minato"}' \
  localhost:9090 greeter.v1.GreeterService/SayHello

If reflection is disabled, grpcurl still works by providing proto descriptors:

grpcurl -plaintext \
  -import-path _example/grpc/proto \
  -import-path _example/grpc/proto/third_party/googleapis \
  -proto greeter.proto \
  -d '{"name":"Minato"}' \
  localhost:9090 greeter.v1.GreeterService/SayHello

Benchmark Methodology

Benchmark scope is HTTP in-process overhead only (not end-to-end network throughput).
The suite compares:

  • Baseline: net/http + chi
  • Minato bare routing
  • Minato production-style stack (RequestID, Recovery, CORS, Logger with no-op sink)

Run the suite and save the raw snapshot:

./scripts/bench-http.sh

This command runs:

go test -run=^$ -bench '^BenchmarkHTTP$' -benchmem -count=10 ./...

Raw output is committed at benchmarks/http/latest.txt.

Latest Results (median of 10 runs)

Environment:

  • Go 1.24.0
  • darwin/arm64 (Apple M1 Pro)
Case ns/op B/op allocs/op
Baseline chi static (GET /ping) 1858 6503 21
Minato bare static (GET /ping) 1848 6503 21
Minato stacked static (missing X-Request-ID) 2902 7309 37
Minato stacked static (present X-Request-ID) 2678 7630 38
Baseline chi param (GET /users/{id}) 2044 6864 23
Minato bare param (GET /users/{id}) 2038 6864 23
Minato stacked param (missing X-Request-ID) 3071 7670 39
Minato stacked param (present X-Request-ID) 2934 7990 40

Notes:

  • Minato bare tracks baseline closely in this in-process benchmark.
  • Stacked mode is intentionally higher overhead due to middleware features.
  • Treat these numbers as machine-specific; rerun the script for your environment.

Documentation

Detailed architectural deep-dives and implementation guides are available in the project repository:

License

MIT License

Documentation

Overview

Package minato provides an opinionated, feature-rich Go server framework for building production-ready HTTP and gRPC-gateway microservices with minimal boilerplate. It is built on top of standard library net/http and chi, with lightweight core routing and additional overhead only when optional middleware is enabled.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Delete added in v0.3.0

func Delete[Req any, Res any](r *Router, pattern string, h GenericHandlerFunc[Req, Res], opts ...RouteOption)

func Get added in v0.3.0

func Get[Req any, Res any](r *Router, pattern string, h GenericHandlerFunc[Req, Res], opts ...RouteOption)

Package-level route helpers. These are package-level (not methods) because Go does not support generic type parameters on methods of non-generic receiver types.

func Patch added in v0.3.0

func Patch[Req any, Res any](r *Router, pattern string, h GenericHandlerFunc[Req, Res], opts ...RouteOption)

func Post added in v0.3.0

func Post[Req any, Res any](r *Router, pattern string, h GenericHandlerFunc[Req, Res], opts ...RouteOption)

func Put added in v0.3.0

func Put[Req any, Res any](r *Router, pattern string, h GenericHandlerFunc[Req, Res], opts ...RouteOption)

Types

type BindError added in v0.3.0

type BindError struct {
	Field  string
	Source string
	Err    error
}

BindError is returned when coercion fails

func (*BindError) Error added in v0.3.0

func (e *BindError) Error() string

type ErrorMapper added in v0.3.0

type ErrorMapper func(ctx context.Context, err error) ErrorResponse

ErrorMapper translates a Go error into an HTTP-safe ErrorResponse

type ErrorResponse added in v0.3.0

type ErrorResponse struct {
	Status  int
	Body    any
	Headers http.Header
}

ErrorResponse dictates how an error is translated into an HTTP response.

type GRPCServiceFunc added in v0.2.0

type GRPCServiceFunc func(s grpc.ServiceRegistrar)

GRPCServiceFunc is a callback that registers a gRPC service implementation against the server's grpc.ServiceRegistrar.

type GatewayRegisterFunc added in v0.2.0

type GatewayRegisterFunc func(
	ctx context.Context,
	mux *runtime.ServeMux,
	endpoint string,
	opts []grpc.DialOption,
) error

GatewayRegisterFunc is a callback that registers a gRPC gateway handler produced by protoc-gen-grpc-gateway.

type GenericHandlerFunc added in v0.3.0

type GenericHandlerFunc[Req any, Res any] func(ctx context.Context, req Req) (Response[Res], error)

GenericHandlerFunc is the pure Go function signature for all business logic.

type Logger added in v0.1.1

type Logger interface {
	Info(msg string, args ...any)
	Error(msg string, args ...any)
}

Logger interface defines the methods required for custom loggin within Minato framework.

type Option

type Option func(*config)

Option defines a functional configuration option for the Minato Server.

func WithAddr

func WithAddr(addr string) Option

WithAddr sets the TCP address for the server to listen on. Defaults to ":8080"

func WithCloser

func WithCloser(name string, fn func() error) Option

WithCloser registers a teardown function that will be called during graceful shutdown.

func WithGRPCAddr added in v0.2.0

func WithGRPCAddr(addr string) Option

WithGRPCAddr sets the TCP address for the gRPC server and enables gRPC mode.

func WithGRPCReflection added in v0.2.0

func WithGRPCReflection() Option

WithGRPCReflection enables gRPC server reflection. Keep disabled by default and enable only when needed.

func WithGRPCServerOption added in v0.6.0

func WithGRPCServerOption(opts ...grpc.ServerOption) Option

WithGRPCServerOption appends one or more arbitrary gRPC ServerOptions. Useful for adding StatsHandlers or custom interceptor chains.

func WithGRPCStreamInterceptor added in v0.2.0

func WithGRPCStreamInterceptor(interceptors ...grpc.StreamServerInterceptor) Option

WithGRPCStreamInterceptor appends one or more stream interceptors to the gRPC server.

func WithGRPCUnaryInterceptor added in v0.2.0

func WithGRPCUnaryInterceptor(interceptors ...grpc.UnaryServerInterceptor) Option

WithGRPCUnaryInterceptor appends one or more unary interceptors to the gRPC server. Deprecated: prefer using WithGRPCServerOption with grpc.ChainUnaryInterceptor instead.

func WithGatewayMuxOptions added in v0.2.0

func WithGatewayMuxOptions(opts ...runtime.ServeMuxOption) Option

WithGatewayMuxOptions forwards options directly to runtime.NewServeMux.

func WithHealthCheck

func WithHealthCheck() Option

WithHealthCheck enables automatic registration of /healthz and /readyz endpoints.

func WithIdleTimeout

func WithIdleTimeout(d time.Duration) Option

WithIdleTimeout sets the maximum amount of time to wait for the next request when keep-alives are enabled. Defaults to 60 seconds.

func WithLogger added in v0.1.1

func WithLogger(l Logger) Option

WithLogger allows injecting a custom Logger implementation for the server. If not provided, it defaults to standard log/slog.

func WithMetrics

func WithMetrics() Option

WithMetrics enables automatic registration of the Prometheus /metrics endpoint.

func WithReadHeaderTimeout

func WithReadHeaderTimeout(d time.Duration) Option

WithReadHeaderTimeout sets the amount of time allowed to read request headers. Defaults to 5 seconds.

func WithReadinessCheck

func WithReadinessCheck(name string, fn func(ctx context.Context) error) Option

WithReadinessCheck registers a named dependency check for the /readyz endpoint.

func WithShutdownTimeout

func WithShutdownTimeout(d time.Duration) Option

WithShutdownTimeout sets the deadline for graceful shutdown. Defaults to 30 seconds.

type Plugin added in v0.2.0

type Plugin struct {
	HTTP func(http.Handler) http.Handler
	GRPC grpc.UnaryServerInterceptor
}

Plugin bundles the HTTP middleware form and the gRPC unary interceptor form of the same cross-cutting concern into a single registerable unit.

type Response added in v0.3.0

type Response[T any] struct {
	Data    T           `json:"-"`
	Status  int         `json:"-"`
	Headers http.Header `json:"-"`
}

Response is the envelope returned by a GenericHandlerFunc

func Created added in v0.3.0

func Created[T any](data T) Response[T]

Created returns a 201 Created response.

func NoContent added in v0.3.0

func NoContent() Response[struct{}]

NoContent returns a 204 No Content response.

func OK added in v0.3.0

func OK[T any](data T) Response[T]

OK returns a 200 OK response.

func (Response[T]) AddHeader added in v0.3.0

func (r Response[T]) AddHeader(key, value string) Response[T]

AddHeader appends a value to the given header key (useful for Set-Cookie)

func (Response[T]) SetHeader added in v0.3.0

func (r Response[T]) SetHeader(key, value string) Response[T]

SetHeader replaces any existing values for the given header key.

type RouteOption added in v0.3.0

type RouteOption func(*RouteOptions)

func WithErrorMapper added in v0.3.0

func WithErrorMapper(m ErrorMapper) RouteOption

WithErrorMapper replaces the default error-to-HTTP translation function for this route.

func WithMaxBodyBytes added in v0.3.0

func WithMaxBodyBytes(n int64) RouteOption

WithMaxBodyBytes limits the JSON request body size. Prevents oversized payload attacks. Example: minato.WithMaxBodyBytes(1 << 20) caps the body at 1 MiB

func WithStrictJSON added in v0.3.0

func WithStrictJSON(strict bool) RouteOption

WithStrictJSON enables json.Decoder.DisallowUnknownFields() Requests that contain JSON keys not present in Req are rejected with 400.

func WithValidator added in v0.3.0

func WithValidator(v Validator) RouteOption

WithValidator attaches a struct validator to this route

type RouteOptions added in v0.3.0

type RouteOptions struct {
	Validator    Validator
	Mapper       ErrorMapper
	MaxBodyBytes int64 // 0 = unlimited
	StrictJSON   bool  // reject unknown JSON fields
}

RouteOptions holds all per-route configuration for a generic handler adapter.

type Router

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

Router is a wrapper around chi.Mux to prevent exposing third-party router dependencies directly to library consumers.

func (*Router) Delete

func (r *Router) Delete(pattern string, h http.HandlerFunc)

Delete adds the route `pattern` that matches a DELETE HTTP method to route handler `h`

func (*Router) Get

func (r *Router) Get(pattern string, h http.HandlerFunc)

Get adds the route `pattern` that matches a GET HTTP method to route handler `h`

func (*Router) Group

func (r *Router) Group(pattern string, fn func(r *Router))

Group creates a new inline-router with a fresh middleware stack.

func (*Router) Mount added in v0.2.0

func (r *Router) Mount(pattern string, h http.Handler)

Mount attaches an http.Handler at the given pattern prefix. Used internally by gRPC mode to mount the grpc-gateway ServeMux

func (*Router) Patch

func (r *Router) Patch(pattern string, h http.HandlerFunc)

Patch adds the route `pattern` that matches a PATCH HTTP method to route handler `h`

func (*Router) Post

func (r *Router) Post(pattern string, h http.HandlerFunc)

Post adds the route `pattern` that matches a POST HTTP method to route handler `h`

func (*Router) Put

func (r *Router) Put(pattern string, h http.HandlerFunc)

Put adds the route `pattern` that matches a PUT HTTP method to route handler `h`

func (*Router) ServeHTTP

func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request)

ServeHTTP implements the standard net/http.Handler interface.

func (*Router) Use

func (r *Router) Use(middlewares ...func(http.Handler) http.Handler)

Use appends one or more middlewares onto the Router stack.

type Server

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

Server represents the Minato HTTP server instance, wrapping an internal http.Server and Router

func New

func New(opts ...Option) *Server

New creates a new Minato Server instance with the provided options.

func (*Server) RegisterGRPC added in v0.2.0

func (s *Server) RegisterGRPC(fn GRPCServiceFunc)

RegisterGRPC registers a gRPC service implementation.

func (*Server) RegisterGateway added in v0.2.0

func (s *Server) RegisterGateway(fn GatewayRegisterFunc)

RegisterGateway registers a generated grpc-gateway handler for HTTP<->gRPC translation.

func (*Server) Router

func (s *Server) Router() *Router

Router returns the underlying Minato Router for registering routes.

func (*Server) Run

func (s *Server) Run() error

Run starts the server. If a gRPC address is configured, it starts in gRPC mode with both gRPC and HTTP/REST endpoints. Otherwise, it starts in standard HTTP mode.

func (*Server) Use

func (s *Server) Use(middlewares ...func(http.Handler) http.Handler)

Use registers global middleware that will be applied to all routes.

func (*Server) UseGRPC added in v0.2.0

func (s *Server) UseGRPC(interceptors ...grpc.UnaryServerInterceptor)

UseGRPC appends gRPC unary interceptors directly (without an HTTP counterpart)

func (*Server) UsePlugin added in v0.2.0

func (s *Server) UsePlugin(plugins ...Plugin)

UsePlugin applies the HTTP form of each plugin to the HTTP middleware chain and the gRPC form to the gRPC interceptor chain.

type ValidationError added in v0.3.0

type ValidationError struct {
	Err error // the original error from the Validator implementation
}

ValidationError is a framework-owned wrapper applied by the adapter around any error returned by a pluggable Validator.

Why wrap? Minato's Validator interface is pluggable — the framework does not know which validation library (go-playground/validator, ozzo-validation, etc.) the developer will use, so it cannot type-assert against a library-specific error type directly inside defaultErrorMapper.

Instead, the adapter always wraps validation failures in *ValidationError before calling handleError. That way the mapper only ever needs to check for this one, stable, framework-owned type.

func (*ValidationError) Error added in v0.3.0

func (e *ValidationError) Error() string

func (*ValidationError) Unwrap added in v0.3.0

func (e *ValidationError) Unwrap() error

type Validator added in v0.3.0

type Validator interface {
	Validate(v any) error
}

Validator is a pluggable interface for struct validation

Directories

Path Synopsis
generic command
Package main demonstrates the Minato Generic Handler feature.
Package main demonstrates the Minato Generic Handler feature.
grpc command

Jump to

Keyboard shortcuts

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