Skip to content

Getting Started

wiki edited this page Sep 4, 2026 · 1 revision

Getting started

go get github.com/kryovyx/rex

A complete application

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

Check the errors

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)
}

Three ways to configure

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))

Adding an extension

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.

Running with a context

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)
}

Testing an application

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.

Next

  • Lifecycle — what happens between New and the first request
  • Routing — parameters, wildcards, 404 vs 405
  • Configuration — every field, and what its default protects you from

Clone this wiki locally