Skip to content

Navigation Menu

Sign in
Sign up

Latest commit

History

26 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Restful Extended eXperience (REX)

A lightweight, extension-based HTTP routing framework for Go with lifecycle management and event-driven architecture.

Go Version Coverage License

Overview

rex is a modular HTTP framework for Go built around extensions and lifecycle hooks:

  • Trie-based routing — Fast path matching with {param} path parameters
  • Extension system — Five lifecycle hooks (OnInitialize, OnStart, OnReady, OnStop, OnShutdown)
  • Event-driven architecture — Typed events for routing and request lifecycle
  • Dependency injection — Built-in integration with dix
  • Multi-router support — Run multiple listeners on different addresses
  • Graceful shutdown — Clean stop with configurable timeout
  • Pluggable loggingslog-backed default logger with structured fields

Installation

go get github.com/kryovyx/rex

Extension Ecosystem

Module Import Description
rextension github.com/kryovyx/rextension Minimal interface contracts for extension authors
rextension-cors github.com/kryovyx/rextension-cors Cross-Origin Resource Sharing
rextension-health github.com/kryovyx/rextension-health Health, readiness & liveness checks
rextension-metric github.com/kryovyx/rextension-metric Prometheus / OpenMetrics instrumentation
rextension-openapi github.com/kryovyx/rextension-openapi OpenAPI 3.1 spec generation
rextension-ratelimit github.com/kryovyx/rextension-ratelimit Global, per-router and per-endpoint rate limiting
rextension-security github.com/kryovyx/rextension-security Authentication middleware
rextension-swagger github.com/kryovyx/rextension-swagger Swagger UI serving
rextension-validation github.com/kryovyx/rextension-validation Request / response validation

Quick Start

package main
import (
 "github.com/kryovyx/rex"
 "github.com/kryovyx/rex/route"
)
func main() {
 app := rex.New()
 app.WithOptions(
 rex.WithConfig(&rex.Config{
 DefaultRouter: rex.RouterConfig{Addr: ":8080"},
 }),
 )
 app.RegisterRoute(route.New("GET", "/hello", func(ctx route.Context) {
 ctx.Text(200, "Hello, World!")
 }))
 if err := app.Run(); err != nil {
 panic(err)
 }
}

Core Concepts

Rex Interface

The top-level entry point combines framework control with the extension-facing surface:

type Rex interface {
 rextension.Rex // Logger, Container, EventBus, routing, middleware
 WithOptions(options ...Option) Rex // Apply functional options
 WithExtensions(ext ...Extension) rextension.Rex // Register extensions
 WithLogger(l logger.Logger) Rex // Set custom logger
 Run() error // Start the application
 Stop() error // Graceful shutdown
 Config() *Config // Read current config
}

The embedded rextension.Rex interface exposes what extensions need:

type Rex interface {
 Logger() Logger
 Container() dix.Container
 EventBus() EventBus
 Use(mw Middleware)
 RegisterRoute(rt Route) error
 RegisterRouteToRouter(rt Route, routerName string) error
 CreateRouter(name string, cfg RouterConfig) error
}

Routing

Routes are created with route.New(method, path, handler) and registered on the app or a named router.

// Basic routes
app.RegisterRoute(route.New("GET", "/users", listUsers))
app.RegisterRoute(route.New("POST", "/users", createUser))
app.RegisterRoute(route.New("GET", "/users/{id}", getUser))
// Named router
app.CreateRouter("admin", rex.RouterConfig{Addr: ":9090"})
app.RegisterRouteToRouter(route.New("GET", "/dashboard", adminDash), "admin")

Route Context

Every handler receives a route.Context:

type Context interface {
 context.Context
 ResponseWriter() http.ResponseWriter
 Request() *http.Request
 Resolver() dix.Resolver // Request-scoped DI
 Param(name string) string // Captured path parameter, e.g. Param("id")
 Respond(status int, contentType string, body interface{}) error
 Text(status int, v string) error
 JSON(status int, v interface{}) error
 OpenMetrics(status int, v interface{}) error
 SetValue(key, value interface{})
 GetValue(key interface{}) interface{}
}
func getUser(ctx route.Context) {
 var svc *UserService
 ctx.Resolver().Resolve(&svc)
 user := svc.FindByID(ctx.Param("id"))
 ctx.JSON(200, user)
}

