-
Notifications
You must be signed in to change notification settings - Fork 0
Getting Started
go get github.com/kryovyx/rex
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( rex.WithConfig(&rex.Config{ DefaultRouter: rex.RouterConfig{Addr: ":8080"}, ShutdownTimeout: 30 * time.Second, }), rex.WithLogLevel(logger.LogLevelDebug), ) // Dependencies if err := app.Container().Singleton(func() *UserService { return &UserService{} }); err != nil { panic(err) } // Routes if err := app.RegisterRoute(route.New("GET", "/", func(ctx route.Context) { _ = ctx.JSON(200, map[string]string{"message": "Welcome to Rex"}) })); err != nil { panic(err) } if err := app.RegisterRoute(route.New("GET", "/greet/{name}", func(ctx route.Context) { var svc *UserService if err := ctx.Resolver().Resolve(&svc); err != nil { _ = ctx.JSON(500, map[string]string{"error": "unavailable"}) return } _ = ctx.Text(200, svc.Greet(ctx.Param("name"))) })); err != nil { panic(err) } if err := app.Run(); err != nil { panic(err) } }
$ curl localhost:8080/greet/ada
Hello, ada
RegisterRoute, RegisterRouteToRouter and CreateRouter all return errors,
and discarding them is the most common way to lose a route silently. A typo'd
path, a missing handler, a duplicate registration or an unknown router name is
rejected — and if you ignore the return value, the route simply never appears
and the 404 is a mystery.
If the if err := noise bothers you, batch it:
if err := errors.Join( app.RegisterRoute(route.New("GET", "/users", listUsers)), app.RegisterRoute(route.New("POST", "/users", createUser)), app.RegisterRoute(route.New("GET", "/users/{id}", getUser)), ); err != nil { return fmt.Errorf("routes: %w", err) }
All equivalent; pick one and be consistent.
// Options to New. app := rex.New(rex.WithConfig(cfg), rex.WithLogLevel(logger.LogLevelDebug)) // Fluent. app := rex.New(). WithOptions(rex.WithConfig(cfg)). WithLogger(logger.NewSlogLoggerWithLevel(logger.LogLevelDebug)) // After the fact — still fine, because nothing is built until Run. app := rex.New() app.WithOptions(rex.WithConfig(cfg))
import ( "github.com/kryovyx/rex" health "github.com/kryovyx/rextension-health" metric "github.com/kryovyx/rextension-metric" ) app := rex.New( health.WithHealth(nil), // /healthz, /readyz on a dedicated listener metric.WithMetrics(nil), // /metrics, plus request instrumentation )
Extensions declare their own routes, routers and middleware during Run. Order
of registration does not matter — middleware order comes from the
priority scale, not from argument order.
Run blocks until SIGINT, SIGTERM or Stop(). RunContext adds a fourth
exit: cancelling the context.
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel() if err := app.RunContext(ctx); err != nil { log.Fatal(err) }
The router is an http.Handler, so after Run you can drive it directly — but
Run blocks, so tests usually start it in a goroutine and use Stop:
func TestGreet(t *testing.T) { app := rex.New(rex.WithConfig(&rex.Config{ DefaultRouter: rex.RouterConfig{Addr: "127.0.0.1:0"}, // any free port })) if err := app.RegisterRoute(route.New("GET", "/greet/{name}", greet)); err != nil { t.Fatal(err) } errCh := make(chan error, 1) go func() { errCh <- app.Run() }() t.Cleanup(func() { _ = app.Stop() if err := <-errCh; err != nil { t.Errorf("run: %v", err) } }) // app.Routers()["default"].Addr() is the bound address. }
Addr: "127.0.0.1:0" binds a free port, and Router.Addr() reports the one
actually bound — so parallel tests do not collide.
Stop() on an instance that was never Run is safe and returns immediately.
-
Lifecycle — what happens between
Newand the first request - Routing — parameters, wildcards, 404 vs 405
- Configuration — every field, and what its default protects you from
Getting started
Core
Operating
Ecosystem