gem

package module
v0.0.11 Latest Latest
Warning

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

Go to latest
Published: Apr 27, 2026 License: MIT Imports: 21 Imported by: 0

README

GemRouter

GemRouter is a fast and minimal HTTP router for Go, built on top of httprouter with a clean middleware chain, structured logging via log/slog, and zero-alloc context pooling.

Features

  • Fast radix tree routing via httprouter
  • Zero-alloc context pool (sync.Pool)
  • Structured logging with log/slog — plug in any logger (zap, zerolog, etc.)
  • Built-in middlewares: CORS, Recovery, Logger, Timeout, Prometheus
  • Route groups with per-group middleware
  • Graceful shutdown out of the box
  • Sonic JSON (3-5x faster than encoding/json)
  • JSON type alias for ergonomic responses

Installation

go get github.com/LynxBytes/GemRouter

Requires Go 1.22+

Quick start

package main

import gemrouter "github.com/LynxBytes/GemRouter"

func main() {
    r := gemrouter.DefaultGemRouter()

    r.GET("/ping", func(ctx *gemrouter.GemContext) {
        ctx.ToJSON(200, gemrouter.JSON{"message": "pong"})
    })

    if err := GemRouter.Run(); err != nil {
        log.Fatalf("failed to run server: %v", err)
    }
}

Routers

Constructor Middlewares CORS
BasicGemRouter() Nothing ✓ default
DefaultGemRouter() CORS, Recovery, Logger ✓ default
NewGemRouter(configs...) Recovery configurable
r := gemrouter.NewGemRouter(
    gemrouter.WithPort("3000"),
    gemrouter.WithCorsDefault(),
    gemrouter.WithJSONLogger(os.Stdout, slog.LevelInfo),
)

HTTP methods

r.GET("/users/:id", handler)
r.POST("/users", handler)
r.PUT("/users/:id", handler)
r.PATCH("/users/:id", handler)
r.DELETE("/users/:id", handler)

Parameters

// path param
r.GET("/users/:id", func(ctx *gemrouter.GemContext) {
    id := ctx.Param("id")
    ctx.ToJSON(200, gemrouter.JSON{"id": id})
})

// query param
r.GET("/search", func(ctx *gemrouter.GemContext) {
    q := ctx.Query("q")
    ctx.String(200, q)
})

// wildcard
r.GET("/files/*path", handler)

JSON

// write
ctx.ToJSON(200, gemrouter.JSON{"user": "mario", "age": 30})

// read
var body CreateUserRequest
if err := ctx.FromJSON(&body); err != nil {
    ctx.ToJSON(400, gemrouter.JSON{"error": err.Error()})
    return
}

Validation

Built-in validator, no dependencies. Supports required, min=N, max=N, len=N, email.

r.POST("/users", func(ctx *gemrouter.GemContext) {
    var body CreateUserRequest
    if err := ctx.FromJSON(&body); err != nil {
        ctx.ToJSON(400, gemrouter.JSON{"error": err.Error()})
        return
    }

    v := gemrouter.NewValidator().
        Check("name",  body.Name,  "required,min=2,max=50").
        Check("email", body.Email, "required,email").
        Check("age",   body.Age,   "min=18,max=120")

    if !v.Valid() {
        ctx.ToJSON(400, gemrouter.JSON{"errors": v.Errors()})
        return
    }

    ctx.ToJSON(201, body)
})

Error response:

{
  "errors": [
    {"field": "email", "message": "must be a valid email"},
    {"field": "age",   "message": "must be at least 18"}
  ]
}
Rule Types Description
required any not empty or zero
min=N string, int, float64 min length / min value
max=N string, int, float64 max length / max value
len=N string exact length
email string valid email format

Middlewares

// global
r.Use(MyMiddleware)

// per route group
api := r.Group("/api", AuthMiddleware)
api.GET("/users", handler)

Writing a middleware:

func AuthMiddleware(next gemrouter.GemHandler) gemrouter.GemHandler {
    return func(ctx *gemrouter.GemContext) {
        token := ctx.Header("Authorization")
        if !isValid(token) {
            ctx.ToJSON(401, gemrouter.JSON{"error": "unauthorized"})
            return // chain stops here
        }
        next(ctx)
    }
}

Route groups

api := r.Group("/api")

v1 := api.Group("/v1", AuthMiddleware)
v1.GET("/users", getUsers)
v1.POST("/users", createUser)

v2 := api.Group("/v2", AuthMiddleware, RateLimitMiddleware)
v2.GET("/users", getUsersV2)

Built-in middlewares

// CORS
gemrouter.WithCors(&gemrouter.CorsConfig{
    AllowOrigins: []string{"https://example.com"},
    AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
    AllowHeaders: []string{"Content-Type", "Authorization"},
    AllowCredentials: true,
})

// Timeout
r.Use(gemrouter.Timeout(5 * time.Second))

// Prometheus metrics
r := gemrouter.NewGemRouter(
    gemrouter.WithPrometheus("/metrics"),
)