Event System

The event bus lets extensions and application code react to framework events:

app.EventBus().Subscribe("router.route.registered", func(e event.Event) {
 app.Logger().Info("route registered")
})
app.EventBus().Subscribe("router.request.incoming", func(e event.Event) {
 app.Logger().Debug("incoming request")
})

Built-in event types:

Event Type Fired When
router.initialized Router trie is built
router.route.registered A route is added
router.request.incoming An HTTP request arrives
router.request.handled A request finishes
router.request.unresolved No matching route found

Dependency Injection

Rex wraps dix for DI. Services registered in the container are available in handlers via ctx.Resolver():

app.Container().Singleton(func() *Database {
 return &Database{DSN: "postgres://localhost/mydb"}
})
// A factory may take a dix.Resolver. For a Scoped registration it receives
// the *scope*, so any scoped dependency it resolves stays inside the same
// scope — closing over the container instead would resolve from the root,
// which returns dix.ErrScopedFromRoot.
app.Container().Scoped(func(r dix.Resolver) *UserRepo {
 var db *Database
 if err := r.Resolve(&db); err != nil {
 panic(err)
 }
 return &UserRepo{DB: db}
})
// Inside a handler
func listUsers(ctx route.Context) {
 var repo *UserRepo
 ctx.Resolver().Resolve(&repo)
 ctx.JSON(200, repo.FindAll())
}

Middleware

Middleware uses the standard Go signature func(http.Handler) http.Handler:

func LoggingMiddleware(next http.Handler) http.Handler {
 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 start := time.Now()
 next.ServeHTTP(w, r)
 log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
 })
}
app.Use(LoggingMiddleware)

Multi-Router

Run multiple HTTP listeners (e.g. public API + admin panel):

app := rex.New(
 rex.WithConfig(&rex.Config{
 DefaultRouter: rex.RouterConfig{Addr: ":8080"},
 }),
)
if err := app.CreateRouter("admin", rex.RouterConfig{Addr: ":9090"}); err != nil {
 return err
}
// default :8080
if err := app.RegisterRoute(route.New("GET", "/api/users", listUsers)); err != nil {
 return err
}
// admin :9090
if err := app.RegisterRouteToRouter(route.New("GET", "/dashboard", adminDash), "admin"); err != nil {
 return err
}

Registration returns an error. It is worth checking: a route with a typo'd path, a missing handler or a duplicate is rejected there, and discarding the return value means the route silently never appears.

Declaration order does not matter

New() collects; Run() builds. Configuration, routers, routes and middleware may be declared in any order before Run, and are resolved together when the route tables are built:

app := rex.New()
// Middleware before the router it applies to — fine.
app.Use(loggingMiddleware)
_ = app.CreateRouter("admin", rex.RouterConfig{Addr: ":9090"})
// A route before its router — also fine.
_ = app.RegisterRouteToRouter(route.New("GET", "/x", h), "metrics")
_ = app.CreateRouter("metrics", rex.RouterConfig{Addr: ":9091"})
// Configuration after everything else — still fine.
app.WithOptions(rex.WithConfig(cfg))
if err := app.Run(); err != nil { // everything is resolved here
 return err
}

What cannot be known until Run is reported from Run — an unknown router name, for instance, wrapped with the registration that named it. Anything checkable at the call site is returned there.

Configuration

type Config struct {
 DefaultRouter RouterConfig
 ShutdownTimeout time.Duration // default: 10s
}
type RouterConfig struct {
 Addr string // default: ":8080"
 BaseURL string // default: "/"
 ListenSSL bool // default: false — TLS is opt-in
 CertFile *string // path to TLS cert; nil = no TLS
 KeyFile *string // path to TLS key; nil = no TLS
 TLSConfig *tls.Config // takes precedence; also where mTLS is configured
 // Listener limits. Zero takes the default; negative disables.
 ReadHeaderTimeout time.Duration // default 10s — the Slowloris bound
 ReadTimeout time.Duration // default 30s
 WriteTimeout time.Duration // default 0 — unset, deliberately
 IdleTimeout time.Duration // default 120s
 MaxHeaderBytes int // default 1 MiB
 MaxBodyBytes int64 // default 4 MiB
}

