Skip to content
wiki edited this page Sep 4, 2026 · 1 revision

rextension — the Rex extension contract

rextension is the module every Rex extension depends on instead of the framework. It declares the interfaces, and nothing implements them here.

go get github.com/kryovyx/rextension

Requires Go 1.27+. It has one dependency, encoding/json/v2 support in the standard library, and imports nothing from this ecosystem.

Why it exists

Module Contains Depends on
rextension interfaces and contracts nothing
rex the framework implementation rextension, dix
dix the DI container implementation nothing
rextension-* extensions rextension only

An extension that imported rex would drag the whole framework — router, event bus, logger, container — into every consumer, and would be recompiled by every change to any of it. Depending on the contract instead means an extension's module graph is one line long, and a change to rex internals cannot reach it.

rex re-exports the types declared here as aliases (rex.Extension is rextension.Extension), so application code that only imports rex never notices the split.

The surface, at a glance

LifecycleExtension with five hooks; RouteValidator for a startup check over the whole route table.

Framework accessRex: logger, container, event bus, route registration, router creation, four flavours of middleware registration.

MiddlewareMiddleware, PerRouteMiddleware, PerRouterMiddleware, and the fixed priority scale that orders the chain.

RoutesRoute, RouteInfo, BodySchema, BodyLimitedRoute.

Errors on the wireProblem, the RFC 9457 document every extension in the ecosystem answers with.

SecuritySecuritySchemeAccessor, SecuredRouteAccessor, SchemeRegistry and the optional capability interfaces, so the security and OpenAPI extensions cooperate without importing each other.

Browser originsOriginPolicy, written once and read by both CORS and CSRF.

DIContainer, Resolver, Scope, re-exported from rextension/di.

Events — the EventBus and the typed router events.

A minimal extension

package myext

import (
	"context"

	rx "github.com/kryovyx/rextension"
)

type Extension struct{}

func New() rx.Extension { return &Extension{} }

// The idiomatic entry point: an Option, so the application writes
// rex.New(myext.With()).
func With() rx.Option { return rx.WithExtension(New()) }

func (e *Extension) OnInitialize(ctx context.Context, r rx.Rex) error {
	r.Logger().Info("myext initialising")
	return nil
}

func (e *Extension) OnStart(ctx context.Context, r rx.Rex) error    { return nil }
func (e *Extension) OnReady(ctx context.Context, r rx.Rex) error    { return nil }
func (e *Extension) OnStop(ctx context.Context, r rx.Rex) error     { return nil }
func (e *Extension) OnShutdown(ctx context.Context, r rx.Rex) error { return nil }

Read Writing an Extension next — it covers the parts that are not obvious from the interface: where to register routes, why never to register from OnReady, and how to attach middleware to only the routes you apply to.

The one rule that shapes everything

Declare before Run; the route table is built once and then frozen.

Extensions declare routes, routers and middleware in OnInitialize and OnStart. The framework then builds every router's route table, composes every middleware chain, validates, and only then binds the listeners. After that the table is immutable and served without locking — so a route registered from OnReady is refused with ErrRouterFrozen, not silently dropped.

Everything else in this module follows from that: per-route middleware is a factory consulted at build time, RouteInfo carries the router name because build time is when that mapping is known, and RouteValidator runs at exactly the moment the whole table exists and no socket is open yet.

Clone this wiki locally