Logging

// text (default)
gemrouter.WithTextLogger(os.Stdout, slog.LevelInfo)

// JSON
gemrouter.WithJSONLogger(os.Stdout, slog.LevelInfo)

// custom slog logger
gemrouter.WithLogger(mySlogLogger)

// use logger inside handler
ctx.Logger.Info("user created", slog.String("id", user.ID))

Context store

// set/get arbitrary values across middlewares
ctx.Set("userID", "123")
val, ok := ctx.Get("userID")

// typed fields
ctx.Store.RequestID
ctx.Store.UserID

Cookies

ctx.SetCookie("session", token, 3600, "/", "", true, true)
val, err := ctx.Cookie("session")
ctx.DeleteCookie("session")

Custom handlers

r := gemrouter.NewGemRouter(
    gemrouter.WithNotFound(func(ctx *gemrouter.GemContext) {
        ctx.ToJSON(404, gemrouter.JSON{"error": "not found"})
    }),
    gemrouter.WithMethodNotAllowed(func(ctx *gemrouter.GemContext) {
        ctx.ToJSON(405, gemrouter.JSON{"error": "method not allowed"})
    }),
    gemrouter.WithHealth(func(ctx *gemrouter.GemContext) {
        ctx.ToJSON(200, gemrouter.JSON{"status": "ok"})
    }),
)

Graceful shutdown

Built-in. Run() listens for SIGINT and SIGTERM and shuts down cleanly.

r := gemrouter.NewGemRouter(
    gemrouter.WithShutdownTimeout(10 * time.Second),
)
r.Run() // blocks until signal

Benchmarks

BenchmarkRouter_Ping-11          15937568     74 ns/op      16 B/op    1 allocs/op
BenchmarkRouter_Param-11         12995856     93 ns/op      48 B/op    2 allocs/op
BenchmarkRouter_ParallelPing-11  64808960     24 ns/op      16 B/op    1 allocs/op
BenchmarkRouter_NoContent-11     31490965     39 ns/op       0 B/op    0 allocs/op
BenchmarkRoutes_500-11           15444356     76 ns/op      32 B/op    1 allocs/op

License

MIT

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ContextStore added in v0.0.6

type ContextStore struct {
	RequestID string
	UserID    string
	// contains filtered or unexported fields
}

func (*ContextStore) Get added in v0.0.6

func (store *ContextStore) Get(key string) (any, bool)

func (*ContextStore) Set added in v0.0.6

func (store *ContextStore) Set(key string, val any)

type CorsConfig added in v0.0.5

type CorsConfig struct {
	AllowOrigins     []string
	AllowMethods     []string
	AllowHeaders     []string
	ExposeHeaders    []string
	AllowCredentials bool
	MaxAge           int
}

type GemConfig added in v0.0.3

type GemConfig func(router *GemRouter)

func WithAddr added in v0.0.3

func WithAddr(addr string) GemConfig

func WithCors added in v0.0.5

func WithCors(cfg *CorsConfig) GemConfig

func WithCorsDefault added in v0.0.5

func WithCorsDefault() GemConfig

func WithHealth added in v0.0.3

func WithHealth(handler GemHandler) GemConfig

func WithIdleTimeout added in v0.0.7

func WithIdleTimeout(d time.Duration) GemConfig

func WithJSONLogger added in v0.0.8

func WithJSONLogger(w io.Writer, level slog.Level) GemConfig

func WithLogger added in v0.0.5

func WithLogger(l *slog.Logger) GemConfig

func WithMethodNotAllowed added in v0.0.8

func WithMethodNotAllowed(handler GemHandler) GemConfig

func WithMiddleware added in v0.0.3

func WithMiddleware(middleware Middleware) GemConfig

func WithMiddlewares added in v0.0.3

func WithMiddlewares(middlewares []Middleware) GemConfig

func WithName added in v0.0.11

func WithName(name string) GemConfig

func WithNotFound added in v0.0.3

func WithNotFound(handler GemHandler) GemConfig

func WithPort added in v0.0.3

func WithPort(port string) GemConfig

func WithPrometheus added in v0.0.6

func WithPrometheus(metricsPath string) GemConfig

func WithReadTimeout added in v0.0.7

func WithReadTimeout(d time.Duration) GemConfig

func WithShutdownTimeout added in v0.0.5

func WithShutdownTimeout(d time.Duration) GemConfig

func WithTextLogger added in v0.0.8

func WithTextLogger(w io.Writer, level slog.Level) GemConfig

func WithTrustedProxy added in v0.0.5

func WithTrustedProxy() GemConfig

func WithVersion added in v0.0.11

func WithVersion(version string) GemConfig

func WithWriteTimeout added in v0.0.7

func WithWriteTimeout(d time.Duration) GemConfig

type GemContext

type GemContext struct {
	Writer  http.ResponseWriter
	Request *http.Request
	Store   *ContextStore
	Logger  *slog.Logger
	Pattern string
	// contains filtered or unexported fields
}