WriteTimeout stays 0 on purpose: it is an absolute deadline on the whole response, so a non-zero value truncates SSE, long polling and large downloads. Slowloris is a read attack, closed by ReadHeaderTimeout.

SSLVerify was removed — it configured nothing. Client certificate verification is TLSConfig.ClientAuth plus TLSConfig.ClientCAs.

Apply configuration via options:

app.WithOptions(rex.WithConfig(&rex.Config{
 DefaultRouter: rex.RouterConfig{
 Addr: ":443",
 BaseURL: "/api",
 CertFile: ptr("cert.pem"),
 KeyFile: ptr("key.pem"),
 },
 ShutdownTimeout: 30 * time.Second,
}))

ListenSSL defaults to true. When both CertFile and KeyFile are provided the router binds with TLS. Set ListenSSL: false to force plain HTTP even with certificates present.

Logging

Rex ships with SlogLogger (backed by log/slog). No external dependencies required.

import "github.com/kryovyx/rex/logger"
// Use default
app := rex.New()
// Or set a custom level
app.WithLogger(logger.NewSlogLoggerWithLevel(logger.LogLevelDebug))

The logger interface:

type Logger interface {
 Info(format string, args ...interface{})
 Warn(format string, args ...interface{})
 Error(format string, args ...interface{})
 Debug(format string, args ...interface{})
 Trace(format string, args ...interface{})
 SetLogLevel(level LogLevel)
 WithField(key string, value interface{}) Logger
 WithFields(fields map[string]interface{}) Logger
 WithError(err error) Logger
}

Complete Example

package main
import (
 "time"
 "github.com/kryovyx/rex"
 "github.com/kryovyx/rex/logger"
 "github.com/kryovyx/rex/route"
)
type UserService struct{}
func (s *UserService) Greet(name string) string { return "Hello, " + name }
func main() {
 app := rex.New()
 app.WithLogger(logger.NewSlogLoggerWithLevel(logger.LogLevelDebug))
 app.WithOptions(rex.WithConfig(&rex.Config{
 DefaultRouter: rex.RouterConfig{Addr: ":8080"},
 ShutdownTimeout: 30 * time.Second,
 }))
 // DI
 app.Container().Singleton(func() *UserService { return &UserService{} })
 // Routes
 app.RegisterRoute(route.New("GET", "/", func(ctx route.Context) {
 ctx.JSON(200, map[string]string{"message": "Welcome to Rex!"})
 }))
 app.RegisterRoute(route.New("GET", "/greet/{name}", func(ctx route.Context) {
 var svc *UserService
 ctx.Resolver().Resolve(&svc)
 ctx.Text(200, svc.Greet(ctx.Param("name")))
 }))
 if err := app.Run(); err != nil {
 panic(err)
 }
}

Architecture

+-------------------------------------------------------------+
| Rex |
+-------------------------------------------------------------+
| Extensions Logger DI Container (dix) |
+-------------------------------------------------------------+
| Event Bus |
+-------------------------------------------------------------+
| Router 1 (default) Router 2 (admin) Router N ... |
| :8080 :9090 :<port> |
| Trie Routes Trie Routes Trie Routes |
+-------------------------------------------------------------+
Lifecycle:
 OnInitialize -> OnStart -> [listeners bind] -> OnReady
 ... serving ...
 OnStop -> [listeners close] -> OnShutdown

Upgrading

v0.2.1 → v0.3.0. MIGRATION.md is the upgrade guide for this module, written to stand alone — it carries the dependency-ordered go get sequence, every breaking change here, and what to verify afterwards. Other modules of the framework each have their own; that file links to them.

Contributing

The framework is in alpha, and external contributions open at v1.0.0. Until then pull requests will be closed unmerged — but issues are very welcome. Bug reports, questions and feature requests all feed into what v1.0.0 looks like.

See CONTRIBUTING.md for the rules that will apply, and COMMIT-CONVENTIONS.md for the commit format.

License

This project is licensed under the MIT License — see the LICENSE file for details.

Copyright

© 2026 Kryovyx

About

Restful Extended eXperience — a lightweight, extension-based HTTP routing framework for Go with lifecycle management, dependency injection and an event-driven architecture.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

AltStyle によって変換されたページ (->オリジナル) /