Skip to main content

Routing & middleware

pkg/router is Nucleus's HTTP layer. It is built on net/http, exposes its own Router and Context types, and ships an opinionated middleware chain.

Defining routes

r := a.Router

r.Get("/api/articles", listArticles)
r.Post("/api/articles", createArticle)
r.Get("/api/articles/{id}", showArticle)
r.Put("/api/articles/{id}", updateArticle)
r.Delete("/api/articles/{id}", deleteArticle)

r.Group("/admin/api", func(g *router.Group) {
g.Use(adminAuthMiddleware)
g.Get("/stats", adminStats)
})

Router.Mount(prefix, handler) mounts an arbitrary http.Handler — useful for embedding third-party handlers or a second app.

The Context type

Handlers receive a *router.Context (or, in fluent mode, a *nucleus.Context that wraps it). The context exposes:

  • Request / ResponseWriter
  • path parameters via c.Param("id")
  • query string helpers (c.Query, c.QueryInt, …)
  • body binding (c.BindJSON, c.BindXML, c.BindForm)
  • response helpers (c.JSON, c.XML, c.String, c.Status)
  • the request-scoped context.Context
  • the resolved request scope (site, tenant) when multi-site is on

Built-in middleware

The default middleware chain (full-stack mode) installs:

MiddlewarePurpose
RecoveryRecovers from panics, logs with stack trace.
Request IDGenerates / propagates an X-Request-ID.
Structured loggingEmits one slog line per request with timing.
OpenTelemetryWraps the handler in an OTel span (when enabled).
CORSConfigured from cors.* keys.
CSRFConfigured from csrf.* keys (form-based apps).
Rate limitingConfigured from rate_limit_* keys.
Request scopeResolves multi-site / multi-tenant context.

Each middleware is opt-out at the config level; none of them rely on hidden state. The order is fixed and documented — handlers can rely on the request having a logger, a request ID and a span by the time they run.

Custom middleware

func auditMiddleware(next router.Handler) router.Handler {
return router.HandlerFunc(func(c *router.Context) error {
start := time.Now()
err := next.ServeHTTP(c)
slog.InfoContext(c.Request.Context(),
"audit",
"method", c.Request.Method,
"path", c.Request.URL.Path,
"took", time.Since(start),
)
return err
})
}

r.Use(auditMiddleware)

router.Handler is a thin wrapper over http.Handler that returns an error. Errors bubble up to the recovery / logging middleware where they are translated into a JSON or HTML response according to the request Accept header.

Mounting an OpenAPI document

The runtime ships an explicit OpenAPI mount:

import "github.com/jcsvwinston/nucleus/pkg/openapi"

a.MountOpenAPI("/api/openapi.json", openapi.From(myDoc))

There is no auto-generation of the document from handler reflection — that path was deliberately not taken. The contract you ship is the one you wrote.