func (*GemContext) Cookie added in v0.0.5

func (context *GemContext) Cookie(name string) (string, error)

func (*GemContext) Copy added in v0.0.5

func (context *GemContext) Copy() *GemContext

func (*GemContext) DeleteCookie added in v0.0.5

func (context *GemContext) DeleteCookie(name string)

func (*GemContext) FromJSON added in v0.0.5

func (context *GemContext) FromJSON(data any) error

func (*GemContext) Get

func (context *GemContext) Get(key string) (any, bool)

func (*GemContext) Header

func (context *GemContext) Header(key string) string

func (*GemContext) Method

func (context *GemContext) Method() string

func (*GemContext) NOTFOUND added in v0.0.3

func (context *GemContext) NOTFOUND()

func (*GemContext) NoContent

func (context *GemContext) NoContent(code int)

func (*GemContext) OK

func (context *GemContext) OK()

func (*GemContext) Param

func (context *GemContext) Param(key string) string

func (*GemContext) Path

func (context *GemContext) Path() string

func (*GemContext) Query

func (context *GemContext) Query(key string) string

func (*GemContext) RequestID added in v0.0.5

func (context *GemContext) RequestID() string

func (*GemContext) Set

func (context *GemContext) Set(key string, val any)

func (*GemContext) SetCookie added in v0.0.5

func (context *GemContext) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool)

func (*GemContext) Status

func (context *GemContext) Status(code int)

func (*GemContext) StatusCode

func (context *GemContext) StatusCode() int

func (*GemContext) String

func (context *GemContext) String(code int, text string)

func (*GemContext) ToJSON

func (context *GemContext) ToJSON(code int, data any)

type GemGroup

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

func (*GemGroup) DELETE

func (g *GemGroup) DELETE(pattern string, handler GemHandler)

func (*GemGroup) GET

func (g *GemGroup) GET(pattern string, handler GemHandler)

func (*GemGroup) Group added in v0.0.5

func (g *GemGroup) Group(prefix string, middlewares ...Middleware) *GemGroup

func (*GemGroup) PATCH

func (g *GemGroup) PATCH(pattern string, handler GemHandler)

func (*GemGroup) POST

func (g *GemGroup) POST(pattern string, handler GemHandler)

func (*GemGroup) PUT

func (g *GemGroup) PUT(pattern string, handler GemHandler)

func (*GemGroup) Use added in v0.0.5

func (g *GemGroup) Use(m Middleware)

type GemHandler

type GemHandler func(ctx *GemContext)

func Logger

func Logger(next GemHandler) GemHandler

func Recovery

func Recovery(next GemHandler) GemHandler

type GemRouter

type GemRouter struct {
	Addr string
	Port string

	NotFound         GemHandler
	MethodNotAllowed GemHandler
	Health           GemHandler
	// contains filtered or unexported fields
}

func BasicGemRouter added in v0.0.6

func BasicGemRouter() *GemRouter

func DefaultGemRouter added in v0.0.5

func DefaultGemRouter() *GemRouter

func NewGemRouter

func NewGemRouter(configs ...GemConfig) *GemRouter

func (*GemRouter) DELETE

func (r *GemRouter) DELETE(pattern string, handler GemHandler)

func (*GemRouter) GET

func (r *GemRouter) GET(pattern string, handler GemHandler)

func (*GemRouter) Group

func (r *GemRouter) Group(prefix string, middlewares ...Middleware) *GemGroup

func (*GemRouter) NoRoute

func (r *GemRouter) NoRoute(handler GemHandler)

func (*GemRouter) PATCH

func (r *GemRouter) PATCH(pattern string, handler GemHandler)

func (*GemRouter) POST

func (r *GemRouter) POST(pattern string, handler GemHandler)

func (*GemRouter) PUT

func (r *GemRouter) PUT(pattern string, handler GemHandler)

func (*GemRouter) Run

func (r *GemRouter) Run() error

func (*GemRouter) Use

func (r *GemRouter) Use(middleware Middleware)

type GemValidator added in v0.0.8

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

func NewValidator added in v0.0.8

func NewValidator() *GemValidator

func (*GemValidator) Check added in v0.0.8

func (v *GemValidator) Check(field string, value any, rules string) *GemValidator

func (*GemValidator) Errors added in v0.0.8

func (v *GemValidator) Errors() []ValidationError

func (*GemValidator) Valid added in v0.0.8

func (v *GemValidator) Valid() bool

type JSON added in v0.0.8

type JSON = map[string]any

type Middleware

type Middleware func(GemHandler) GemHandler

func Cors added in v0.0.5

func Cors(cfg *CorsConfig) Middleware

func Timeout added in v0.0.5

func Timeout(d time.Duration) Middleware

type ValidationError added in v0.0.8

type ValidationError struct {
	Field   string `json:"field"`
	Message string `json:"message"`
}

Jump to

Keyboard shortcuts

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