Documentation
¶
Overview ¶
Package nexus is a thin framework over Gin that registers every endpoint (REST, GraphQL, WebSocket) into a central registry, traces every request into an in-memory event bus, and exposes both under /__nexus for tooling — notably the Vue dashboard.
nexus does NOT replace the caller's GraphQL layer: hand it a *graphql.Schema (typically built with github.com/paulmanoni/nexus/graph) and it mounts + introspects.
Index ¶
- Constants
- Variables
- func BindConfig(prefix string, dst any) error
- func Boot(opts ...Option)
- func BootFrom(path string, opts ...Option)
- func ClearConfigStoreForTest()
- func ClientIPFromCtx(ctx context.Context) string
- func ConfigVersion() string
- func EnvVars(path ...string) (map[string]string, error)
- func Get[T any](key string, defaults ...T) T
- func HasConfig(key string) bool
- func InstallConfigStore(values map[string]any, version string)
- func IsDev() bool
- func LintRuntimeFile(path string) ([]manifest.Issue, error)
- func MapCRUDError(err error) (status int, ok bool)
- func MustGet[T any](key string) T
- func OnConfigChange(key string, fn func(value any))
- func RegisterExtensionDecoder(name string, dec ExtensionDecoder)
- func RegisterGqlType(name string, t graphql.Input)
- func RegisteredExtensionNames() []string
- func RoutePrefix(p string) routePrefixOption
- func Run(cfg Config, opts ...Option)
- func ServerTLSConfig(certFile, keyFile, caFile string) (*tls.Config, error)
- func SetGraphStatus(ctx context.Context, code int)
- func UpdateConfigStore(values map[string]any, version string)
- func WithClientIP(ctx context.Context, ip string) context.Context
- type App
- func (a *App) AddStartupTask(t manifest.StartupTask)
- func (a *App) AllowIntrospection(c *gin.Context) bool
- func (a *App) Bus() *trace.Bus
- func (a *App) Cache() Cache
- func (a *App) ClientContributors() []ClientContributorRecord
- func (a *App) ClientHandler() *client.Handler
- func (a *App) Cron(name, schedule string) *CronBuilder
- func (a *App) DeclareCORS(c manifest.CORSBlock)
- func (a *App) DeclareEnv(e manifest.EnvVar)
- func (a *App) DeclareEnvProvider(p manifest.EnvProvider)
- func (a *App) DeclareEnvironment(e manifest.Environment)
- func (a *App) DeclareErrors(e manifest.ErrorsBlock)
- func (a *App) DeclareFile(f manifest.File)
- func (a *App) DeclareHooks(h manifest.Hooks)
- func (a *App) DeclareOverride(env string, ov manifest.Override)
- func (a *App) DeclareSecret(s manifest.Secret)
- func (a *App) DeclareService(s manifest.ServiceNeed)
- func (a *App) DeclareServiceProvider(p manifest.ServiceDependencyProvider)
- func (a *App) DeclareTLS(t manifest.TLSBlock)
- func (a *App) DeclareVolumeProvider(p manifest.VolumeProvider)
- func (a *App) EffectiveManifest() *manifest.Manifest
- func (a *App) Engine() *gin.Engine
- func (a *App) Environment() string
- func (a *App) GenerateDrivers() []GenerateDriver
- func (a *App) LoadDeployManifest(path string) error
- func (a *App) Metrics() metrics.Store
- func (a *App) OnResourceUse(target UseReporter)
- func (a *App) Plugins() []PluginRecord
- func (a *App) PrefixPath(p string) string
- func (a *App) RateLimiter() ratelimit.Store
- func (a *App) Register(r resource.Resource)
- func (a *App) RegisterClientContributor(rec ClientContributorRecord)
- func (a *App) RegisterGenerateDriver(drv GenerateDriver)
- func (a *App) RegisterPlugin(rec PluginRecord)
- func (a *App) Registry() *registry.Registry
- func (a *App) RoutePrefix() string
- func (a *App) Run(addr string) error
- func (a *App) Scheduler() *cron.Scheduler
- func (a *App) SchemaRefs() map[string]registry.NamedType
- func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (a *App) Service(name string) *Service
- func (a *App) SetClientAuthInfo(fn func() client.ExtractorInfo)
- func (a *App) SetValue(key, value any)
- func (a *App) Static(urlPrefix, dir string)
- func (a *App) UseVolume(v manifest.Volume)
- func (a *App) Value(key any) (any, bool)
- func (a *App) Version() string
- type AuthRouteOption
- type BoundHandler
- type Bus
- type CORSConfig
- type CORSConfigBlock
- type CRUDResolver
- type Cache
- type ClientContributorFunc
- type ClientContributorRecord
- type Config
- type CronBuilder
- type DashboardConfig
- type DashboardConfigBlock
- type DashboardHiddenOption
- type DatabaseSpec
- type DevReloadConfig
- type DevReloadConfigBlock
- type EndpointGate
- type ErrorRenderer
- type ExtensionDecoder
- type FrontendOption
- type GenerateContext
- type GenerateDriver
- type GeneratedFile
- type GqlField
- type GqlOption
- func Deprecated(reason string) GqlOption
- func Desc(s string) GqlOption
- func GraphMiddleware(name, description string, mw graph.FieldMiddleware) GqlOption
- func Middleware(name, description string, mw graph.FieldMiddleware) GqlOptiondeprecated
- func OnService[S any]() GqlOption
- func Op(name string) GqlOption
- func RateLimit(l ratelimit.Limit) GqlOption
- func WithArgValidator(arg string, vs ...graph.Validator) GqlOption
- type GraphQLConfig
- type GraphQLConfigBlock
- type HandlerShape
- type ListOptions
- type Listener
- type ListenerConfigBlock
- type ListenerScope
- type MemoryStore
- type MiddlewareConfig
- type MiddlewareConfigBlock
- type MiddlewareOption
- type NexusResourceProvider
- type Notifier
- type Option
- func AddStartupTask(t manifest.StartupTask) Option
- func AsCRUD[T any](resolver any, opts ...Option) Option
- func AsMutation(fn any, opts ...GqlOption) Option
- func AsQuery(fn any, opts ...GqlOption) Option
- func AsRest(method, path string, fn any, opts ...RestOption) Option
- func AsRestHandler(method, path string, factory any, opts ...RestOption) Option
- func AsSubscription(fn any, opts ...GqlOption) Option
- func AsWS(path, msgType string, fn any, opts ...WSOption) Option
- func AsWorker(name string, fn any) Option
- func ClientUse(cfg client.Config) Option
- func ClientUseWithContributions(cfg client.Config, buildFactory func(*App) client.ContributionsBuilder) Option
- func DeclareEnv(e manifest.EnvVar) Option
- func DeclareEnvList(es []manifest.EnvVar) Option
- func DeclareService(s manifest.ServiceNeed) Option
- func IfDev(opts ...Option) Option
- func IfNotDev(opts ...Option) Option
- func Invoke(fns ...any) Option
- func LoadDotenvIfPresent(path ...string) Option
- func LoadExtensionOptions(path ...string) ([]Option, error)
- func LoadField[Parent any, Key comparable, Child any](fieldName string, keyFn func(Parent) Key, fetch any) Option
- func Managed[T any](name string, build func(*zap.Logger) (*T, error), ...) Option
- func Module(name string, opts ...Option) Option
- func MustLoadDotenv(path ...string) Option
- func MustLoadExtensions(path ...string) []Option
- func Options(opts ...Option) Option
- func Provide(fns ...any) Option
- func Raw(opt fx.Option) Option
- func ServeFrontend(fsys fs.FS, root string, opts ...FrontendOption) Option
- func Supply(values ...any) Option
- func UseVolume(v manifest.Volume) Option
- func WithGraphQL() Option
- func WithoutREST() Option
- type Page
- type Params
- type PathOpt
- type PluginRecord
- type PublicOption
- type RateLimitConfigBlock
- type ResponseRenderer
- type RestOption
- type RuntimeConfigBlock
- type ServerConfig
- type ServerConfigBlock
- type Service
- func (s *Service) AtGraphQL(path string) *Service
- func (s *Service) Attach(r resource.Resource) *Service
- func (s *Service) Auth(fn UserDetailsFn) *Service
- func (s *Service) Describe(desc string) *Service
- func (s *Service) GraphQLPath() string
- func (s *Service) MountGraphQL(path string, schema *graphql.Schema, opts ...gql.Option)
- func (s *Service) Name() string
- func (s *Service) REST(method, path string) *rest.Builder
- func (s *Service) Using(names ...string) *Service
- func (s *Service) UsingDefaults() *Service
- func (s *Service) WebSocket(path string) *ws.Builder
- type Store
- type StoreConfig
- type TabRecord
- type UseReporter
- type UserDetailsFn
- type UserError
- type WSOption
- type WSSession
- func (s *WSSession) ClientID() string
- func (s *WSSession) Context() context.Context
- func (s *WSSession) Emit(eventType string, data any)
- func (s *WSSession) EmitToClient(eventType string, data any, clientIDs ...string)
- func (s *WSSession) EmitToRoom(eventType string, data any, room string)
- func (s *WSSession) EmitToUser(eventType string, data any, userIDs ...string)
- func (s *WSSession) JoinRoom(room string)
- func (s *WSSession) LeaveRoom(room string)
- func (s *WSSession) Metadata() map[string]any
- func (s *WSSession) Send(eventType string, data any) error
- func (s *WSSession) SendRaw(data []byte)
- func (s *WSSession) UserID() string
Constants ¶
const AuthFlowTag = "auth.flow"
AuthFlowTag is the registry.Endpoint.Tags key used by AuthRoute to mark which framework auth flow ("login" | "logout" | "me") an endpoint belongs to. The client SDK reads this tag to expose top-level auth.login() / auth.logout() / auth.me() calls so users don't have to know the underlying route shape — the framework surfaces it via convention without owning the handlers themselves.
const DefaultConfigPath = "nexus.toml"
DefaultConfigPath is the conventional file LoadConfig + MustLoadConfig read from when no explicit path is provided. Resolved relative to the binary's working directory, matching the rest of the framework's "look in cwd" defaults (lockfile, deploy manifest).
const DefaultGraphQLPath = "/graphql"
DefaultGraphQLPath is the mount path nexus.AsQuery / AsMutation use when a service hasn't called AtGraphQL.
const DefaultManifestPath = "nexus.toml"
DefaultManifestPath is the file the framework auto-loads from the current working directory at boot. Operators who need a different path call `app.LoadDeployManifest("alt-path.toml")` from their own nexus.Invoke — that runs AFTER auto-load and the framework's declarations are idempotent (last writer wins on each block) so explicit overrides land cleanly.
const DotenvDefaultPath = ".env"
DotenvDefaultPath is the file LoadDotenvIfPresent reads when no explicit path is supplied. Matches the .env convention every Go, Node, Python, and Ruby developer already knows from dotenv / direnv / docker-compose.
const EnvAdminToken = "NEXUS_ADMIN_TOKEN" // #nosec G101 -- env var name, not a credential value
EnvAdminToken is the env var the framework reads for the admin token gating GET /__nexus/manifest. Empty value (or unset) leaves the endpoint unmounted — fail-closed so a forgotten orchestrator config doesn't expose declared services / env / crons.
const GqlFieldGroup = "nexus.graph.fields"
GqlFieldGroup is the single fx value-group name every reflective GraphQL registration feeds. fxmod's auto-mount Invoke reads this group, partitions entries by ServiceType, and mounts one schema per service.
const NexusDevEnv = "NEXUS_DEV"
NexusDevEnv signals dev mode to the framework. When set to "1", ServeFrontend reads files from disk (os.DirFS) instead of the supplied embed.FS, so a watching frontend toolchain (vite build --watch, esbuild --watch) can refresh the served bundle without recompiling Go. nexus dev sets it on the spawned process env.
const NexusDevRootEnv = "NEXUS_DEV_ROOT"
NexusDevRootEnv overrides the disk root used in dev mode. Defaults to "." (the binary's CWD), which matches how //go:embed paths are declared. nexus dev sets it to the resolved target directory so users running from a non-project CWD still resolve correctly.
const PublicTag = "auth.public"
PublicTag is the registry.Endpoint.Tags key set by Public(). When an extension installs a default endpoint gate (deny-by-default auth), endpoints carrying this tag are exempted from it.
Variables ¶
var ( ErrCRUDNotFound = errors.New("crud: not found") ErrCRUDConflict = errors.New("crud: conflict") ErrCRUDValidation = errors.New("crud: validation error") )
Sentinel errors that AsCRUD maps to HTTP status codes:
ErrCRUDNotFound → 404 ErrCRUDConflict → 409 ErrCRUDValidation → 400
Stores wrap or return these so transport-level mapping stays in one place. Anything else maps to 500.
Functions ¶
func BindConfig ¶ added in v0.75.0
BindConfig decodes the snapshot subtree at prefix into dst via encoding/json (same shape rules as Unmarshal — tags, omitempty, etc.). Returns an error when the subtree shape doesn't match dst.
var payment PaymentConfig
if err := nexus.BindConfig("config.payment", &payment); err != nil { ... }
func Boot ¶ added in v1.12.4
func Boot(opts ...Option)
Run builds and runs the app. Blocks until SIGINT/SIGTERM, then gracefully shuts the HTTP server + cron scheduler. Returns nothing — identical to fx.App.Run(). For tests where you need explicit Start/Stop control, build the app via a test helper that calls fxBootOptions.
func main() {
nexus.Run(
nexus.Config{Addr: ":8080", EnableDashboard: true},
nexus.Provide(NewDBManager),
advertsModule,
)
}
When NEXUS_FX_QUIET=1 is set in the environment, fx's startup log (PROVIDE/INVOKE/HOOK lines) is suppressed. The splitter sets this in subprocesses so the prefixed log streams don't drown in fx scaffolding noise; users hitting framework-level issues can unset it for full diagnostics. Boot loads nexus.toml automatically — the runtime Config, every [extensions.*] block, the [env] bridge, and the nexus.Get base layer — then runs the app. It's the zero-boilerplate form of:
cfg := nexus.MustLoadConfig() opts := nexus.MustLoadExtensions() nexus.Run(cfg, append(opts, userOpts...)...)
so main() collapses to:
func main() {
nexus.Boot(
nexus.ServeFrontend(webFS, "web/dist"),
billing.Module,
)
}
A missing nexus.toml is tolerated (zero Config, no extensions) so apps without one still boot; a malformed one panics, matching the MustLoad* helpers. Override the path with the NEXUS_CONFIG env var, or call BootFrom(path, opts...).
Run stays available for apps that build Config in Go or want explicit control over load order — Boot is sugar over it. Note that extension PACKAGES still need their blank import (Go links only imported code); Boot removes the load calls, not the imports.
func ClearConfigStoreForTest ¶ added in v0.75.0
func ClearConfigStoreForTest()
ClearConfigStoreForTest unwinds InstallConfigStore. Test-only escape hatch — production never calls this.
func ClientIPFromCtx ¶ added in v0.3.0
ClientIPFromCtx pulls the IP stashed via WithClientIP (or ratelimit.WithClientIP). Empty when absent.
func ConfigVersion ¶ added in v0.75.0
func ConfigVersion() string
ConfigVersion returns the version stamp of the current snapshot, or "" when no store is installed. Useful for log breadcrumbs ("starting on config version X").
func EnvVars ¶ added in v1.11.0
EnvVars reads the [env] table from a nexus.toml and returns the flattened dotted name→value map (e.g. {"client.id": "ajira_portal-web"}), with ${VAR} placeholders expanded. Path defaults to DefaultConfigPath. The CLI uses it to expose the same values to the frontend build/dev server; the runtime publishes them as env vars via LoadConfig. Returns (nil, nil) when the file is absent or has no [env] table.
func Get ¶ added in v0.75.0
Get is the unified config accessor. Walks the active config store at the dotted-path key, converts to T, returns the result. With no default, returns T's zero value when the key is missing or the conversion fails. With a default, returns the default in those cases. At most one default is consulted.
addr := nexus.Get[string]("config.server.addr")
port := nexus.Get[int]("config.server.port", 8080)
ttl := nexus.Get[time.Duration]("config.cache.ttl", 5*time.Minute)
Resolution priority, highest first:
- Environment variable (CONFIG_SERVER_PORT for "config.server.port")
- Config-extension snapshot (config.Server / .Client / .Local)
- nexus.toml base layer (seeded by LoadConfig/Boot)
- The default arg (or T's zero value)
Layers 2 and 3 resolve per-key: a key absent from the extension snapshot falls through to the nexus.toml layer rather than returning the default. So nexus.Get reads any key declared in nexus.toml — the dotted key mirrors the TOML table path, e.g. [runtime.storage] url → Get("runtime.storage.url") — with NO config extension wired. The extension snapshot (when installed) overrides nexus.toml for keys it carries, since it's runtime-managed and hot-reloadable.
The nexus.toml layer lands when MustLoadConfig/LoadConfig/Boot runs (typically the first line of main); the extension snapshot lands a little later, at app start. Calls before either is installed return the default (or zero).
func HasConfig ¶ added in v0.75.0
HasConfig reports whether key resolves to a non-nil value in the current snapshot or as an ENV override.
func InstallConfigStore ¶ added in v0.75.0
InstallConfigStore installs the process-wide config store with the given initial values. Called once by extension/config at boot. Multiple installations in one process is a configuration error caught here (the second installer panics with a clear message).
Public so extension/config can call it across package boundaries. User code does NOT call this directly — the extension's Server/Client/Local entrypoints drive it.
func IsDev ¶ added in v0.84.0
func IsDev() bool
IsDev reports whether the current process is running under `nexus dev` (or anywhere else that set NEXUS_DEV=1). Public so operator code can branch the same way without re-checking the env var by hand.
Production binaries never see NEXUS_DEV=1 — `nexus build` strips it from the codegen output and operator-launched systemd / docker / k8s units don't set it. Treating it as the dev-mode signal is a one-direction guarantee.
func LintRuntimeFile ¶ added in v0.97.1
LintRuntimeFile reads nexus.toml at path and returns lint issues for its runtime block. Used by `nexus lint` to validate runtime config alongside the deploy manifest's inputs surface — operators get one command that catches both classes of misconfiguration.
Files without a runtime block return (nil, nil) — empty issue list, no error. This lets `nexus lint` call us unconditionally on every nexus.toml without having to pre-check whether the file uses the runtime feature.
Missing file returns os.ErrNotExist so callers can decide whether to treat absent as warning or hard error.
Validation rules:
- Listener scope must be "public" / "admin" / "internal" / "" (empty = public default). Other values fail loudly.
- Server.Addr + Listener.Addr must parse as a valid host:port. Empty Addr is OK (framework default applies).
- CORS.max_age must parse as a Go duration string ("12h", "300s"). Empty is OK.
- RateLimit.RPM and Burst must be non-negative. Zero disables rate limiting; negative is operator error.
- IntrospectionNetworks must be valid CIDRs. Wrong values here would crash at boot, so catching them at lint time is the point.
- GraphQL.DocumentCacheSize must be non-negative.
- Environment string is informational; we don't constrain to a known-good list — operators use any naming scheme.
func MapCRUDError ¶ added in v0.21.28
MapCRUDError translates a sentinel error into the right HTTP status. Handler return errors flow through Gin's standard JSON error path, but a transport-level wrapper can use this to attach the right code without each Store having to know about HTTP.
Currently unused by AsRest's default 500 path; reserved for the next pass when we wire the sentinel mapping into the framework's error renderer.
func MustGet ¶ added in v0.75.0
MustGet is Get without a fallback — panics if the key is missing or can't be converted to T. Use when absence is a boot-time bug, not a runtime condition.
signKey := nexus.MustGet[string]("config.signing.key")
func OnConfigChange ¶ added in v0.75.0
OnConfigChange registers a callback that fires when the value at key changes between snapshots. The callback receives the new value typed as any; cast to the concrete type at the receiver. Callbacks run synchronously on the snapshot-apply goroutine — keep them cheap.
Use for hot-reload: feature flags, timeouts, rate limits. Registrations made before app start are queued and replayed once the store installs.
nexus.OnConfigChange("config.api.timeout", func(v any) {
if d, ok := v.(time.Duration); ok { srv.timeout.Store(d) }
})
func RegisterExtensionDecoder ¶ added in v1.0.0
func RegisterExtensionDecoder(name string, dec ExtensionDecoder)
RegisterExtensionDecoder makes name a recognized [extensions.<name>] block in nexus.toml. The decoder gets the raw TOML bytes of just that sub-tree.
Idiomatic usage: each extension's init() registers itself:
package myext
func init() {
nexus.RegisterExtensionDecoder("myext", decode)
}
func decode(raw []byte) ([]nexus.Option, error) {
var c MyExtConfig
if err := toml.Unmarshal(raw, &c); err != nil { return nil, err }
return []nexus.Option{Plugin(c)}, nil
}
Calling RegisterExtensionDecoder twice with the same name replaces the prior decoder — operators with conflicting extension imports get the LAST registration, which is usually what they want when testing with a stub.
Names are case-sensitive. Convention is lowercase kebab (`"myext"`, `"oauth2"`, `"rate-limit"`) matching the TOML idiom; the framework doesn't enforce this.
func RegisterGqlType ¶ added in v0.63.0
RegisterGqlType makes a named GraphQL input type available to flat-args resolvers. Call this once at startup (typically inside a module's init or the app's bootstrap). Re-registering the same name overwrites the previous entry — useful in tests but otherwise a smell.
func RegisteredExtensionNames ¶ added in v1.0.0
func RegisteredExtensionNames() []string
RegisteredExtensionNames returns the sorted list of every extension name a decoder is registered for. Useful for lint + diagnostic output ("declared: [a, b]; available: [a, b, c]").
func RoutePrefix ¶ added in v0.7.5
func RoutePrefix(p string) routePrefixOption
RoutePrefix prepends a string to the paths of the REST endpoints it applies to. Two usage patterns:
// Module-wide: every AsRest / AsRestHandler in the module sees "/api/v1".
nexus.Module("adverts", nexus.RoutePrefix("/api/v1"),
nexus.AsRest("GET", "/adverts", NewListAdverts), // → /api/v1/adverts
nexus.AsRest("POST", "/adverts", NewCreateAdvert),
)
// Per-endpoint:
nexus.AsRest("GET", "/health", NewHealth, nexus.RoutePrefix("/ops"))
The prefix is stored verbatim — include (or omit) the leading slash yourself; nexus does not normalize. Stacking within a single Module concatenates left-to-right, so `Module(..., RoutePrefix("/a"), RoutePrefix("/b"), AsRest("GET", "/x", ...))` mounts at /a/b/x. Prefixes do NOT stack across nested Module calls — the inner module already stamped its children before the outer sees them. If you need stacking, compose the prefix string explicitly.
func ServerTLSConfig ¶ added in v0.74.0
ServerTLSConfig builds a *tls.Config for a server-terminating Listener. certFile and keyFile are required (PEM-encoded server cert + key). caFile is optional: when set, the listener requires clients to present a certificate signed by that CA (mTLS), and 1.3 handshakes for clients without one will fail at the TLS layer before any HTTP request is dispatched. Pass "" to skip client auth.
Defaults: TLS 1.2 minimum (1.0/1.1 are deprecated and unsafe); modern cipher suite selection left to Go's defaults, which track the IETF recommended list.
cfg, err := nexus.ServerTLSConfig("admin.crt", "admin.key", "admin-ca.crt")
if err != nil { log.Fatal(err) }
listener := nexus.Listener{Addr: "10.0.0.5:9443", Scope: nexus.ScopeAdmin, TLS: cfg}
func SetGraphStatus ¶ added in v0.20.0
SetGraphStatus overrides the HTTP status code for the current GraphQL request. Call from a graph.FieldMiddleware (the Graph realization of a middleware.Middleware bundle) or from a resolver to translate a decision into a non-200 response code:
authMw := middleware.Middleware{
Name: "auth",
Graph: func(p graphql.ResolveParams, next graphql.FieldResolveFn) (any, error) {
if !authed(p.Context) {
nexus.SetGraphStatus(p.Context, http.StatusUnauthorized)
return nil, errors.New("unauthorized")
}
return next(p)
},
}
Without this call the framework returns 200 OK with errors in the GraphQL response body — the GraphQL-spec default. When called multiple times within one request, the LAST value wins.
No-op when ctx didn't pass through the framework's GraphQL adapter — useful for resolver code under test with a bare graphql.Do call.
Re-export of gql.SetStatusCode so user code stays on the `nexus.` import without pulling in the transport package.
func UpdateConfigStore ¶ added in v0.75.0
UpdateConfigStore swaps in a new value tree + version, triggering OnConfigChange callbacks for keys whose values changed. Called by config.Client on every successful refresh and by config.Local's reload path (phase 2).
func WithClientIP ¶ added in v0.3.0
WithClientIP is a thin pass-through to ratelimit.WithClientIP so nexus callers can thread IP into context without importing the lower-level ratelimit package. Kept here for API consistency with other nexus helpers.
Types ¶
type App ¶
type App struct {
// contains filtered or unexported fields
}
func AppFromGin ¶ added in v1.15.3
AppFromGin returns the *App associated with the current request, set by the framework before a ResponseRenderer runs. It lets a renderer reach per-app state (e.g. App.Value) that can't be threaded through the Render(c, result) signature. Returns (nil, false) outside a renderer-bearing request.
func New ¶
New constructs an *App from a single Config. The canonical (and, since v0.18, only) public constructor — both nexus.Run and the lower-level "build app, then app.Run(addr)" pattern feed through here.
Behavior:
- Manifest-derived defaults (codegen'd by `nexus build --deployment X`) fill any zero Config field; explicit Config fields always win.
- The framework owns engine creation, scope-filter middleware, /__nexus/health + /ready, the cache + ratelimit + metrics stores, and (when EnableDashboard is true) the dashboard mount.
- Listeners with empty Addrs auto-fill from the resolved Config.Addr (admin = port+1000, internal = port+2000).
- Global rate limit and global middlewares are installed at the engine root in registration order.
The returned *App is fully constructed and safe to register endpoints/services on, but listeners aren't bound until Run is invoked (either nexus.Run or App.Run for direct callers).
func (*App) AddStartupTask ¶ added in v0.22.1
func (a *App) AddStartupTask(t manifest.StartupTask)
AddStartupTask registers a one-shot task that runs before listeners bind. Migrations and other pre-start side-effecting work belong here. The Run function is opaque to print mode (manifest only surfaces Name + Description + Phase), so a print-mode invocation never actually executes Run — it just lists the task so the orchestration platform knows one is expected.
The Run function IS executed in normal boot mode, sequenced by runStartupTasks at the head of the lifecycle OnStart hook. Failure halts boot with the task name surfaced in the error.
func (*App) AllowIntrospection ¶ added in v0.30.1
AllowIntrospection reports whether the request c is permitted to see schema-shape data — drives both the dashboard gate and the GraphQL adapter's __schema-query block. Returns true when Config.Introspection is true OR the TCP peer falls within Config.IntrospectionNetworks. Exposed so transport adapters outside the nexus package (notably gql) can consult one source of truth.
func (*App) ClientContributors ¶ added in v0.54.0
func (a *App) ClientContributors() []ClientContributorRecord
ClientContributors returns a snapshot of every registered codegen contributor. Order matches registration order; the returned slice is a copy so callers can iterate without holding the lock.
func (*App) ClientHandler ¶ added in v0.28.0
ClientHandler returns the live *client.Handler when the client SDK is mounted (Config.Client.Enabled OR a nexus.ClientUse option fired in the chain), nil otherwise. Used by auth.Module's option chain to wire the AuthInfo bridge without forcing app code to thread the manager through Mount manually.
func (*App) Cron ¶ added in v0.3.0
func (a *App) Cron(name, schedule string) *CronBuilder
Cron starts building a scheduled job. Finalize with .Handler(fn). Jobs run bare — middleware chains do not apply — but the scheduler still emits request.start / request.end trace events so they appear on the dashboard Traces tab.
app.Cron("refresh-pets", "*/5 * * * *").
Describe("Warm the pet cache").
Handler(func(ctx context.Context) error { return nil })
func (*App) DeclareCORS ¶ added in v0.52.0
DeclareCORS sets the cross-origin policy block read by the extension/cors plugin at boot. Idempotent — last call wins.
func (*App) DeclareEnv ¶ added in v0.22.1
DeclareEnv records one env var the app reads. Safe to call from any fx.Invoke — typically from a module-level nexus.DeclareEnv option, which expands to an invoke that calls this. Empty Name is silently dropped to keep callers from having to guard zero values when they build env lists from a slice.
func (*App) DeclareEnvProvider ¶ added in v0.22.1
func (a *App) DeclareEnvProvider(p manifest.EnvProvider)
DeclareEnvProvider records a provider whose NexusEnv() is called at manifest assembly time. Use when a module's env list is data-driven (e.g. one EnvVar per registered DB connection); use DeclareEnv directly when the list is static.
func (*App) DeclareEnvironment ¶ added in v0.40.0
func (a *App) DeclareEnvironment(e manifest.Environment)
DeclareEnvironment records a deploy target ("production", "staging", "preview", ...). The orchestration platform consults the list to know which environments the binary is built for; the active one (Config.Environment / NEXUS_ENVIRONMENT) is matched against this set at boot.
func (*App) DeclareErrors ¶ added in v0.52.0
func (a *App) DeclareErrors(e manifest.ErrorsBlock)
DeclareErrors sets the error-capture configuration block read by the extension/errors plugin at boot. Idempotent — last call wins.
func (*App) DeclareFile ¶ added in v0.40.0
DeclareFile records a mounted-blob input — TLS bundle, JSON config override, etc. The platform writes the bytes to Path at deploy time. Empty Name or Path is silently dropped.
func (*App) DeclareHooks ¶ added in v0.40.0
DeclareHooks sets the platform-orchestrated build/predeploy/ postdeploy commands. Idempotent within a single boot: subsequent calls fully replace the previous Hooks block (rather than accumulating) — typical use is one call from the top-level main() with the full set.
func (*App) DeclareOverride ¶ added in v0.40.0
DeclareOverride registers a per-environment Override against the declared inputs. env must match a previously-declared Environment; validation happens at merge time via manifest.MergeOverrides, not here, so the declaration order doesn't matter.
Calling DeclareOverride twice for the same env REPLACES the previous diff — there's no merging at registration time. This matches operator intent: one module owns the prod override for a given env, not a chain of modules each contributing slices.
func (*App) DeclareSecret ¶ added in v0.40.0
DeclareSecret records one sensitive input the app reads. Distinct from DeclareEnv so the platform's secret store can manage it separately (encrypted at rest, redacted in UI, rotation reminders). Empty Name is silently dropped.
func (*App) DeclareService ¶ added in v0.22.1
func (a *App) DeclareService(s manifest.ServiceNeed)
DeclareService records a backing-service dependency (Postgres, Redis, RabbitMQ, etc.) the orchestration platform should provision and bind. The ExposeAs map drives env-var fill-in: when the platform binds the sidecar, it sets each named env var to the corresponding field of the resolved sidecar.
func (*App) DeclareServiceProvider ¶ added in v0.22.1
func (a *App) DeclareServiceProvider(p manifest.ServiceDependencyProvider)
DeclareServiceProvider is the data-driven counterpart to DeclareService.
func (*App) DeclareTLS ¶ added in v0.52.0
DeclareTLS sets the public-internet TLS configuration block read by the extension/tls plugin at boot. Idempotent within a single boot: subsequent calls fully replace the previous block.
func (*App) DeclareVolumeProvider ¶ added in v0.22.1
func (a *App) DeclareVolumeProvider(p manifest.VolumeProvider)
DeclareVolumeProvider is the data-driven counterpart to UseVolume.
func (*App) EffectiveManifest ¶ added in v0.40.0
EffectiveManifest returns the merged + validated manifest produced at boot. Returns nil before fx.Start completes — callers serving /__nexus/manifest fall back to manifest.Build(manifestInputs()) so print mode and pre-boot inspection still work.
func (*App) Environment ¶ added in v0.40.0
Environment returns the resolved environment name ("production", "staging", "preview", ...) the binary is booting into. Set from Config.Environment or NEXUS_ENVIRONMENT; never empty.
func (*App) GenerateDrivers ¶ added in v0.54.0
func (a *App) GenerateDrivers() []GenerateDriver
GenerateDrivers returns a snapshot of every registered codegen driver. The v1 contract caps the slice at length one; the accessor returns a slice anyway so future relaxations don't break callers. Order matches registration order; the returned slice is a copy.
func (*App) LoadDeployManifest ¶ added in v0.42.0
LoadDeployManifest reads nexus.toml at path, parses its inputs surface (environments / secrets / files / hooks / environment_overrides), and registers each entry through the existing Declare* methods. Idempotent: re-declaring an environment with the same name is a no-op, secrets / files dedup by name, and DeclareOverride replaces any prior diff for the same environment.
Intended call sites:
- Directly in main() before nexus.Run, when the operator wants YAML to be the source of truth for the inputs surface.
- From a nexus.Invoke(...) for apps that want the inputs to participate in fx ordering.
- From a codegen'd init() emitted by `nexus build` (future extension — the codegen path that currently bakes deployment defaults will also bake DeclareEnvironment / DeclareSecret / DeclareOverride calls so the runtime stays file-IO-free).
YAML parse errors return immediately. Schema validation (duplicate names, malformed validation rules) is NOT performed here — call manifest.Lint() on the result manifest to surface those issues. Boot validation against actual env values still runs at fx.Start via resolveEffectiveManifest.
Missing file is treated as an error so a typo in the path doesn't silently produce an empty inputs surface. To make the call optional, stat the file first or wrap in a guard.
func (*App) OnResourceUse ¶
func (a *App) OnResourceUse(target UseReporter)
OnResourceUse installs an auto-attach hook onto any UseReporter (typically a *multi.Registry or a user wrapper around one). Whenever code calls target.UsingCtx(ctx, "resource-name") during a request, the hook:
- reads the current trace.Span from ctx so we know which service made the call
- AttachResource(service, resource) on the registry — edge appears live
- emits a "downstream" trace event so the Traces tab shows the lookup
Calls with no span in context (e.g. UsingCtx fired from main or a cron job outside the trace middleware) are silently ignored — there's no service to attribute the usage to.
func (*App) Plugins ¶ added in v0.38.0
func (a *App) Plugins() []PluginRecord
Plugins returns a snapshot of every registered plugin. Order matches registration order; the returned slice is a copy, safe to mutate.
func (*App) PrefixPath ¶ added in v0.21.15
PrefixPath returns p with the deployment route prefix prepended. Mount sites use this so a single binary can be served behind a path-based ingress without each registration re-implementing the prepend. Both inputs are expected to start with "/"; the helper is a noop when the prefix is empty.
func (*App) RateLimiter ¶ added in v0.3.0
func (*App) Register ¶
Register adds a resource (database, cache, queue) to the app so its health shows up on the dashboard. Use Service.Attach(r) to also draw an edge between the owning service(s) and the resource.
func (*App) RegisterClientContributor ¶ added in v0.54.0
func (a *App) RegisterClientContributor(rec ClientContributorRecord)
RegisterClientContributor records a per-plugin codegen contribution. Called by extension.Use when a Plugin declares a Contributor slot. Many contributors may be registered (one per plugin); the active Generate driver invokes them in registration order and merges their output into the rendered tree. Duplicate plugin names are allowed (re-registration appends — useful in tests; the in-process driver invokes both copies, which matches "last write wins" for files sharing a Path).
func (*App) RegisterGenerateDriver ¶ added in v0.54.0
func (a *App) RegisterGenerateDriver(drv GenerateDriver)
RegisterGenerateDriver records a codegen driver on the app. Called by extension.Use when a Plugin declares a Generate slot. Exactly one driver per app is allowed — a second registration panics to surface misconfiguration at boot rather than at `nexus build` time. The driver carries the owning plugin's name so error messages can point at the source.
func (*App) RegisterPlugin ¶ added in v0.38.0
func (a *App) RegisterPlugin(rec PluginRecord)
RegisterPlugin records plugin metadata on the app. Called by extension.Use during fx.Start. Duplicate Names overwrite — the last registration wins so test harnesses can re-wire plugins in place.
func (*App) RoutePrefix ¶ added in v0.21.15
RoutePrefix returns the deployment-wide path prefix applied to every user-mounted route (REST, GraphQL, WebSocket). Empty when no manifest `prefix:` (or Config.Server.RoutePrefix) was set.
func (*App) SchemaRefs ¶ added in v0.28.0
SchemaRefs is the public accessor for the deduped named-type pool. Used by the client SDK package (client/) at manifest-build time to populate the Refs section. Returns the live map; callers MUST NOT mutate.
func (*App) SetClientAuthInfo ¶ added in v0.28.0
func (a *App) SetClientAuthInfo(fn func() client.ExtractorInfo)
SetClientAuthInfo installs the manifest's auth-section provider on the mounted SDK handler. No-op when the client SDK isn't mounted (apps that don't ship the SDK pay nothing). Designed for auth.Module's option chain to call via fx.Invoke once both auth.Manager and the SDK are in scope.
func (*App) SetValue ¶ added in v1.15.3
SetValue stashes a key/value on the app. Extensions use it to record boot-time state (typically in an fx.Invoke) that they must read back at request time — without depending on gin-middleware install ordering, which fx.Module route registration can run ahead of. Safe for concurrent use.
func (*App) Static ¶ added in v1.12.3
Static serves the files in the local directory dir at the URL prefix urlPrefix — app.Static("/media", "media") makes ./media/cat.png reachable at /media/cat.png. The prefix is mounted under the deployment route prefix when one is set.
This is the guarded counterpart to Engine().Static(). gin panics at boot when a static mount registers a /*filepath catch-all at the root, which is what happens whenever the prefix is blank or "/": the catch-all collides with framework routes (the /__nexus dashboard, your API, the SPA fallback) and takes the whole app down. A blank/root prefix is exactly what an unset config value yields — e.g. nexus.Get on a key that isn't in the config store returns "" — so the failure is easy to hit by accident.
Static refuses to crash for it: a blank, root, or /__nexus-shadowing prefix is logged and skipped, the app keeps booting, and the misconfiguration shows up in the logs instead of a panic stack. A valid prefix mounts exactly like Engine().Static.
func (*App) UseVolume ¶ added in v0.22.1
UseVolume records a writable path that must persist across restarts. The orchestration platform mounts a persistent volume at each declared path. Set Shared=true when the path must be visible to every replica (e.g. uploads dir read by all instances) — single- replica apps can leave it false.
type AuthRouteOption ¶ added in v0.31.0
type AuthRouteOption struct {
// contains filtered or unexported fields
}
AuthRouteOption is the cross-transport carrier returned by AuthRoute. Implements both RestOption and GqlOption so the same expression can flow through AsRest, AsQuery, or AsMutation without callers caring which transport the endpoint lives on.
Mirrors the pattern in nexus.Use / MiddlewareOption: a single concrete value satisfying multiple per-transport apply interfaces, with each transport reading what it needs.
func AuthRoute ¶ added in v0.28.0
func AuthRoute(flow string) AuthRouteOption
AuthRoute marks the endpoint as part of one of the framework-aware auth flows. Three values are recognized today:
"login" endpoint that exchanges credentials for a token /
session cookie. The SDK calls it via auth.login(creds).
"logout" endpoint that revokes the active session. The SDK calls
it via auth.logout() and clears its locally-stored
token on success.
"me" endpoint returning the current Identity. The SDK calls
it via auth.me() to bootstrap an existing session on
page load (cookie-based apps) or check token freshness
(bearer apps).
Apps stay in control of the actual handler — AuthRoute is purely a metadata marker. Works on both REST (AsRest) and GraphQL ops (AsQuery, AsMutation). The SDK auto-dispatches to the right transport based on the manifest's recorded transport tag:
nexus.AsRest("POST", "/api/login", NewLogin, nexus.AuthRoute("login"))
nexus.AsMutation(NewLogin, nexus.AuthRoute("login"))
nexus.AsQuery(NewMe, nexus.AuthRoute("me"))
AuthRoute does not gate access — combine with auth.Optional() (for /me on bearer apps) or auth.Required() (for /logout) as needed.
Unknown flow values are accepted but ignored by the SDK. The recognized set may grow in future framework versions; the tag is the contract.
type BoundHandler ¶ added in v0.74.0
BoundHandler is the closure form an extension dispatcher invokes per request. ctx is the per-call context (with deadlines, trace IDs, etc.), args is a value assignable to ArgsType (typically a freshly-decoded struct value).
The returned any is the handler's first return value; the error is its second. Nil-pointer-or-interface results collapse to a nil any, matching the existing graphql / REST conventions so downstream marshallers don't have to special-case them.
type Bus ¶ added in v0.69.0
type Bus interface {
// Publish broadcasts a topic to every node subscribed via
// Subscribe. Empty topic means the global "everything
// changed" signal (equivalent to Notify with no topic).
Publish(topic string) error
// Subscribe returns a channel that receives topic strings
// published by peers. Cancel detaches the subscription;
// callers should close the bus separately when done.
//
// Single-subscriber model: implementations typically return
// the same channel for every call (the Notifier is the only
// expected consumer).
Subscribe() (<-chan string, func(), error)
// Close releases the bus's resources (network connections,
// pubsub subscriptions). Idempotent.
Close() error
}
Bus is the cross-process transport for notifier events. Implementations bridge a local Notifier to peers across pods so a mutation on node A reaches sessions connected to node B.
Implementations live in sub-packages so importing live doesn't pull every backend's transitive deps:
- live/bus/redis — Redis pub/sub (go-redis)
- live/bus/nats — NATS (planned)
Implementations are responsible for any node-identity filtering (a publisher receiving its own message is allowed but wasteful); AttachBus on the Notifier side coalesces duplicates via the existing buffer-1 channel semantics.
type CORSConfig ¶ added in v0.19.0
type CORSConfig struct {
// AllowOrigins lists allowed Origin header values verbatim.
// Use "*" for "any origin" — note that AllowCredentials cannot
// be true with "*" per the CORS spec; the middleware will
// downgrade to echoing the request's Origin in that case.
// Empty defaults to ["*"].
AllowOrigins []string
// AllowMethods lists HTTP methods allowed on cross-origin
// requests. Empty defaults to GET, POST, PUT, PATCH, DELETE,
// OPTIONS — covers every method nexus handlers register.
AllowMethods []string
// AllowHeaders lists request headers the browser is allowed to
// send. Empty defaults to Origin, Content-Type, Accept,
// Authorization, X-Requested-With.
AllowHeaders []string
// ExposeHeaders lists response headers the browser is allowed
// to read from JavaScript. Empty omits the header (browser
// only sees the safelisted response headers).
ExposeHeaders []string
// AllowCredentials sets Access-Control-Allow-Credentials: true
// when an origin matches. Required when the SPA sends cookies
// or Authorization headers cross-origin.
AllowCredentials bool
// MaxAge caches the preflight response for this duration.
// Zero defaults to 12 hours — enough to amortize the OPTIONS
// round-trip across a session, conservative enough that policy
// changes propagate within a workday.
MaxAge time.Duration
}
CORSConfig declares the framework's built-in CORS policy. All fields are optional; reasonable defaults fill in for the common "allow my SPA's origin to hit my API" case.
type CORSConfigBlock ¶ added in v0.97.0
type CORSConfigBlock struct {
AllowOrigins []string `toml:"allow_origins"`
AllowMethods []string `toml:"allow_methods"`
AllowHeaders []string `toml:"allow_headers"`
ExposeHeaders []string `toml:"expose_headers"`
AllowCredentials bool `toml:"allow_credentials"`
MaxAge string `toml:"max_age"` // duration string, e.g. "12h"
}
CORSConfigBlock is the TOML shape of CORSConfig.
type CRUDResolver ¶ added in v0.21.28
CRUDResolver is the per-request Store resolver passed to AsCRUD. Returns the Store the framework should use for this request — for multi-tenant apps, scope the Store to the tenant pulled from ctx.
The resolver runs once per request before the action handler. An error short-circuits the request as a 500 (or as the mapped sentinel status when the error wraps one of the ErrCRUD* constants).
func MemoryResolver ¶ added in v0.21.28
func MemoryResolver[T any](getID func(*T) string, setID func(*T, string)) CRUDResolver[T]
MemoryResolver returns a CRUDResolver that always yields the same process-wide MemoryStore[T]. Construction inspects T via reflection to locate ID accessors:
- explicit get/set if both non-nil
- struct field named "ID" otherwise (case-sensitive, kind == String)
Boot fails with a clear error if neither path resolves.
The most common AsCRUD call:
nexus.AsCRUD[User](nexus.MemoryResolver[User](nil, nil))
type Cache ¶ added in v1.4.0
type Cache interface {
Get(ctx context.Context, key string, out any) error
Set(ctx context.Context, key string, value any, ttl time.Duration) error
Delete(ctx context.Context, key string) error
Clear(ctx context.Context) error
}
Cache is the minimal interface the framework core needs from a cache tier. It lives in the root package so the core never imports a concrete cache implementation — keeping Redis, gocache, and Prometheus OUT of the dependency graph of an app that doesn't actually wire a cache.
The concrete *cache.Manager in extension/cache satisfies this interface. Wire one declaratively with cache.Bind[T](...) (the cache counterpart to nexus.Database / pubsub.Broker), or hand the framework an existing one via Config.Stores.Cache. App.Cache() returns whatever was wired, or nil.
type ClientContributorFunc ¶ added in v0.54.0
type ClientContributorFunc func(GenerateContext) ([]GeneratedFile, error)
ClientContributorFunc is the per-plugin callback that turns the active GenerateContext into a list of files a Generate driver should merge into its output tree. The contract mirrors GenerateDriver.Render but scoped to one plugin's contribution rather than the whole tree.
Errors propagate up to the driver's Render — partial writes never reach disk, so a misbehaving contributor cleanly aborts the run.
type ClientContributorRecord ¶ added in v0.54.0
type ClientContributorRecord struct {
PluginName string
Contribute ClientContributorFunc
}
ClientContributorRecord pairs a contributor's callback with the plugin that registered it. The plugin name powers error reporting (the driver wrapper attributes failures to a specific contributor) and keeps registration order observable for tests.
type Config ¶ added in v0.3.0
type Config struct {
// Server bundles every network-binding knob: the single-listener
// fallback Addr, and the explicit Listeners map for multi-scope
// deployments. Both fields are optional; the framework supplies a
// :8080 default when both are empty.
//
// nexus.Config{
// Server: nexus.ServerConfig{
// Listeners: map[string]nexus.Listener{
// "public": {Addr: ":8080"},
// "admin": {Addr: "127.0.0.1:7000", Scope: nexus.ScopeAdmin},
// },
// },
// }
Server ServerConfig
// Dashboard bundles the /__nexus surface knobs (whether it
// mounts at all, the brand label). Middleware that gates the
// dashboard lives under Middleware.Dashboard so all middleware
// configuration stays in one place.
//
// nexus.Config{
// Dashboard: nexus.DashboardConfig{Enabled: true, Name: "MyApp"},
// }
Dashboard DashboardConfig
// Client bundles the auto-generated JS/TS client SDK knobs —
// whether the SDK routes mount at all, what URL prefix they
// land under, and any middleware that gates them. Mirrors the
// Dashboard knob; defaults to disabled so apps that don't ship
// an SDK pay no embed cost.
//
// When Enabled, four routes appear under cfg.Client.Path
// (default "/__nexus/client"):
//
// GET <path>/manifest.json SDK-tailored, public
// GET <path>/client.js runtime ESM
// GET <path>/client.d.ts generated TS types
// GET <path>/vue.js Vue 3 composables
//
// For per-deployment gating (ship the SDK only from the
// public-facing service), use nexus.IfDeployment([...],
// nexus.ClientUse(...)) instead of setting Client.Enabled at
// the Config level — IfDeployment composes with the option
// chain while Config is a static value across the binary.
Client client.Config
// SDK is the one-switch front door to the typed client SDK —
// PocketBase-style. Set it (Config.SDK or `[runtime] sdk = true`
// in nexus.toml) and nexus generates + serves the full typed
// client covering REST, GraphQL, and WebSocket — and, when a
// frontend dir is present, dumps the SDK files + wires tsconfig so
// `import 'nexus-client'` resolves with types. No client.Config
// ceremony, no manifest wiring.
//
// Gating (security): SDK only takes effect under `nexus dev`
// (NEXUS_DEV=1) OR when Introspection is true. A locked-down
// production binary (introspection off, not under dev) ignores it,
// so the API surface is never exposed by this flag alone. The file
// dump additionally requires a detected frontend dir, so production
// binaries serve SDK routes (when introspection is on) without
// writing to a project tree that isn't there.
//
// Equivalent to enabling Client with the full manifest + frontend
// defaults; for finer control (custom Path, middleware gating,
// explicit OutDir) set Client directly instead.
SDK bool
// TraceCapacity is the ring-buffer size for request traces. 0 disables
// tracing — the Traces tab will stay empty.
TraceCapacity int
// DevReload tunes the dev-mode live-reload file watcher, which
// runs only under NEXUS_DEV=1. Production builds never start the
// watcher, so this field is inert there.
DevReload DevReloadConfig
// GraphQL bundles every environment-level GraphQL knob that
// applies across all services' mounted schemas. Set once on the
// app, not per-service.
//
// nexus.Config{
// GraphQL: nexus.GraphQLConfig{
// Path: "/api/graphql",
// Pretty: true,
// },
// }
GraphQL GraphQLConfig
// Middleware bundles every middleware-related knob: engine-root
// stacks, dashboard gating, and the built-in global rate limit.
//
// nexus.Config{
// Middleware: nexus.MiddlewareConfig{
// Global: []middleware.Middleware{requestID, logger, cors},
// Dashboard: []middleware.Middleware{bearerAuth, requireAdminRole},
// RateLimit: ratelimit.Limit{RPM: 600, Burst: 50},
// },
// }
Middleware MiddlewareConfig
// Stores groups the framework's pluggable backends for state
// nexus needs to keep around — rate-limit counters, metrics
// rings, the general-purpose cache. All optional; the framework
// supplies sensible defaults (in-memory / cache-backed) when
// fields are zero. Set explicitly to swap in Redis-backed,
// Prometheus-backed, or other implementations.
//
// nexus.Config{
// Stores: nexus.StoreConfig{
// RateLimit: ratelimit.NewRedisStore(rdb),
// Cache: myCacheManager,
// },
// }
Stores StoreConfig
// Environment is the named target the binary is booting into —
// "production", "staging", "preview", etc. Distinct from
// Deployment (which is the topology unit, e.g. "users-svc"):
// one Deployment can run in many Environments. Drives the
// per-environment Override merge at boot.
//
// Resolution priority:
// 1. Explicit Config.Environment field
// 2. NEXUS_ENVIRONMENT env var (set by the orchestration platform)
// 3. Default "production"
//
// Empty string is normalized to "production" at resolveConfig time
// so downstream code doesn't branch on the empty value.
Environment string
// Version stamps the binary's version on /__nexus/config. Used by
// generated clients to detect peer-version skew across services
// in a split deployment ("service A is on v2, service B on v1"
// is the source of most weird microservice bugs). Defaults to
// "dev" when unset. Stamp via -ldflags at release:
//
// go build -ldflags "-X main.version=$GIT_SHA"
// nexus.Config{Version: version}
Version string
// Introspection is the master gate over developer-facing
// surfaces under /__nexus (the dashboard, /__nexus/config,
// /__nexus/manifest, /__nexus/endpoints, etc.). Default false:
// requests to these surfaces 404 unless the request bypasses
// the gate via IntrospectionNetworks.
//
// Health + readiness probes (/__nexus/health, /__nexus/ready)
// stay unconditional — orchestrators need them. The SDK manifest
// at /__nexus/client/manifest.json has its own gate via
// Config.Client.Public; Introspection does not affect it.
//
// Typical prod: leave false, populate IntrospectionNetworks with
// VPN / office / loopback CIDRs so operators can still reach the
// dashboard from trusted networks.
//
// Set true on dev/internal listeners (compose with
// nexus.IfDeployment) to expose introspection unconditionally.
Introspection bool
// IntrospectionNetworks is the CIDR allowlist that bypasses the
// Introspection gate. When the request's TCP peer
// (gin.Context.RemoteIP — UNSPOOFABLE; ignores X-Forwarded-For)
// falls within any of these networks, the introspection routes
// serve as if Introspection were true.
//
// CIDRs are parsed once at nexus.New() time; an invalid entry
// fails fast with a clear error so misconfiguration surfaces
// at boot, not at the first dashboard request.
//
// nexus.Config{
// Introspection: false,
// IntrospectionNetworks: []string{
// "127.0.0.0/8", // loopback
// "192.168.1.0/24", // office LAN
// "10.0.0.0/8", // VPN
// },
// }
//
// Behind a load balancer the TCP peer is the LB itself — the
// allowlist won't recognize the original client. For LB-fronted
// deploys, prefer a separate internal listener bound to the
// loopback / VPN interface (Server.Listeners with ScopeAdmin).
IntrospectionNetworks []string
}
Config drives how nexus.Run builds the app. Supply it as the first argument to nexus.Run; users never construct a *App directly when using the top-level builder.
func LoadConfig ¶ added in v0.97.0
LoadConfig reads the runtime block from nexus.toml and returns a Config pre-populated from it. Fields not present in the TOML keep their zero values; the caller is free to mutate the result before passing it to nexus.Run.
LoadConfig also seeds the nexus.Get base layer with the FULL nexus.toml document, so nexus.Get[T]("section.key") resolves any value declared in nexus.toml (dotted key = TOML table path) with no config extension wired. These values are frozen at boot.
Coexists with extension/config — overlapping but distinct:
- nexus.LoadConfig reads `nexus.toml` at STARTUP → the Config struct nexus.Run consumes (listen addr, dashboard, GraphQL, CORS, …) AND the static nexus.Get base layer. Frozen at boot.
- extension/config reads `nexus.config.toml` at RUNTIME and installs a higher-priority, hot-reloadable nexus.Get snapshot (feature flags, sampling rates, remote/server-pushed values). It overrides the nexus.toml base layer for keys it carries.
So nexus.toml alone covers the common case; reach for extension/config when you need hot-reload or a remote source.
Path is optional — pass nothing to read DefaultConfigPath ("nexus.toml") from the current working directory:
cfg, err := nexus.LoadConfig() // reads nexus.toml
cfg, err := nexus.LoadConfig("alt.toml") // explicit path
Typical usage in main():
cfg, err := nexus.LoadConfig()
if err != nil { log.Fatal(err) }
cfg.Version = buildVersion // Go-side override (ldflags)
nexus.Run(cfg, /* opts */)
The TOML schema mirrors Config's shape; see RuntimeConfigBlock godoc + the schema sample in docs/. ${VAR} placeholders in string values get env-expanded the same way the deploy manifest does, so secrets / per-env hostnames can live outside the file.
Errors:
- os.ErrNotExist when the file is missing — operators wanting "TOML is optional" should stat the file or use os.IsNotExist to decide whether to fall through to a hardcoded default.
- Parse / schema errors return a wrapped error citing the offending key when go-toml provides one.
Fields NOT representable in TOML (middleware function slices, pluggable Store interfaces) stay Go-only — set those on the returned cfg via direct field assignment before nexus.Run.
func MustLoadConfig ¶ added in v0.97.0
MustLoadConfig is the panic-on-error variant for binaries that REQUIRE a nexus.toml and treat its absence as a fatal startup error. Equivalent to:
cfg, err := nexus.LoadConfig(path...)
if err != nil { panic(err) }
Path is optional — pass nothing to read DefaultConfigPath ("nexus.toml") from cwd:
cfg := nexus.MustLoadConfig() // reads nexus.toml
cfg := nexus.MustLoadConfig("alt.toml") // explicit path
Use in main() when the operator has explicitly declared their runtime config in the TOML; saves an `if err != nil` line.
type CronBuilder ¶ added in v0.3.0
type CronBuilder struct {
// contains filtered or unexported fields
}
CronBuilder accumulates optional metadata before Handler registers the job with the scheduler.
func (*CronBuilder) Describe ¶ added in v0.3.0
func (c *CronBuilder) Describe(desc string) *CronBuilder
Describe sets a human-readable description shown on the dashboard.
func (*CronBuilder) Handler ¶ added in v0.3.0
func (c *CronBuilder) Handler(fn func(ctx context.Context) error)
Handler finalizes the registration. A bad schedule is logged and the job is dropped; the app keeps running.
func (*CronBuilder) Service ¶ added in v0.3.0
func (c *CronBuilder) Service(name string) *CronBuilder
Service groups the cron under a service on the dashboard. Optional.
type DashboardConfig ¶ added in v0.19.0
type DashboardConfig struct {
// Enabled mounts /__nexus/* on the engine when true. Pulls in
// the Architecture / Endpoints / Crons / Rate-limits / Traces
// tabs and the JSON API the dashboard reads from.
Enabled bool
// Name is the brand shown in the dashboard header and the
// browser tab title. Defaults to "Nexus" when empty. Served
// over /__nexus/config so you can change it per-environment
// without rebuilding the UI.
Name string
}
DashboardConfig groups the /__nexus surface knobs. Both fields are optional: leave the struct zero-valued and the dashboard stays unmounted (default).
type DashboardConfigBlock ¶ added in v0.97.0
DashboardConfigBlock is the TOML shape of DashboardConfig.
type DashboardHiddenOption ¶ added in v1.16.0
type DashboardHiddenOption struct{}
DashboardHiddenOption is the cross-transport carrier returned by HideFromDashboard — implements RestOption, GqlOption, and WSOption so one expression flows through any endpoint builder, mirroring PublicOption. It stamps registry.HiddenTag on the endpoint's tags, which the registry's VisibleEndpoints() accessor (used by every dashboard data path) filters on.
func HideFromDashboard ¶ added in v1.16.0
func HideFromDashboard() DashboardHiddenOption
HideFromDashboard marks an endpoint as exempt from the introspection dashboard (/__nexus): it is dropped from /__nexus/endpoints, the live snapshot, and the architecture graph. The endpoint STILL routes and serves requests normally — this is dashboard-only visibility, not a 404 and not an auth gate. Useful for internal/debug/health ops you don't want cluttering the topology. Works on REST (AsRest / AsRestHandler), GraphQL (AsQuery / AsMutation), and WS (AsWS):
nexus.AsRest("GET", "/internal/debug", NewDebug, nexus.HideFromDashboard())
nexus.AsQuery(NewInternalReport, nexus.HideFromDashboard())
nexus.AsWS("/events", "debug.tap", NewDebugTap, nexus.HideFromDashboard())
type DatabaseSpec ¶ added in v1.4.0
type DatabaseSpec struct {
Driver string `toml:"driver"`
KeyPrefix string `toml:"key_prefix"`
SSLMode string `toml:"sslmode"`
TimeZone string `toml:"timezone"`
Schema string `toml:"schema"`
Default bool `toml:"default"`
// Inline values (optional). Each, when set, takes precedence over
// the config-server lookup for that field. ${ENV} placeholders are
// expanded by LoadConfig.
Host string `toml:"host"`
Port string `toml:"port"`
User string `toml:"user"`
Password string `toml:"password"`
Name string `toml:"name"`
}
DatabaseSpec is one [databases.<name>] block in nexus.toml. Each connection value (host, port, user, password, db name) resolves inline-first, then via the config server:
- if the inline field is set, it's used (supports ${ENV} expansion, applied by LoadConfig) — works with NO config server;
- else, if KeyPrefix is set, the value is read from the config server at boot as <KeyPrefix>.{hostname,port,username,password,name} — keeping secrets out of nexus.toml.
Config-server mode (secrets stay external):
[databases.uaa] driver = "postgres" key_prefix = "db.uaa" sslmode = "disable" timezone = "Africa/Dar_es_Salaam"
Inline mode (no config server needed):
[databases.local]
driver = "postgres"
host = "localhost"
port = "5432"
user = "postgres"
password = "${DB_PASSWORD}"
name = "myapp"
sslmode = "disable"
The binders that consume a spec (db.Bind / db.BindFromConfig) live in package db, not here — so the nexus core never imports GORM or a driver just to hold these TOML structs. The core only parses and stores specs; db reads them back via DatabaseSpecFor.
func DatabaseSpecFor ¶ added in v1.13.0
func DatabaseSpecFor(name string) (DatabaseSpec, bool)
DatabaseSpecFor returns the parsed [databases.<name>] block, if any. It is the seam db.BindFromConfig uses to read specs without the core importing package db. The lookup is deferred to boot (the binder calls it from its fx constructor), so specs need only exist by boot, not at option-construction time — which is what lets BindFromConfig work under nexus.Boot (Boot builds options before it loads nexus.toml).
type DevReloadConfig ¶ added in v1.1.0
type DevReloadConfig struct {
// Exclude lists glob patterns whose matches never trigger a
// browser reload. Each changed file is tested (via filepath.Match)
// three ways, and a match on any one excludes it:
//
// - against the base name → "*.tmp", "*.db"
// - against the path relative to the
// watch root → "cache/*.json"
// - as a directory subtree prefix → "uploads" skips
// everything under uploads/ (a trailing slash is optional)
//
// Invalid patterns are logged once at boot and skipped.
Exclude []string
}
DevReloadConfig tunes the NEXUS_DEV=1 live-reload watcher. The watcher already ignores hidden files, sourcemaps, and runtime data artifacts (SQLite databases + their -wal/-shm/-journal sidecars, .log files) out of the box; Exclude adds app-specific patterns on top of those built-ins.
type DevReloadConfigBlock ¶ added in v1.1.0
type DevReloadConfigBlock struct {
Exclude []string `toml:"exclude"`
}
DevReloadConfigBlock is the TOML shape of DevReloadConfig.
type EndpointGate ¶ added in v1.14.0
type EndpointGate struct {
// Middleware is the gate. It should declare AllTransports so the same
// value gates every transport; a transport it doesn't implement is
// simply not gated.
Middleware middleware.Middleware
}
EndpointGate is a middleware the framework applies to every endpoint by default — the primitive behind deny-by-default auth. An extension supplies one (e.g. auth's Authorization.Default = Authenticated()) and the framework prepends it to each endpoint's chain across REST, GraphQL, and WS unless the endpoint opts out with Public(). It lives here, not in the auth extension, so package nexus can honor the policy without importing the extension that configures it (auth imports nexus, never the reverse).
type ErrorRenderer ¶ added in v1.15.0
ErrorRenderer is an optional companion to ResponseRenderer: when a renderer also implements it, the framework offers handler *errors* to it before the default error write. This is how Inertia turns a handler that returns inertia.Redirect(...) / inertia.Location(...) into a 303/409 redirect rather than a JSON 500.
RenderError reports handled=true when it took ownership of the response (the default error write, trace error-wrapping, and status mapping are then all skipped — a redirect is not an error to log). Returning handled=false leaves the error to the standard path unchanged. A non-nil error return is treated like a renderer failure (500 if nothing was written).
type ExtensionDecoder ¶ added in v1.0.0
ExtensionDecoder turns the raw TOML bytes of one [extensions.<name>] sub-tree into the nexus.Option(s) that wire the extension into nexus.Run. The decoder owns its schema — operators write TOML keys the decoder defines, the decoder unmarshal()s them however it likes (typed struct, generic map, whatever).
Returns the decoded Options + an error. Empty/nil Options + nil error means "successfully decoded but the block has no effect" (e.g. all fields zero); the framework treats it as a no-op.
Why bytes rather than a typed struct: we don't know the extension's schema at framework-level, so we leave that decision to the decoder. Re-marshaling adds ~µs per extension at boot; negligible compared to the rest of startup cost.
func LookupExtensionDecoder ¶ added in v1.0.0
func LookupExtensionDecoder(name string) ExtensionDecoder
LookupExtensionDecoder returns the registered decoder for name, or nil when none is registered. Exposed for the lint command (so it can warn on [extensions.X] blocks for which no decoder exists — typically because the operator forgot to import the extension package).
type FrontendOption ¶ added in v0.21.17
type FrontendOption interface {
// contains filtered or unexported methods
}
FrontendOption tunes a ServeFrontend call. Returned by helpers like FrontendAt; users don't construct these directly.
func FrontendAt ¶ added in v0.21.17
func FrontendAt(path string) FrontendOption
FrontendAt sets a sub-path the SPA is served under, in addition to the deployment-wide route prefix. The two compose: deployment prefix /v1 + FrontendAt("/admin") → SPA at /v1/admin/. Useful when API endpoints live at the deployment root and the frontend should answer on a sibling path. Empty / "/" mean the SPA mounts directly under the deployment prefix (the default).
Trailing slashes are trimmed; a leading slash is added if missing. Pass "/admin" or "admin" — both resolve to "/admin".
type GenerateContext ¶ added in v0.54.0
type GenerateContext struct {
Registry *registry.Registry
Refs map[string]registry.NamedType
BasePath string
Extras map[string]any
}
GenerateContext is the input handed to a codegen driver's Render callback. The driver reads from the live registry and the shared named-type pool to project TS source files (or any other generated artifact) without re-walking the schema.
Extras is a free-form map so the driver can pass framework-specific knobs (Vue vs React, public manifest flags, etc.) into the renderer without baking them into this struct. Convention: keys live in the driver package's namespace ("frontend.framework", not "framework").
type GenerateDriver ¶ added in v0.54.0
type GenerateDriver struct {
// PluginName is the owning plugin's Name. Used in error messages
// and the "which driver is registered?" introspection surface.
PluginName string
// OutDir resolves the absolute directory the driver wants files
// written to. Resolution is deferred (a function, not a string)
// so drivers that compute the path from Config + cwd at boot can
// honor whatever working directory the user invoked `nexus build`
// from.
OutDir func(*App) (string, error)
// Render produces the file tree. Returning a non-nil error aborts
// the generation pass — partial writes never reach disk.
Render func(GenerateContext) ([]GeneratedFile, error)
}
GenerateDriver is the codegen contribution one plugin per app may declare. extension.Use converts an extension.Generate slot into this record and registers it on the App at boot. The nexus CLI (or any in-process tool) calls App.GenerateDrivers() to find the active driver, asks for its OutDir, runs Render, and writes the result.
Exactly one driver per app is the v1 contract — apps with multiple frontends are a v2 problem. Duplicate registration panics at boot so misconfiguration surfaces immediately rather than producing last-write-wins output mismatched with the consumer's imports.
type GeneratedFile ¶ added in v0.54.0
GeneratedFile is one file the codegen driver wants written to its OutDir. Path is forward-slash relative to OutDir; Body is the raw bytes. Mirrors extension.File so the extension package can convert values across the package boundary without an import cycle.
type GqlField ¶ added in v0.3.0
type GqlField struct {
Kind graph.FieldKind
ServiceType reflect.Type
Service *Service // nil if dep[0] didn't unwrap (misuse)
Module string // nexus.Module name this field was declared under; "" if unscoped
// Deployment is the DeployAs tag of the enclosing module; "" when
// the module is always-local. Forwarded to the registry entry so
// dashboard consumers can group by deployment unit.
Deployment string
Field any // graph.QueryField or graph.MutationField
DepTypes []reflect.Type // for resource auto-attach
Deps []reflect.Value // for resource auto-attach (NexusResourceProvider)
// RateLimit is the baseline rate limit this op declared. Auto-mount
// publishes it to the registry so the dashboard can render it and
// — once operator overrides land — show the effective limit beside
// the declared one.
RateLimit *ratelimit.Limit
// ArgsType / ReturnType are the reflect.Type of the handler's args
// struct + return value, captured at AsQuery/AsMutation time. The
// auto-mount walks them into registry.TypeRef structures and
// stamps the result on the registered Endpoint via
// registry.SetEndpointSchema. Powers the client SDK's TS codegen
// alongside REST + WS.
//
// Either may be nil — handlers that take no args / return only
// error pass through as the SDK's "no schema" signal.
ArgsType reflect.Type
ReturnType reflect.Type
// Tags carries registry.Endpoint.Tags entries declared via
// option helpers (e.g. nexus.AuthRoute → "auth.flow"). Forwarded
// through the auto-mount so the registered endpoint surfaces the
// same tags REST endpoints receive via registerEndpoint, keeping
// the SDK manifest's auth-flow discovery transport-agnostic.
Tags map[string]string
}
GqlField is the shared-group payload that AsQuery / AsMutation produce and fxmod's auto-mount Invoke consumes. Exported so consumers building their own mount logic can see what's in the graph, but most users never touch it.
type GqlOption ¶ added in v0.3.0
type GqlOption interface {
// contains filtered or unexported methods
}
GqlOption tunes a GraphQL registration. An interface (not a func type) so a single value — notably the nexus.Use cross-transport bundle — can satisfy both GqlOption and RestOption by implementing each's applyToX.
func Deprecated ¶ added in v0.3.0
Deprecated marks the field deprecated. The reason shows up in SDL and the dashboard "deprecated" badge.
func Desc ¶ added in v0.3.0
Desc sets the resolver's description (shown on the dashboard and in SDL documentation).
func GraphMiddleware ¶ added in v0.3.0
func GraphMiddleware(name, description string, mw graph.FieldMiddleware) GqlOption
GraphMiddleware attaches a named graph-only middleware to the resolver. Equivalent to go-graph's WithNamedMiddleware — the name appears in FieldInfo.Middlewares for dashboard rendering (and "auth", "cors", etc. get labelled "builtin" via nexus/middleware.Builtins).
For cross-transport middleware, prefer nexus.Use(middleware.Middleware{...}) — it accepts the same bundle on REST and GraphQL alike.
func Middleware
deprecated
added in
v0.3.0
func Middleware(name, description string, mw graph.FieldMiddleware) GqlOption
Middleware is a deprecated alias for GraphMiddleware. Exists so existing call sites keep compiling while codebases migrate to nexus.Use for cross-transport middleware.
Deprecated: use GraphMiddleware for graph-only middleware, or nexus.Use with a middleware.Middleware bundle for cross-transport.
func OnService ¶ added in v0.3.0
OnService routes this registration onto the given service wrapper type without requiring the handler to take it as a dep. Use when the handler is minimal (`func NewListQuestions(q *QuestionsDB) (...)`) but still belongs to a particular service on the dashboard.
nexus.AsQuery(NewListQuestions, nexus.OnService[*AdvertsService]())
The resolver still needs the owning service to have been provided into the fx graph elsewhere so MountGraphQL can pick up the field.
func RateLimit ¶ added in v0.3.0
RateLimit declares a baseline rate limit for this op. The auto-mount registers it with the app's rate-limit store and wires an enforcement middleware that consults the store on every request. Operators can override the effective limit live from the dashboard — the declared baseline stays in source-of-truth, the override survives in the store.
nexus.AsMutation(NewCreateAdvert,
nexus.RateLimit(ratelimit.Limit{RPM: 30, PerIP: true}),
)
Burst defaults to RPM/6 when zero (10-second burst window). Set PerIP to true to scope the bucket to the caller's IP; leave false for a shared global bucket.
type GraphQLConfig ¶ added in v0.19.0
type GraphQLConfig struct {
// Path overrides the default mount path for auto-generated
// GraphQL services. Empty falls back to DefaultGraphQLPath
// ("/graphql").
Path string
// DisablePlayground turns OFF the in-browser GraphQL IDE served on
// GET <service>/<path>. The IDE is Apollo Sandbox by default.
// Enabled by default — flip in prod wiring to hide the interactive
// console.
DisablePlayground bool
// Debug skips query validation + response sanitization in
// go-graph. Dev-only. Default false.
Debug bool
// Pretty pretty-prints JSON responses. Convenient while
// exploring; ship off in prod.
Pretty bool
// DocumentCacheSize bounds the parse+validate memo (LRU) the
// framework installs in front of graphql.Do. Repeat queries
// re-use the cached AST and validation verdict, skipping the
// ~89% of GraphQL request allocations that profiling pinned on
// parse + validate.
//
// Zero (the default) means 1024 entries — enough for any app
// with a fixed query catalog. Set to a negative value to
// disable the cache entirely.
//
// A "miss every request" pattern usually indicates clients are
// embedding variable values in the query string instead of
// using $vars. Check the cache stats on the dashboard if hit
// rate is suspiciously low.
DocumentCacheSize int
}
GraphQLConfig groups the framework's environment-level GraphQL knobs. Per-service paths via (*Service).AtGraphQL still win over these defaults — these only apply to services that don't carry an explicit AtGraphQL call.
type GraphQLConfigBlock ¶ added in v0.97.0
type GraphQLConfigBlock struct {
Path string `toml:"path"`
DisablePlayground bool `toml:"disable_playground"`
Debug bool `toml:"debug"`
Pretty bool `toml:"pretty"`
DocumentCacheSize int `toml:"document_cache_size"`
}
GraphQLConfigBlock is the TOML shape of GraphQLConfig.
type HandlerShape ¶ added in v0.74.0
type HandlerShape struct {
// contains filtered or unexported fields
}
HandlerShape is the export-facing wrapper around the framework's internal handlerShape. Extensions (extension/peer, future RPC transports, custom auth bundles) that want to mount user handlers using the canonical reflective signature go through this type.
The public surface is intentionally narrow: depTypes for fx wiring, argsType for body decoding, hasArgs/hasCtx for slot decisions, and Invoke for the actual call. Everything else stays unexported so the internal shape can evolve without breaking out-of-tree code.
func InspectHandlerForExt ¶ added in v0.74.0
func InspectHandlerForExt(fn any) (HandlerShape, error)
InspectHandlerForExt is the public version of inspectHandler. Extensions call this once at registration time, then use the returned HandlerShape to build an fx.Invoke that resolves deps and stamps a bound closure into the extension's dispatch table.
Shape constraints match every other reflective registration in nexus — see the package doc on AsRest for the full grammar.
Errors here are returned to the caller as a Go error rather than wrapped in an fx.Error option, because the caller usually wants to attach its own context ("peer.AsCall(%q): %w") before letting fx see them.
func (HandlerShape) ArgsType ¶ added in v0.74.0
func (h HandlerShape) ArgsType() reflect.Type
ArgsType returns the struct type the handler decodes its body into — the T in Params[T] (or the trailing flat-args struct for legacy handlers). nil when the handler takes no args.
func (HandlerShape) BuildInvokeOption ¶ added in v0.74.0
func (h HandlerShape) BuildInvokeOption(mount func(app *App, bound BoundHandler) error) Option
BuildInvokeOption produces a nexus.Option whose underlying fx.Invoke has the signature `(*App, dep1, dep2, ...) → ()`. When fx fires the invoke at app start, it resolves every dep type returned by DepTypes, captures them in a BoundHandler closure, and hands the closure to mount.
Extensions use this as the single source of fx wiring — they don't need to assemble reflect.FuncOf signatures themselves. Each extension's AsX option boils down to:
sh, err := nexus.InspectHandlerForExt(fn)
if err != nil { return ... }
return sh.BuildInvokeOption(func(app *App, bound BoundHandler) error {
extDispatchTable.Store(name, bound)
return nil
})
The mount closure receives the App so it can read app-level state (engine, registry, plugin store) before stashing the bound handler.
func (HandlerShape) DepTypes ¶ added in v0.74.0
func (h HandlerShape) DepTypes() []reflect.Type
DepTypes returns the reflective types of every fx-injected dep the handler expects, in registration order. Extension code uses this to build an fx.FuncOf with the same signature so fx resolves the deps at boot.
func (HandlerShape) HasArgs ¶ added in v0.74.0
func (h HandlerShape) HasArgs() bool
HasArgs reports whether the handler expects an args struct. Extensions use this to decide whether to allocate + decode a body before invoking.
func (HandlerShape) ReturnType ¶ added in v0.74.0
func (h HandlerShape) ReturnType() reflect.Type
ReturnType returns the handler's first return type (the result). nil when the handler returns only an error (or nothing). Extensions use this for response shape inspection — schema emission, dashboard endpoint metadata.
type ListOptions ¶ added in v0.21.28
type ListOptions struct {
// Limit caps the page size. AsCRUD clamps to [1, 100] (default 20)
// before invoking the Store, so user code can trust the bounds.
Limit int `json:"limit" query:"limit"`
// Offset is the start index, default 0. Negative values are
// clamped to 0 by the framework.
Offset int `json:"offset" query:"offset"`
// Sort is a CSV of field names; "-" prefix indicates descending.
// Stores interpret these against their own column names.
Sort []string `json:"sort,omitempty" query:"sort"`
}
ListOptions is the parsed shape of a List request's query string. AsCRUD's generated List handler reads ?limit / ?offset / ?sort and hands a populated ListOptions to the Store.
type Listener ¶ added in v0.16.0
type Listener struct {
// Addr is the listen address (e.g. ":8080", "127.0.0.1:9000").
// Required — an empty Addr is rejected by Run with a precise
// error message.
Addr string
// Scope decides which routes this listener exposes. Zero value
// is ScopePublic — the conservative default for an exposed port.
Scope ListenerScope
// TLS, when non-nil, terminates TLS on this listener. The raw
// TCP listener is wrapped with tls.NewListener at bind time so
// the same http.Server serves HTTPS without a second code path.
// Leave nil for plain HTTP (today's behavior on every listener).
//
// Build via ServerTLSConfig for a typical cert/key (and optional
// client-CA for mTLS), or supply a *tls.Config directly when you
// need custom cipher suites, SNI via GetCertificate, etc.
//
// For public-internet HTTPS with Let's Encrypt auto-issuance,
// prefer extension/tls.Plugin — it owns its own :443/:80 pair
// and handles ACME challenges. This field is the right tool for
// an admin/internal listener fronted by your own cert material
// (e.g. an internal CA, mTLS-protected dashboard).
TLS *tls.Config
}
Listener declares one bound address with a scope. Multiple listeners can share a scope (e.g. one bound to 0.0.0.0:8080 and another to a loopback for sidecar health checks).
type ListenerConfigBlock ¶ added in v0.97.0
type ListenerConfigBlock struct {
Addr string `toml:"addr"`
Scope string `toml:"scope"` // "public" / "admin" / "internal"
}
ListenerConfigBlock is the TOML shape of a Listener. TLS is intentionally not exposed here — TLS config typically includes file paths + ACME settings that the extension/tls plugin handles separately. Operators wanting TLS on a Listener should use ServerTLSConfig in Go code.
type ListenerScope ¶ added in v0.16.0
type ListenerScope int
ListenerScope decides which routes a listener exposes. The framework uses the request's bound local address (via http.LocalAddrContextKey) to look up the scope and 404s requests to routes outside that scope.
The scope abstraction is opt-in: when Config.Listeners is empty, a single listener bound to Config.Addr serves every route (today's behavior). The scope filter only fires for explicitly-declared listeners.
const ( // ScopePublic exposes user-facing routes (REST, GraphQL, WebSocket) // and hides the /__nexus dashboard surface. The default for any // listener whose Scope is left zero — public is the safe default // for the listener bound to the world. ScopePublic ListenerScope = iota // ScopeInternal exposes user-facing routes plus /__nexus/health // and /__nexus/ready, so peer services can call your handlers and // orchestrators (k8s probes, load balancers) can poll readiness. // The rest of /__nexus stays hidden. ScopeInternal // ScopeAdmin exposes everything — /__nexus surface AND user // routes. The admin listener is meant for operators (typically // bound to a private subnet or behind an SSH tunnel), so giving // it the full route set is a UX win: the dashboard's in-page // RestTester / GraphQLTester make relative fetch() calls, and // blocking user routes here would silently 404 those. // // If you need a strictly-dashboard-only listener, that's a // future ScopeIntrospection — the current ScopeAdmin trades // surface area for ergonomics. ScopeAdmin )
func (ListenerScope) String ¶ added in v0.16.0
func (s ListenerScope) String() string
String returns the lowercase scope name. Dashboards and logs render scopes by name; keeping the mapping in one place makes additions future-safe.
type MemoryStore ¶ added in v0.21.28
type MemoryStore[T any] struct { // contains filtered or unexported fields }
MemoryStore is a thread-safe in-memory Store[T]. Useful for prototyping and tests; not durable, not suitable for production.
Construction requires id accessors so the store knows where the "primary key" lives on T — auto-detection happens at AsCRUD's boot when MemoryStore is built behind the scenes via MemoryResolver, so most users never call NewMemoryStore directly.
func NewMemoryStore ¶ added in v0.21.28
func NewMemoryStore[T any](getID func(*T) string, setID func(*T, string), newID func() string) *MemoryStore[T]
NewMemoryStore constructs an empty in-memory store. getID/setID are required; newID defaults to uuid.NewString.
func (*MemoryStore[T]) Find ¶ added in v0.21.28
func (s *MemoryStore[T]) Find(_ context.Context, id string) (*T, error)
Find returns a copy of the stored value to avoid handing the caller a reference to the live map entry — saves us from "I mutated the returned object and the store changed" surprises.
func (*MemoryStore[T]) Remove ¶ added in v0.21.28
func (s *MemoryStore[T]) Remove(_ context.Context, id string) error
func (*MemoryStore[T]) Save ¶ added in v0.21.28
func (s *MemoryStore[T]) Save(_ context.Context, item *T) error
Save assigns a new id when getID returns "" (treat as create); otherwise upserts at the existing id. Mutates *item via setID so the caller sees the assigned id on a fresh create.
func (*MemoryStore[T]) Search ¶ added in v0.21.28
func (s *MemoryStore[T]) Search(_ context.Context, opts ListOptions) ([]T, int, error)
Search applies sort + pagination over a snapshot of the items map. For very large maps this allocates the whole slice; that's acceptable for an in-memory store whose purpose is prototyping.
type MiddlewareConfig ¶ added in v0.19.0
type MiddlewareConfig struct {
// Global stacks on the Gin engine root, so every REST endpoint,
// GraphQL POST, WebSocket upgrade, and dashboard request flows
// through it in registration order. Use for cross-cutting
// concerns (request-id, logger, CORS, auth pre-gate, etc.).
// Each bundle's Gin field runs; nil Gin realizations are
// skipped silently. Per-op middleware (via nexus.Use on a
// registration) layers on top.
Global []middleware.Middleware
// Dashboard gates the /__nexus surface behind user-supplied
// middleware — typically auth + permission checks. Each
// bundle's Gin realization runs in registration order on the
// /__nexus route group BEFORE any dashboard handler, covering
// the JSON API, WebSocket events, and the embedded Vue UI in
// one pass.
//
// Bundles whose Gin field is nil are ignored — the dashboard
// is an HTTP surface, so graph-only bundles don't apply.
Dashboard []middleware.Middleware
// RateLimit is the built-in app-wide rate limit. When set,
// installs as a gin middleware on the engine root so every
// HTTP path consults the bucket. Combine with per-op
// nexus.RateLimit() declarations for layered protection: the
// request must pass both the global bucket and the op's bucket.
// Zero disables.
RateLimit ratelimit.Limit
// CORS configures the built-in CORS middleware. Nil = no CORS
// handling (the framework installs nothing — same-origin
// browsers work, cross-origin requests are rejected by the
// browser). Set to a populated struct to allow cross-origin
// requests with the listed origins / methods / headers. The
// middleware lands on the engine root before any route, so
// REST + GraphQL + WebSocket upgrades all see it.
//
// For finer control (per-route CORS, dynamic origin checks),
// install your own gin middleware via Global instead.
CORS *CORSConfig
}
MiddlewareConfig groups every middleware-related knob the framework recognizes. All fields are optional — leave the struct zero-valued for "no extra middleware" and the framework runs with its built-in stack alone.
type MiddlewareConfigBlock ¶ added in v0.97.0
type MiddlewareConfigBlock struct {
CORS *CORSConfigBlock `toml:"cors"`
RateLimit *RateLimitConfigBlock `toml:"ratelimit"`
}
MiddlewareConfigBlock is the TOML shape of MiddlewareConfig. Only data-driven fields are exposed here (CORS settings, rate-limit knobs). Slice-of-middleware fields (Global, Dashboard) require Go-side functions and stay Go-only.
type MiddlewareOption ¶ added in v0.3.0
type MiddlewareOption struct {
// contains filtered or unexported fields
}
MiddlewareOption carries a Middleware across the AsRest/AsQuery/... call sites. Each transport's option type embeds / converts this, so a single nexus.Use(...) expression can appear wherever the transport accepts it.
MiddlewareOption also satisfies the top-level Option interface as a no-op so callers can flow it through Option-typed variadic slots (notably nexus.AsCRUD, which accepts ...Option). The option still only takes effect via applyToRest / applyToGql / applyToWS — the no-op nexusOption() exists purely for type-system passage.
func Use ¶ added in v0.3.0
func Use(m middleware.Middleware) MiddlewareOption
Use attaches a transport-agnostic middleware bundle to a registration. Works on AsRest, AsQuery, AsMutation, (future AsSubscription / AsWebSocket) — each transport picks the realization it understands from the bundle (Gin for REST/WS upgrade, Graph for GraphQL). Missing fields are silently ignored so a single bundle can degrade gracefully across transports.
rl := ratelimit.NewMiddleware(store, key, ratelimit.Limit{RPM: 30})
fx.Provide(
nexus.AsMutation(NewCreateAdvert, nexus.Use(rl)),
nexus.AsRest("POST", "/quick", NewQuick, nexus.Use(rl)),
)
For app-wide coverage (every REST endpoint + GraphQL POST + WS upgrade + the dashboard itself) put the middleware in Config.GlobalMiddleware instead of naming it on each registration.
type NexusResourceProvider ¶ added in v0.3.0
NexusResourceProvider is implemented by managers that know the external resources they front. A manager's NexusResources slice is used in two places:
- Boot-time registration via nexus.ProvideResources — each returned resource.Resource is added to the app registry so it appears on the dashboard with its health, description, and details.
- Service attachment via the GraphQL auto-mount — whenever a resolver names this manager as a dep, every resource in the slice gets linked to the owning service by name, drawing the architecture edge.
A manager may list more resources than any one handler uses; the edge is drawn per named resource on every service that mentions the manager.
func (m *DBManager) NexusResources() []resource.Resource {
var out []resource.Resource
m.Each(func(name string, db *DB) {
out = append(out, resource.NewDatabase(name, ...))
})
return out
}
type Notifier ¶ added in v0.63.0
type Notifier struct {
// contains filtered or unexported fields
}
Notifier is the shared signal hub. Multiple subsystems call Notify(); multiple consumers (typically just streamLive, but the API supports more for future widgets) Subscribe to receive a nudge channel.
Topic-aware: NotifyTopic("post:42") wakes only subscribers who joined that topic via SubscribeTopic; the un-topic'd Notify() and Subscribe() pair preserves the v0 broadcast behavior for callers that don't care about routing. The two surfaces overlap deliberately — Notify() also wakes every topic subscriber so a global "everything changed" still reaches everyone, while NotifyTopic stays scoped.
Notify is cheap (single mutex acquire + N non-blocking sends) and safe from any goroutine. The zero value is unusable — callers must go through New so the maps are initialized.
func NewNotifier ¶ added in v0.63.0
func NewNotifier() *Notifier
NewNotifier returns a fresh Notifier. Typically created once at app boot and threaded into each mutating subsystem via SetChangeHook (or the equivalent setter on each package).
Production apps don't need to call this directly — nexus.Run wires a singleton into the fx graph; constructors that need a notifier just take a *Notifier param.
func (*Notifier) AttachBus ¶ added in v0.69.0
AttachBus wires a Bus to this Notifier. After Attach:
- Every Notify() and NotifyTopic() also Publish to the bus so peers see the change.
- Incoming bus messages fan out to this node's local subscribers (Subscribe / SubscribeTopic).
Returns a cancel func that detaches the forwarding goroutine. The Bus itself is not closed by cancel — caller owns its lifecycle (typically via fx Lifecycle.OnStop).
Returns an error if a bus is already attached; only one bus per Notifier is supported. To switch buses, detach the old one first and call AttachBus again.
func (*Notifier) Notify ¶ added in v0.69.0
func (n *Notifier) Notify()
Notify wakes every current subscriber whose channel has buffer room. Includes both broadcast subscribers (Subscribe) and every topic subscriber (SubscribeTopic), so "global change" callers don't need to know what topics exist. Subscribers whose channel already has a pending nudge are left alone — coalescing N rapid mutations into one wake-up is the whole point. Never blocks.
When a Bus is attached, also Publish("") so peer nodes wake their local subscribers too — that's the horizontal-scale path. Publish errors are swallowed: the wake is best-effort, and a flaky bus shouldn't take down the mutator that called us.
func (*Notifier) NotifyTopic ¶ added in v0.69.0
NotifyTopic wakes only subscribers of the given topic. Broadcast subscribers (Subscribe with no topic) are NOT woken — they're for "tell me about everything" and would defeat the scoping if every topic notify also woke them. Use Notify() for the global case.
Topic strings are arbitrary; common patterns are entity-scoped keys like "post:42" or "user:alice/inbox". An empty topic is a no-op (NotifyTopic("") matches no one — SubscribeTopic rejects empty strings).
When a Bus is attached, also publishes the topic so peer nodes fan it out to their local subscribers. See Notify for the rationale on swallowed Publish errors.
func (*Notifier) Subscribe ¶ added in v0.69.0
func (n *Notifier) Subscribe() (<-chan struct{}, func())
Subscribe registers a new broadcast listener and returns its nudge channel plus a cancel func. The channel has buffer 1 so a single pending notify is held even when the subscriber is busy. Cancel removes the listener and closes the channel; subsequent Notify calls skip it.
Cancel is safe to call multiple times.
func (*Notifier) SubscribeTopic ¶ added in v0.69.0
SubscribeTopic registers a listener scoped to one topic. Same channel + cancel contract as Subscribe; the difference is that only NotifyTopic(topic) and the global Notify() wake the channel — NotifyTopic for other topics leaves it alone.
Empty topic returns a closed channel and a no-op cancel — the caller usually has a programming error in that case (constructed a topic key from missing input), and a never-firing channel makes the symptom visible without panicking.
type Option ¶
type Option interface {
// contains filtered or unexported methods
}
Option composes a nexus app. Everything returned by Provide, Supply, Invoke, Module, AsRest, AsQuery, AsMutation, AsWebSocket, AsSubscription is an Option, ready to pass to Run. fx is an implementation detail — user code imports only nexus.
func AddStartupTask ¶ added in v0.22.1
func AddStartupTask(t manifest.StartupTask) Option
AddStartupTask produces an Option that registers a startup task. The task's Run is preserved through to integration step 3 where registerLifecycle invokes it before binding listeners.
func AsCRUD ¶ added in v0.21.28
AsCRUD registers a default set of CRUD endpoints for type T.
REST surface (default — always on unless WithoutREST() is passed):
GET /<plural> → List GET /<plural>/:id → Read POST /<plural> → Create PATCH /<plural>/:id → Update DELETE /<plural>/:id → Delete
GraphQL surface (opt-in via WithGraphQL()):
query list<T>s(limit, offset, sort) query get<T>(id) mutation create<T>(...) mutation update<T>(id, ...) mutation delete<T>(id) → Boolean
The `resolver` is a function returning a Store[T] for each request. Its first argument must be context.Context; further arguments are fx-injected at boot, so depending on your DBM (or any other dep) just means putting it in the signature:
nexus.AsCRUD[Note](
func(ctx context.Context, db *OAtsDB) (nexus.Store[Note], error) {
return gormstore.For[Note](db.GormDB().WithContext(ctx)), nil
},
nexus.WithGraphQL(),
)
Any resolver dep that implements NexusResources() (the framework's resource provider interface — DBs, caches, queues) is automatically linked to the generated endpoints on the dashboard, so the resource node fans out to every action without a manual edge declaration.
Storage is per-request: the resolver runs on every action and the returned Store handles that one request. That makes multi-tenancy / read-replica routing trivial — scope your Store inside the resolver from anything you can pull off ctx.
Convenience: if `resolver` is a `CRUDResolver[T]` (no fx deps), it's accepted as-is — handy for `MemoryResolver[T]` and tests.
nexus.AsCRUD[User](nexus.MemoryResolver[User](nil, nil)) // REST only nexus.AsCRUD[User](resolver, nexus.WithGraphQL()) // REST + GraphQL nexus.AsCRUD[User](resolver, nexus.WithGraphQL(), nexus.WithoutREST()) // GraphQL only
Options layer over the generated endpoints — auth.Required(), nexus.Use(...), nexus.OnCreate(...) all work as they do for AsRest.
func AsMutation ¶ added in v0.3.0
AsMutation is the mutation analogue of AsQuery.
func AsQuery ¶ added in v0.3.0
AsQuery registers a GraphQL query from a plain Go handler. The handler's signature is inspected reflectively:
- First param should be the service wrapper (e.g. *AdvertsService). Its type is used as the fx value-group key so MountGraphQL[*AdvertsService] picks up this query.
- Subsequent params are fx-injected deps.
- Optional last param is an args struct. Field tags drive arg config: graphql:"name" — arg name (defaults to lowercased field name) graphql:"name,required" — NonNull validate:"required" — graph.Required() validate:"len=3|120" — graph.StringLength(3, 120) validate:"int=1|100" — graph.Int(1, 100) validate:"oneof=a|b|c" — graph.OneOf("a","b","c") Chain multiple rules with commas.
- Return type must be (T, error). T is the resolver's return; pointer and slice wrappers are honored.
Op name defaults to the handler's func name, stripping a leading "New" and lowercasing the first rune ("NewGetAllAdverts" → "getAllAdverts"). Override with nexus.Op("explicit").
fx.Provide(
nexus.AsQuery(NewGetAllAdverts),
nexus.AsMutation(NewCreateAdvert,
nexus.Middleware("auth", "Bearer token", AuthMw)),
)
func AsRest ¶ added in v0.3.0
func AsRest(method, path string, fn any, opts ...RestOption) Option
AsRest registers a REST endpoint from a plain Go handler. The handler's signature is inspected via reflection:
- Leading params are fx-injected deps. The first such param should be your service wrapper (see docs on Service) — its type grounds the endpoint in a service node on the dashboard.
- The optional last param is an "args" struct whose tags direct gin on how to bind from the request: uri:"id" → ShouldBindUri query:"x" → ShouldBindQuery header:"x" → ShouldBindHeader form:"x" → ShouldBind (multipart/url-encoded) json:"x" → ShouldBindJSON (for non-GET; default when other binders are absent)
- The return may be (T, error), (T), (error), or nothing. T gets JSON-marshalled with status 200 (201 for POST) on success; errors become status 500 with {"error": "..."}.
Returns an fx.Option; drop it into fx.Provide.
fx.Provide(
nexus.AsRest("GET", "/pets", NewListPets),
nexus.AsRest("POST", "/pets", NewCreatePet),
nexus.AsRest("GET", "/pets/:id", NewGetPet),
)
func AsRestHandler ¶ added in v0.7.3
func AsRestHandler(method, path string, factory any, opts ...RestOption) Option
AsRestHandler registers a REST endpoint whose handler is a plain gin.HandlerFunc supplied by a *factory* function. The factory is the fx-resolved piece: its parameters are the deps needed to build the handler (controllers, resources, other services), its single return is the gin.HandlerFunc that serves requests.
Use this when the handler already manages its own request binding and response shaping (typical for code migrated from ad-hoc Gin routes) but you still want module annotation, metrics, and the dashboard packet-animation treatment AsRest provides:
nexus.Module("oats-rest",
nexus.AsRestHandler("POST", "/api/devices/register",
func(d *DeviceController) gin.HandlerFunc { return d.RegisterDevice },
nexus.Description("Register a device"),
auth.Required(),
),
)
Factory signature requirements:
- Zero or more parameters (fx-injected deps).
- Exactly one return value of type gin.HandlerFunc.
On the dashboard this endpoint appears under its enclosing nexus.Module (same grouping as AsRest / AsQuery), with metrics + trace middleware attached so request.op events drive the live packet animation.
func AsSubscription ¶ added in v0.3.0
AsSubscription is reserved for a follow-up: subscriptions use a separate builder (SubscriptionResolver[T] with PubSub + channel plumbing) that we haven't taught the reflective path yet. Use graph.NewSubscriptionResolver directly for now; once the reflective SubscriptionResolverFromType exists this helper will mirror AsQuery/AsMutation.
func AsWS ¶ added in v0.9.0
AsWS registers one message-type-scoped handler on a WebSocket endpoint. Multiple AsWS calls with the same path share a single connection pool — the framework dispatches inbound messages by their envelope `type` to the matching handler.
type ChatPayload struct{ Text string }
func NewChatSend(svc *ChatSvc, sess *nexus.WSSession,
p nexus.Params[ChatPayload]) error {
sess.EmitToRoom("chat.message",
map[string]string{"text": p.Args.Text, "user": sess.UserID()},
"lobby")
return nil
}
nexus.AsWS("/events", "chat.send", NewChatSend, auth.Required())
Wire protocol — every message is wrapped in the framework's envelope:
{ "type": "chat.send", "data": {...}, "timestamp": <unix> }
The built-in types `ping`, `authenticate`, `subscribe`, `unsubscribe` are handled by the hub directly and never reach user handlers.
Handler signature: same reflective convention as AsRest / AsQuery —
- fx-injected deps anywhere (service wrappers, resources, other services);
- an optional *nexus.WSSession parameter gets the live connection handle;
- an optional nexus.Params[T] carries the decoded message payload in Args;
- return (error) — a non-nil error is sent back on the same connection as an `error` envelope event. The connection stays open.
Middleware (auth.Required, rate limits, etc.) on the FIRST AsWS call for a path is installed on the HTTP upgrade route and gates every subsequent connection. Middleware declared on later AsWS calls for the same path is ignored with a warning log — all dispatches share one upgrade route.
func AsWorker ¶ added in v0.7.0
AsWorker registers a long-lived background worker. The framework owns lifecycle — it starts the worker on fx.Start in its own goroutine, cancels its context on fx.Stop, and records status + last-error on the registry so the dashboard can surface it.
Signature requirements:
First parameter MUST be context.Context. The framework supplies a context that cancels when the app stops; workers are expected to honor it and return.
Remaining parameters are fx-injected deps (same rules as a constructor — they must exist in the graph).
Return is optional: a single (error) return lets the worker report a fatal error. context.Canceled / nil is treated as a clean stop; anything else sets Status="failed" + LastError.
nexus.AsWorker("cache-invalidation", func(ctx context.Context, db *OatsDB, cache *CacheManager, logger *zap.Logger) error { for !db.IsConnected() { select { case <-ctx.Done(): return ctx.Err() case <-time.After(time.Second): } } listener := pq.NewListener(db.ConnectionString(), ...) defer listener.Close() _ = listener.Listen("cache_invalidation") for { select { case <-ctx.Done(): return nil case n := <-listener.Notify: handle(n, cache) } } })
Resource / service deps (for the architecture graph) are detected the same way nexus.ProvideService does it — any param implementing NexusResourceProvider contributes its resources, any param whose type is a service wrapper contributes a service dep.
A worker panic is caught and reported as Status=failed; the app keeps running. For restart semantics, wrap your worker body in a loop that re-dials on ctx.Done() exit OR let the operator restart the app.
func ClientUse ¶ added in v0.28.0
ClientUse is the option-chain alias for setting Config.Client. Most apps wire the SDK via the Config field — one line on the Config literal — but ClientUse is the right choice when:
You want per-deployment gating: composes with IfDeployment.
nexus.IfDeployment([]string{"public-api"}, nexus.ClientUse(client.Config{Enabled: true}), )
The SDK is conditional on runtime state the Config struct can't easily express (env-driven feature flags, multi-binary option chains). ClientUse + nexus.Options(...) compose freely with everything else.
Idempotent: when both Config.Client.Enabled AND ClientUse are in scope, the second mount is skipped (the App already has a live Handler from the first).
Sets cfg.Enabled=true unconditionally — the option-chain caller's intent is "mount it"; the Config-side knob exists for the value- driven path only.
func ClientUseWithContributions ¶ added in v0.54.0
func ClientUseWithContributions(cfg client.Config, buildFactory func(*App) client.ContributionsBuilder) Option
ClientUseWithContributions is ClientUse + a contributions builder factory. When buildFactory is non-nil, the resulting builder is invoked at HTTP-request time so the closure can read live state (contributor list, schema refs) that isn't available at option- construction time. The factory itself runs once inside fx.Invoke with the constructed *App in scope.
Lives in nexus/ because the App's clientHandler field is unexported — keeping the assignment inside the package avoids exposing it as a Setter.
Phase-3 caller: extension/frontend's mountClientSDK helper. Direct user-side wiring is rare; most apps go through frontend.Plugin(...) which composes this transparently.
func DeclareEnv ¶ added in v0.22.1
DeclareEnv produces an Option that registers one EnvVar on the app at graph construction. Multiple calls compose:
nexus.Module("cache",
nexus.DeclareEnv(manifest.EnvVar{Name: "REDIS_HOST", Required: true, BoundTo: "redis.host"}),
nexus.DeclareEnv(manifest.EnvVar{Name: "REDIS_PORT", Required: true, BoundTo: "redis.port"}),
nexus.Provide(NewManager),
)
func DeclareEnvList ¶ added in v0.22.1
DeclareEnvList is the bulk variant of DeclareEnv. Used to splice in a slice an upstream package exposes (e.g. cache.ManifestEnv()):
nexus.Run(cfg,
cache.Module,
nexus.DeclareEnvList(cache.ManifestEnv()),
...
)
Lets a leaf package describe its env surface as static data without importing nexus (which would cycle). The app composes the declaration at boot.
func DeclareService ¶ added in v0.22.1
func DeclareService(s manifest.ServiceNeed) Option
DeclareService produces an Option that registers one ServiceNeed.
func IfDev ¶ added in v0.84.0
IfDev applies the supplied options ONLY when running under `nexus dev` (NEXUS_DEV=1). In production the wrapped block is a no-op — useful for dev-only stubs (config.Local, in-memory auth, fake mailers) that have no place in a real deploy.
Variadic so multiple options compose cleanly without a surrounding nexus.Options(...) call. Empty input is a no-op regardless of mode.
func IfNotDev ¶ added in v0.84.0
IfNotDev applies the supplied options ONLY when NOT running under `nexus dev`. The mirror image of IfDev — gate plugins that require production-grade configuration (TLS certs, signing keys, OAuth2 client secrets, config-server URLs) so `nexus dev` boots without forcing the operator to fill those in.
nexus.IfNotDev(
tls.Module(tls.Config{Domains: []string{"app.example.com"}}),
oauth2.Module(oauth2.Config{ClientSecret: secret}),
)
`nexus dev` skips both; `./bin/app` wires them normally.
func Invoke ¶ added in v0.3.0
Invoke runs a function at startup, resolving its parameters from the graph. Use for side-effects on boot — attaching resources, registering hooks, seeding state. Multiple Invoke options run in registration order.
nexus.Invoke(func(app *nexus.App, dbs *DBManager) {
app.OnResourceUse(dbs)
})
func LoadDotenvIfPresent ¶ added in v0.82.0
LoadDotenvIfPresent reads `./.env` (or the supplied path) and populates os.Environ for any key NOT already set in the process environment. Use it in main() so ${VAR} placeholders in nexus.toml — and any other code calling os.Getenv at boot — resolve from the file:
func main() {
nexus.Run(nexus.Config{...},
nexus.LoadDotenvIfPresent(), // ← reads .env if present
appModule,
)
}
Behavior:
- Missing file → silent no-op. Production runs without a .env file boot normally; the platform's injected env vars are the source of truth.
- Existing env vars are preserved. A real `DB_PASSWORD` set by systemd / docker / kubectl always beats the .env entry. This matches Spring's precedence (env vars beat application.yml) and the dotenv convention every other ecosystem uses.
- Parse errors (malformed line, unterminated quotes) fail boot with the line number. A broken .env should not silently produce a partially-loaded environment.
File format — minimal, no shell-style expansion:
KEY=value # plain KEY="value with ==" # quoted; quotes stripped KEY='literal $foo' # single-quoted; literal, no expansion # comment export KEY=value # leading 'export ' allowed, ignored
What's intentionally NOT supported (lean parser, predictable behavior):
- Variable expansion inside values (no $OTHER references)
- Multi-line values
- Shell command substitution
Operators wanting those features should source the .env in a real shell before launching the binary — the framework's job is to consume what the environment already has, not to be a shell.
func LoadExtensionOptions ¶ added in v1.0.0
LoadExtensionOptions reads the [extensions.*] block from nexus.toml at path (defaults to DefaultConfigPath, same as LoadConfig), looks up each declared extension's decoder, and returns the collected Options ready to be spread into nexus.Run.
Operators typically combine with LoadConfig:
cfg := nexus.MustLoadConfig()
extOpts, err := nexus.LoadExtensionOptions()
if err != nil { log.Fatal(err) }
opts := append(extOpts, /* hand-coded options */...)
nexus.Run(cfg, opts...)
Or via the convenience helper LoadExtensions which panics:
nexus.Run(nexus.MustLoadConfig(), nexus.MustLoadExtensions()...)
Behaviour:
- Missing file → returns ([], nil). Operators without a nexus.toml pay nothing; existing code keeps working.
- [extensions.X] declared but no decoder registered for X → returns a wrapped error citing X. Catch via lint at CI time so the error never reaches boot.
- Decoder errors → wrapped with the extension name so the operator knows which block was malformed.
Ordering: decoders execute in alphabetical order of name for repeatable boot behavior. If you need a specific ordering (extension A depends on B's option) wire those via Go code instead — TOML is for data, not graph dependencies.
func LoadField ¶ added in v0.60.0
func LoadField[Parent any, Key comparable, Child any]( fieldName string, keyFn func(Parent) Key, fetch any, ) Option
LoadField registers a batched virtual field on the GraphQL Object type for Parent. The field's resolver pulls a per-request Loader from context, enqueues the parent's key, and returns a thunk that graphql-go's executor dethunks breadth-first — collapsing N individual lookups across sibling resolvers into one fetch call.
This is the framework-shaped fix for the N+1 pattern that nested GraphQL resolvers otherwise produce. Parent and Child are Go types; Key is the lookup key (typically int64 / string / UUID).
The fetch argument is one of three shapes; the framework picks the right path by reflecting on it at registration time:
// (a) Direct fetch — no fx deps. Type-check at compile time.
nexus.LoadField[User, int64, *Bank](
"bank",
func(u User) int64 { return u.ID },
func(ctx context.Context, ids []int64) (map[int64]*Bank, error) {
return db.BanksByUserIDs(ctx, ids)
},
)
// (b) Constructor returning Fetch[K, V] — fx resolves the
// constructor's params at boot. Inner Fetch is fully typed.
func NewBankFetcher(db *DB) dataloader.Fetch[int64, *Bank] {
return func(ctx context.Context, ids []int64) (map[int64]*Bank, error) {
return db.BanksByUserIDs(ctx, ids)
}
}
nexus.LoadField[User, int64, *Bank]("bank", keyFn, NewBankFetcher)
// (c) Inline fetch with trailing fx-injected deps — fx resolves
// the deps at boot; ctx + keys remain the head of the fn.
nexus.LoadField[User, int64, *Bank](
"bank",
func(u User) int64 { return u.ID },
func(ctx context.Context, ids []int64, db *DB, cache *Cache) (map[int64]*Bank, error) {
...
},
)
Wrong shapes surface as an fx.Error at app start with a message describing the expected signatures.
Parent's SDL name is the Go type's reflect name (User → "User"). Type aliases unwrap to the original (`type User = users.Row` registers against "Row"); use a defined type or rename if that isn't what you want.
func Managed ¶ added in v1.4.0
func Managed[T any](name string, build func(*zap.Logger) (*T, error), resourceFor func(*T) resource.Resource) Option
Managed is the general-purpose declarative binder for any resource manager that doesn't fit the kind-specific Database[T] / Cache[T] / pubsub.Broker[T] (e.g. a custom RabbitMQ/queue manager, an object store client, a third-party SDK wrapper).
Unlike Database[T], the caller constructs the whole *T in build — so T can have arbitrary extra fields, not just an embedded manager — and returns an error to fail boot when the resource is required. The framework then:
manages lifecycle automatically: on boot it calls Start() if *T has one; on shutdown it calls Stop() (or Close()). Both void and error-returning forms are detected, so db/cache/queue managers (Start()/Stop()) and transport-style handles (Close() error) all work without the caller wiring hooks.
registers a dashboard resource via resourceFor (pass nil to skip — useful for a managed handle that isn't a topology node).
type RabbitMQ struct { *rabbitmq.RabbitMQManager cfg *rabbitmq.RabbitMQConfig }
nexus.Managed("rabbitmq", func(logger *zap.Logger) (*RabbitMQ, error) { cfg := rabbitmq.NewRabbitMQConfig() return &RabbitMQ{rabbitmq.NewRabbitMQManager(cfg, logger), cfg}, nil }, func(r *RabbitMQ) resource.Resource { return resource.NewQueue("rabbitmq", "RabbitMQ broker", map[string]any{"broker": "rabbitmq"}, r.IsConnected) }, )
Handlers inject *RabbitMQ unchanged.
func Module ¶ added in v0.3.0
Module groups options under a name. Mirrors fx.Module's logging — the group name appears in startup/shutdown logs and in error messages, which helps when several modules touch the same service or resource. The name is also stamped onto every AsQuery/AsMutation/AsRest registration inside the module so the dashboard's architecture view can group endpoints by module container.
var advertsModule = nexus.Module("adverts",
nexus.Provide(NewAdvertsService),
nexus.AsQuery(NewGetAllAdverts),
nexus.AsMutation(NewCreateAdvert, …),
)
func MustLoadDotenv ¶ added in v0.82.0
MustLoadDotenv is the strict variant: a missing file fails boot instead of being a no-op. Use it when the .env contents are required (you've intentionally committed a stub for dev and want to catch "forgot to copy it" mistakes before they become silent `${VAR}` lookup failures).
func MustLoadExtensions ¶ added in v1.0.0
MustLoadExtensions is the panic-on-error variant matching MustLoadConfig's idiom. Use in main() when an extension block is required to boot.
func Options ¶ added in v0.21.18
Options bundles multiple Option values into a single Option. Useful when one logical feature expands into several: a conditional gate that pulls in a frontend mount + a config supply + an extra invoke, for example. Empty input is a no-op.
func Provide ¶ added in v0.3.0
Provide registers one or more constructor functions with the dep graph and auto-detects two opt-in extensions:
Resource providers: any returned value implementing NexusResourceProvider has its resource.Resource list registered with the app at boot. Add UseReporter alongside and OnResourceUse wires automatically — service→resource edges appear on first UsingCtx call without manual plumbing.
Service wrappers: when the first return is a *T whose struct anonymously embeds *nexus.Service, the constructor's params are scanned for resource providers and other service wrappers. The resulting (resourceDeps, serviceDeps) lists are recorded on the service's registry entry so the dashboard's architecture view draws service→service and service→resource edges at the SERVICE layer with no extra annotation.
Constructors that don't trigger either detector behave like plain fx.Provide — return types enter the graph, params resolve from it. Mixed sets (one service wrapper + one resource manager + one plain helper) work in a single call.
nexus.Provide(
NewDBManager, // resource provider — auto-registered
NewCacheManager, // ditto
NewAdvertsService, // service wrapper — deps recorded
NewClock, // plain type — just enters the graph
)
func Raw ¶ added in v0.3.0
Raw is an escape hatch: accept any fx.Option and route it through nexus. For features nexus hasn't mirrored yet (fx.Decorate, fx.Replace, named values via fx.Annotate with ParamTags, etc.) or one-off integrations. Normal apps never need it.
nexus.Raw(fx.Decorate(wrapLogger))
func ServeFrontend ¶ added in v0.21.16
func ServeFrontend(fsys fs.FS, root string, opts ...FrontendOption) Option
ServeFrontend mounts a built single-page-app bundle from an embedded filesystem. The classic shape:
//go:embed all:web/dist
var webFS embed.FS
nexus.Run(nexus.Config{...},
nexus.ServeFrontend(webFS, "web/dist"),
uaa.Module,
interview.Module,
)
The `root` argument is the directory inside fsys that holds index.html plus the asset subdirectories — typically the same path passed to //go:embed minus the `all:` prefix. Pass "" when fsys is already rooted at the dist directory (e.g. after fs.Sub).
Pass nexus.FrontendAt("/admin") (or any sub-path) to mount the SPA under a sub-path instead of at the deployment root — useful when REST/GraphQL live at /api/* and the frontend should answer at /admin/* on the same listener.
Behavior (under the deployment route prefix when one is set, then the FrontendAt mount path when one is set):
- Files with an extension (foo.js, /assets/main.css, /favicon.ico) are served from the embed.FS directly. Files under /assets/ get an immutable far-future Cache-Control — Vite, Webpack, and esbuild all stamp content hashes into filenames there, so the cached copy can never go stale.
- Anything else is treated as a client-side route and gets index.html with a no-cache header (so an updated bundle is picked up on the next reload, not held for a year).
- REST / GraphQL / WebSocket / dashboard routes are registered before the NoRoute hook fires, so they win on conflict.
App boot fails fast when the FS lacks an index.html so a stale or unbuilt bundle surfaces at start time, not at first request.
func Supply ¶ added in v0.3.0
Supply puts concrete values into the graph (no constructor). Useful for config structs or pre-built instances created outside the fx graph.
nexus.Supply(nexus.Config{Server: ServerConfig{Addr: ":8080"}}) // rare — Run takes Config directly
nexus.Supply(myAlreadyBuiltClient) // typical
func WithGraphQL ¶ added in v0.21.28
func WithGraphQL() Option
WithGraphQL turns on GraphQL op generation for AsCRUD. By default AsCRUD only registers REST endpoints; pass this option (and optionally WithoutREST) to opt in to the GraphQL surface.
nexus.AsCRUD[User](resolver, nexus.WithGraphQL()) // REST + GraphQL nexus.AsCRUD[User](resolver, nexus.WithGraphQL(), nexus.WithoutREST()) // GraphQL only
func WithoutREST ¶ added in v0.21.28
func WithoutREST() Option
WithoutREST disables REST endpoint generation for AsCRUD. Combine with WithGraphQL() to expose the resource over GraphQL alone. Without WithGraphQL, AsCRUD will return a boot error rather than silently registering nothing.
type Page ¶ added in v0.21.28
type Page[T any] struct { Items []T `json:"items"` Total int `json:"total"` Limit int `json:"limit"` Offset int `json:"offset"` }
Page is the standard list-response envelope. Generic so callers (tests, generated SDKs) keep type safety on the items slice.
type Params ¶ added in v0.3.0
type Params[T any] struct { Context context.Context Args T Source any Info graphql.ResolveInfo // Method is the HTTP verb for REST handlers ("GET", "POST", …). It lets // one handler registered for several methods (e.g. an Inertia page // mounted for GET+POST) branch on the verb. Empty for GraphQL / WS. Method string }
Params is the bundle a reflective resolver receives when it wants more than just typed args — namely the resolve context, parent source, or schema info. Use it as the last parameter of an AsQuery / AsMutation handler (or AsRest, where only Context is filled).
func NewCreateAdvert(
svc *AdvertsService,
dbs *DBManager,
cache *CacheManager,
p nexus.Params[CreateAdvertArgs],
) (*AdvertResponse, error) {
advert := Advert{Title: p.Args.Title, EmployerName: p.Args.EmployerName}
return create(p.Context, advert)
}
The type parameter T is the args struct — its fields carry the same `graphql:"..."` and `validate:"..."` tags as the legacy flat-args form. Use Params[struct{}] for resolvers that need Context/Source/Info but have no user-supplied args.
For simple handlers that only need a context.Context, you can still take that as a plain parameter; Params[T] is additive, not required.
type PathOpt ¶ added in v0.63.0
type PathOpt interface {
Option
}
PathOpt is the static type Path returns. Satisfies Option so the value composes into a nexus.Module as a public-path prefix. Defined as a named interface so future Path behaviors can be added without breaking callers.
func Path ¶ added in v0.21.20
Path sets the module's public URL path. Equivalent to declaring both nexus.RoutePrefix(path) on the module AND service.AtGraphQL(path+"/graphql") on the module's service — expressed once, kept in sync.
var Module = nexus.Module("uaa",
nexus.DeployAs("uaa-svc"),
nexus.Path("/oats-uaa"),
nexus.Provide(NewService),
nexus.AsRest("POST", "/oauth/token", TokenHandler),
nexus.AsQuery(NewSearchUsers),
)
Effect: REST endpoints mount under /oats-uaa/* and GraphQL fields belonging to this module mount at /oats-uaa/graphql.
Why bother (vs a deployment-level prefix in the manifest): Path travels with the module — same URL in monolith and split deployments. The SPA's calls to /oats-uaa/graphql work in both shapes without conditional client logic.
Convention: the module name (first arg of nexus.Module) and the *Service name (passed to app.Service in the constructor) must match for the GraphQL path override to apply. Path looks up app.Service(name) by the module's name; if the service uses a different name, declare AtGraphQL explicitly for that service instead.
Leading slash is added if missing; trailing slash is trimmed.
The raw path is stored unchanged; module-side consumers normalize when reading (so "/" → "" for prefix purposes).
type PluginRecord ¶ added in v0.38.0
type PluginRecord struct {
Name string
Version string
Namespace string // SDK accessor, "" if none
HasDashboard bool // declares Dashboard contribution
HasClient bool // declares Client contribution
HasGenerate bool // declares Generate contribution (codegen driver)
Tab *TabRecord // nav-tab metadata, nil if none
LiveEvents []string // trace event names the plugin emits
}
PluginRecord is the inert metadata snapshot for a registered plugin. extension.Use builds one of these per Plugin and passes it to (*App).RegisterPlugin so the dashboard / introspection surfaces can list what's wired into the app without depending on the extension package directly.
type PublicOption ¶ added in v1.14.0
type PublicOption struct{}
PublicOption is the cross-transport carrier returned by Public — implements RestOption, GqlOption, and WSOption so one expression flows through any endpoint builder, mirroring AuthRouteOption / MiddlewareOption.
func Public ¶ added in v1.14.0
func Public() PublicOption
Public marks an endpoint as exempt from any framework-installed default gate — the explicit opt-out for deny-by-default auth (auth.Authorization.Default). With no default gate configured it's a harmless no-op marker. Works on REST (AsRest / AsRestHandler), GraphQL (AsQuery / AsMutation), and WS (AsWS):
nexus.AsRest("GET", "/health", NewHealth, nexus.Public())
Login endpoints marked nexus.AuthRoute("login") are auto-exempted (you can't require auth to obtain auth), so they need no Public().
type RateLimitConfigBlock ¶ added in v0.97.0
RateLimitConfigBlock is the TOML shape of ratelimit.Limit. Mirrors the most commonly-set fields; operators wanting per-endpoint overrides should stay in Go.
type ResponseRenderer ¶ added in v1.15.0
ResponseRenderer overrides how a REST endpoint's *successful* return value is written to the response. By default an AsRest handler's return is encoded with c.JSON(...); attaching a renderer via WithRenderer hands that final write to custom code instead.
This is the single extension point the Inertia integration (github.com/paulmanoni/nexus/extension/inertia) builds on: a page handler stays an ordinary reflective handler returning a typed props struct, and the renderer wraps that struct into the Inertia page object — emitting JSON for XHR visits or an HTML document shell for full loads. Keeping the hook here (rather than re-implementing handler reflection in the extension) means params binding, validation, DI, tracing, and metrics behave identically to every other REST endpoint.
Render receives the live *gin.Context (headers, request URL, the ResponseWriter) and the handler's return value. Returning an error is reported through gin.Context.Error and, if nothing has been written yet, produces a 500 — same as a handler error. Only successful returns reach a renderer; the error path is untouched.
type RestOption ¶ added in v0.3.0
type RestOption interface {
// contains filtered or unexported methods
}
RestOption tunes an AsRest registration. Interface (not a func) so nexus.Use can satisfy both GqlOption and RestOption from a single value. The one-off func-shaped helpers below adapt via restOptionFn.
func Description ¶ added in v0.3.0
func Description(s string) RestOption
Description sets the human-readable description shown on the dashboard.
func WithRenderer ¶ added in v1.15.0
func WithRenderer(r ResponseRenderer) RestOption
WithRenderer attaches a ResponseRenderer to a single AsRest registration, replacing the default JSON success write. It is a REST-only option (GraphQL and WebSocket returns are encoded by their own transports).
nexus.AsRest("GET", "/users", NewListUsers, nexus.WithRenderer(myRenderer))
Most apps never call this directly — higher-level helpers such as inertia.Page wire the renderer for you.
type RuntimeConfigBlock ¶ added in v0.97.0
type RuntimeConfigBlock struct {
Server ServerConfigBlock `toml:"server"`
Dashboard DashboardConfigBlock `toml:"dashboard"`
GraphQL GraphQLConfigBlock `toml:"graphql"`
Middleware MiddlewareConfigBlock `toml:"middleware"`
DevReload DevReloadConfigBlock `toml:"devreload"`
Environment string `toml:"environment"`
Version string `toml:"version"`
Introspection bool `toml:"introspection"`
IntrospectionNetworks []string `toml:"introspection_networks"`
TraceCapacity int `toml:"trace_capacity"`
SDK bool `toml:"sdk"`
}
RuntimeConfigBlock is the TOML-tagged mirror of Config. Each field corresponds to a top-level [runtime.<key>] table or scalar in nexus.toml.
Schema sample:
[runtime] environment = "production" version = "1.2.3" introspection = false introspection_networks = ["127.0.0.0/8", "10.0.0.0/8"] trace_capacity = 1000 [runtime.server] addr = ":8080" route_prefix = "/api" [runtime.server.listeners.public] addr = ":8080" scope = "public" [runtime.server.listeners.admin] addr = "127.0.0.1:7000" scope = "admin" [runtime.dashboard] enabled = true name = "My App" [runtime.devreload] exclude = ["uploads", "*.tmp"] [runtime.graphql] path = "/graphql" pretty = false debug = false disable_playground = false document_cache_size = 1024 [runtime.middleware.cors] allow_origins = ["https://app.example.com"] allow_methods = ["GET", "POST"] allow_credentials = true max_age = "12h" [runtime.middleware.ratelimit] rpm = 600 burst = 50
Fields not in the TOML leave the corresponding Config field zero-valued, so the framework's defaults apply.
type ServerConfig ¶ added in v0.19.0
type ServerConfig struct {
// Addr is the HTTP listen address used in single-listener
// mode (default ":8080"). Ignored when Listeners is non-empty.
// Manifest-driven defaults via DeploymentDefaults.Addr fill
// this when zero, so split binaries each pick up their own
// per-deployment port.
Addr string
// Listeners declares one or more named listeners with explicit
// scopes. Empty Addrs auto-fill from the resolved Addr above
// (admin = port+1000, internal = port+2000); explicit Addrs
// are passed through unchanged.
Listeners map[string]Listener
// RoutePrefix is prepended to every user-mounted route — REST
// endpoints, the GraphQL POST mount, and WebSocket upgrades —
// so a single binary can be served behind a path-based ingress.
// Framework routes (/__nexus, /health, /ready) are not prefixed.
//
// Typical use: per-deployment routing in a shared-domain setup,
// e.g. /oats-uaa/* on the uaa-svc binary and /oats-interview/*
// on the interview-svc binary. Set in source via Config or
// declaratively via nexus.toml's `prefix:` per deployment;
// the manifest value lands here through DeploymentDefaults.
//
// Leading slash is required; trailing slash is trimmed at apply
// time so paths concatenate cleanly.
RoutePrefix string
}
ServerConfig groups the network-binding knobs. Addr is the single-listener fallback (used when Listeners is empty); Listeners declares one or more named listeners with explicit scopes. Both optional — leaving both zero binds a single listener at :8080 with ScopePublic.
When Listeners is set, Addr is ignored and every declared listener binds. The framework installs a scope-filter middleware that 404s out-of-scope routes per listener (e.g. requests to /__nexus/* on the public listener).
type ServerConfigBlock ¶ added in v0.97.0
type ServerConfigBlock struct {
Addr string `toml:"addr"`
RoutePrefix string `toml:"route_prefix"`
Listeners map[string]ListenerConfigBlock `toml:"listeners"`
}
ServerConfigBlock is the TOML shape of ServerConfig.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service is a named group of endpoints. Services are the nodes the dashboard draws in the architecture view.
A Service also carries the GraphQL mount path for reflective controllers registered via nexus.AsQuery / AsMutation. The auto-mount step inside fxmod.Module reads this path at fx.Start and wires the schema onto the engine. Default is "/graphql"; override via (*Service).AtGraphQL.
func (*Service) AtGraphQL ¶ added in v0.3.0
AtGraphQL overrides the GraphQL mount path for this service. Most apps keep the default ("/graphql") — override only when you need a per-service path (e.g. "/admin/graphql") or want to unify multiple nexus apps behind the same reverse proxy.
app.Service("admin").AtGraphQL("/admin/graphql").Describe("Admin ops")
func (*Service) Attach ¶
Attach links a resource to this service so the dashboard draws an edge. If the resource isn't already registered, Attach registers it too — that's convenient for ad-hoc services but means typos silently create orphan nodes. For centrally-declared resources, prefer .Using("name") instead.
func (*Service) Auth ¶ added in v0.3.0
func (s *Service) Auth(fn UserDetailsFn) *Service
Auth wires a Bearer-token → user hook. Resolvers read the user via graph.GetRootInfo(p, "details", &user) after successful authentication. Per-service because different services often use different auth mechanisms (admin vs public).
func (*Service) GraphQLPath ¶ added in v0.3.0
GraphQLPath returns the mount path set via AtGraphQL (or the default). Read by the auto-mount Invoke; users rarely need this.
func (*Service) MountGraphQL ¶
MountGraphQL attaches schema (assembled by go-graph or graphql-go) and auto-registers every operation into the nexus registry. Pass gql.With* options for auth (UserDetailsFn), Playground, Pretty, and DEBUG.
func (*Service) Name ¶ added in v0.3.0
Name returns the service's identifier (same string used on the dashboard and passed to *App.Service). Exposed so framework internals (the auto- mount Invoke, service wrappers) can identify a service without reaching into the private field.
func (*Service) Using ¶
Using attaches already-registered resources by name so the dashboard draws edges. An empty string resolves to the default database (the resource of kind Database marked resource.AsDefault(), or the lexically-first if none is marked). Unknown names are attached anyway so the registry shows a disconnected edge — surfacing the typo rather than hiding it.
app.Service("adverts").Using("").MountGraphQL(...) // default DB
app.Service("qb").Using("questions", "session").MountGraphQL(...) // explicit
func (*Service) UsingDefaults ¶
UsingDefaults attaches the default resource of every kind that has at least one registered (database, cache, queue). Useful for services that touch the common "main DB + session cache" pair without naming either.
type Store ¶ added in v0.21.28
type Store[T any] interface { Find(ctx context.Context, id string) (*T, error) Search(ctx context.Context, opts ListOptions) (items []T, total int, err error) Save(ctx context.Context, item *T) error Remove(ctx context.Context, id string) error }
Store is the persistence contract AsCRUD operates against. A resolver function (passed to AsCRUD as its first argument) yields a Store on each request, so request-scoped behaviour — multi- tenancy, transactions, read-replica routing — is just "build the right Store in the resolver body."
Custom backends (GORM, sqlc, Redis, …) implement this interface directly. The framework ships MemoryStore[T] for prototyping and tests; the GORM adapter ships in nexus/storage/gorm (subpackage) for production use.
Method contracts:
- Find: returns ErrCRUDNotFound when id is unknown.
- Search: returns the page slice + total count for pagination.
- Save: upsert by id (Find then Save round-trips a create).
- Remove: returns ErrCRUDNotFound when id is unknown.
type StoreConfig ¶ added in v0.19.0
type StoreConfig struct {
// RateLimit replaces the default in-memory rate-limit store.
// Set when you want to share the store between the app and
// externally-built middleware bundles (ratelimit.NewMiddleware
// consumes a Store), or for persistence / multi-replica via a
// Redis-backed implementation. Nil → app builds its own
// MemoryStore (or cache-backed when Cache is set).
RateLimit ratelimit.Store
// Metrics replaces the default cache-backed metrics store. Use
// for Prometheus / StatsD / OTel-backed implementations. The
// dashboard's /__nexus/stats endpoint reads from whichever
// Store is installed.
Metrics metrics.Store
// Cache is the framework's general-purpose cache.Manager. When
// set, nexus uses it as the default backing for the metrics +
// rate-limit stores (so counters and overrides benefit from
// the app's cache tier). Pass your own when user code already
// runs a cache.Manager — framework + app share one tier.
//
// Explicit RateLimit / Metrics settings still win; Cache is
// just the default when those are nil.
//
// Typed as the root Cache interface so the core stays decoupled
// from extension/cache; *cache.Manager satisfies it.
Cache Cache
}
StoreConfig groups the framework's pluggable backends. All fields are optional — leave them nil and the framework supplies in- memory / cache-backed defaults. Set explicitly to share state across replicas, push to a monitoring stack, or hand the framework an existing cache tier.
type UseReporter ¶
UseReporter is satisfied by any type that exposes an OnUse hook with this exact signature. multi.Registry and anything embedding it fit — including the project's own DBManager wrapper. This is a structural interface so nexus doesn't need to import nexus/multi directly.
type UserDetailsFn ¶ added in v0.3.0
UserDetailsFn, when set on a service, routes GraphQL requests through graph.NewHTTP so resolvers can read the authenticated user via graph.GetRootInfo(p, "details", &user). Returning an error aborts the request with the framework's standard unauthenticated shape.
type UserError ¶ added in v0.14.0
type UserError struct {
Op string // short verb-noun: "topology", "remote call", "path expand"
Msg string // primary one-line description
Notes []string // optional context lines (peer list, body snippet, etc.)
Hint string // optional fix recipe; single line
Cause error // optional wrap — accessible via errors.Unwrap
}
UserError is the framework's developer-facing error envelope. Fields produce a multi-line format with an optional `hint:` recipe and an optional `cause:` wrap so a developer hitting a framework error sees what went wrong, what to do about it, and the underlying cause in one block — instead of having to chase the message through several fmt.Errorf wraps.
nexus error [topology]: Deployment "users-svc" not in Topology.Peers declared peers: [checkout-svc orders-svc] hint: add Topology.Peers["users-svc"] in main.go's nexus.Config — URL may be empty for the active unit
User code typically doesn't construct these — the framework emits them at known failure boundaries. They behave like normal errors (Error / Unwrap), so existing error-handling paths work unchanged.
type WSOption ¶ added in v0.9.0
type WSOption interface {
// contains filtered or unexported methods
}
WSOption tunes an AsWS registration. Interface (not a func) so nexus.Use and auth.Required() — which return middleware bundles — can satisfy both RestOption and WSOption from a single value.
type WSSession ¶ added in v0.9.0
type WSSession struct {
// contains filtered or unexported fields
}
WSSession is the per-connection handle injected into AsWS handlers. Every handler that declares `*nexus.WSSession` as a parameter receives the session tied to the connection that produced the current inbound message. Safe for concurrent use — wraps a ws.Hub under the hood.
The framework uses the same envelope protocol as the built-in ws.Hub:
{ "type": "chat.send", "data": {...}, "timestamp": <unix> }
Emit / EmitToUser / EmitToRoom / EmitToClient publish in that shape; SendRaw is the escape hatch for non-envelope payloads.
func (*WSSession) ClientID ¶ added in v0.9.0
ClientID is the UUID the hub minted for this connection at upgrade time.
func (*WSSession) Context ¶ added in v0.9.0
Context returns a context cancelled when the connection disconnects. Safe to pass downstream — long-running work will unblock on hangup.
func (*WSSession) Emit ¶ added in v0.9.0
Emit broadcasts an envelope to every connection on this endpoint.
func (*WSSession) EmitToClient ¶ added in v0.9.0
EmitToClient sends an envelope to the connections with the given IDs.
func (*WSSession) EmitToRoom ¶ added in v0.9.0
EmitToRoom sends an envelope to every connection subscribed to room.
func (*WSSession) EmitToUser ¶ added in v0.9.0
EmitToUser sends an envelope to every connection authed as one of userIDs.
func (*WSSession) JoinRoom ¶ added in v0.9.0
JoinRoom subscribes this connection to a room. Matching server-side helper for the client's `{"type":"subscribe","room":"..."}` message.
func (*WSSession) Metadata ¶ added in v0.9.0
Metadata is the map the hub's identify hook populated at upgrade time. Read freely; mutation is not safe across goroutines.
func (*WSSession) Send ¶ added in v0.9.0
Send wraps data in an envelope and unicasts it to this connection.
Source Files
¶
- app.go
- app_conditional.go
- app_cron.go
- app_options.go
- app_recovery.go
- app_use.go
- app_values.go
- app_workers.go
- bus.go
- cache.go
- client_option.go
- config_get.go
- config_store.go
- crud_endpoints.go
- crud_reflect.go
- crud_store.go
- crud_types.go
- database_toml.go
- dotenv.go
- env_config.go
- ext_devreload.go
- ext_frontend.go
- ext_plugins.go
- ext_public_path.go
- ext_usererror.go
- extension_toml.go
- managed_generic.go
- manifest_app.go
- manifest_auto.go
- manifest_automount.go
- manifest_config.go
- manifest_cors.go
- manifest_print.go
- manifest_resolve.go
- notifier.go
- obs_graph_status.go
- obs_health.go
- obs_integration.go
- obs_introspection.go
- obs_listeners.go
- reflect_handler.go
- reflect_handler_ext.go
- reflect_loadfield.go
- reflect_params.go
- reflect_runtime.go
- resource_declare.go
- routing_auth.go
- routing_default_gate.go
- routing_endpoint_chain.go
- routing_endpoint_config.go
- routing_hidden.go
- routing_public.go
- routing_renderer.go
- routing_service.go
- runtime_config_lint.go
- runtime_config_loader.go
- transport_graph.go
- transport_rest.go
- transport_websocket.go
- transport_ws.go
Directories
¶
| Path | Synopsis |
|---|---|
|
Package client embeds and serves the nexus client SDK — a JS/TS runtime, generated TypeScript types, and Vue 3 composables — directly from the Go binary.
|
Package client embeds and serves the nexus client SDK — a JS/TS runtime, generated TypeScript types, and Vue 3 composables — directly from the Go binary. |
|
cmd
|
|
|
nexus
command
Command nexus is the developer CLI for the nexus framework.
|
Command nexus is the developer CLI for the nexus framework. |
|
Package crud carries zero-sized marker types used as the first generic parameter of nexus.On[A, T] to identify which CRUD action a handler overrides.
|
Package crud carries zero-sized marker types used as the first generic parameter of nexus.On[A, T] to identify which CRUD action a handler overrides. |
|
Package dataloader coalesces N individual key lookups into one batched fetch, eliminating the N+1 query pattern that GraphQL's nested resolvers otherwise produce.
|
Package dataloader coalesces N individual key lookups into one batched fetch, eliminating the N+1 query pattern that GraphQL's nested resolvers otherwise produce. |
|
Package db is nexus's driver-agnostic GORM manager.
|
Package db is nexus's driver-agnostic GORM manager. |
|
examples
|
|
|
bigtopo
command
Command bigtopo is a synthetic large-topology app for stress-testing the dashboard's architecture graph (drill-down, edge bundling, level-of-detail, layout/perf) at ~1000 nodes — far beyond what the small examples produce.
|
Command bigtopo is a synthetic large-topology app for stress-testing the dashboard's architecture graph (drill-down, edge bundling, level-of-detail, layout/perf) at ~1000 nodes — far beyond what the small examples produce. |
|
fxapp
command
An example showing nexus's top-level builder: nexus.Run composes modules, nexus.Provide / Invoke / Module replace fx.Provide / Invoke / Module — no go.uber.org/fx import in user code.
|
An example showing nexus's top-level builder: nexus.Run composes modules, nexus.Provide / Invoke / Module replace fx.Provide / Invoke / Module — no go.uber.org/fx import in user code. |
|
graphapp
command
Example: a complete GraphQL service wired with nexus's reflective controller API.
|
Example: a complete GraphQL service wired with nexus's reflective controller API. |
|
petstore
command
|
|
|
petstore-spa
command
Command petstore-spa is a runnable end-to-end demo of the nexus frontend extension.
|
Command petstore-spa is a runnable end-to-end demo of the nexus frontend extension. |
|
pubsub
command
Demonstrates nexus's typed pub/sub primitive end-to-end:
|
Demonstrates nexus's typed pub/sub primitive end-to-end: |
|
wsecho
command
Command wsecho is a runnable demo of nexus.AsWS: one WebSocket path (/events) with two typed message handlers (chat.send + chat.typing) sharing one connection pool.
|
Command wsecho is a runnable demo of nexus.AsWS: one WebSocket path (/events) with two typed message handlers (chat.send + chat.typing) sharing one connection pool. |
|
wstest
command
|
|
|
Package extension is the plugin seam for nexus.
|
Package extension is the plugin seam for nexus. |
|
auth
Package auth is nexus's built-in authentication surface.
|
Package auth is nexus's built-in authentication surface. |
|
cache
Package cache provides a cache for nexus apps.
|
Package cache provides a cache for nexus apps. |
|
cache/redis
Package redis is the opt-in Redis backend for the nexus cache.
|
Package redis is the opt-in Redis backend for the nexus cache. |
|
config
Package config wires Spring-Cloud-Config-style configuration distribution into a nexus mesh.
|
Package config wires Spring-Cloud-Config-style configuration distribution into a nexus mesh. |
|
config/internal/canonical
Package canonical emits RFC 8785-style canonical JSON so two servers producing the same value tree produce byte-identical signing inputs.
|
Package canonical emits RFC 8785-style canonical JSON so two servers producing the same value tree produce byte-identical signing inputs. |
|
cors
Package cors implements CORS (Cross-Origin Resource Sharing) for nexus apps.
|
Package cors implements CORS (Cross-Origin Resource Sharing) for nexus apps. |
|
cron
Package cron registers and runs scheduled jobs alongside the app's HTTP surface.
|
Package cron registers and runs scheduled jobs alongside the app's HTTP surface. |
|
dashboard
Package dashboard mounts the nexus introspection surface under /__nexus.
|
Package dashboard mounts the nexus introspection surface under /__nexus. |
|
errors
Package errors captures unhandled panics and 5xx-shaped failures from a nexus app, deduplicates them by fingerprint, surfaces the rolling history on the dashboard, and forwards each new occurrence to one or more configured transports (Sentry, generic webhook, stdout).
|
Package errors captures unhandled panics and 5xx-shaped failures from a nexus app, deduplicates them by fingerprint, surfaces the rolling history on the dashboard, and forwards each new occurrence to one or more configured transports (Sentry, generic webhook, stdout). |
|
frontend
Package frontend wires a single-page-app bundle into a nexus app as a first-class plugin.
|
Package frontend wires a single-page-app bundle into a nexus app as a first-class plugin. |
|
inertia
Package inertia adds Inertia.js (https://inertiajs.com) support to nexus: server-driven pages that return a typed props struct instead of building a client-side API.
|
Package inertia adds Inertia.js (https://inertiajs.com) support to nexus: server-driven pages that return a typed props struct instead of building a client-side API. |
|
inertia/iauth
Package iauth bridges nexus's auth extension to Inertia: an auth.ErrorHandler that renders auth denials the way each caller expects — a redirect for page navigations (Inertia visits and full-page loads), the error in the array for GraphQL, and JSON for API/SDK clients.
|
Package iauth bridges nexus's auth extension to Inertia: an auth.ErrorHandler that renders auth denials the way each caller expects — a redirect for page navigations (Inertia visits and full-page loads), the error in the array for GraphQL, and JSON for API/SDK clients. |
|
metrics
Package metrics records per-endpoint request counts + errors so the dashboard can show at-a-glance health next to each op: how busy it is, whether it's failing, and if so, what the last error was.
|
Package metrics records per-endpoint request counts + errors so the dashboard can show at-a-glance health next to each op: how busy it is, whether it's failing, and if so, what the last error was. |
|
oauth2
Package oauth2 wires a go-oauth2/oauth2/v4 server into a nexus app and bridges its access-token store to nexus.auth so handlers can gate themselves with auth.Required() / auth.Requires().
|
Package oauth2 wires a go-oauth2/oauth2/v4 server into a nexus app and bridges its access-token store to nexus.auth so handlers can gate themselves with auth.Required() / auth.Requires(). |
|
openapi
Package openapi auto-generates an OpenAPI 3.1 specification from the framework's typed registry and serves it (plus Swagger UI) from a nexus app.
|
Package openapi auto-generates an OpenAPI 3.1 specification from the framework's typed registry and serves it (plus Swagger UI) from a nexus app. |
|
peer
Package peer wires typed RPC between nexus apps.
|
Package peer wires typed RPC between nexus apps. |
|
ratelimit
Package ratelimit provides the rate-limit primitives nexus uses to throttle endpoints: a Limit shape, a Store interface (pluggable to memory, Redis, or any backend), and a token-bucket MemoryStore.
|
Package ratelimit provides the rate-limit primitives nexus uses to throttle endpoints: a Limit shape, a Store interface (pluggable to memory, Redis, or any backend), and a token-bucket MemoryStore. |
|
tls
Package tls auto-acquires and renews Let's Encrypt TLS certificates for a nexus app, terminating HTTPS in-process — no nginx, certbot, or external reverse proxy needed.
|
Package tls auto-acquires and renews Let's Encrypt TLS certificates for a nexus app, terminating HTTPS in-process — no nginx, certbot, or external reverse proxy needed. |
|
tour
Package tour ships a Spring-Boot-style guided-tour plugin for nexus apps.
|
Package tour ships a Spring-Boot-style guided-tour plugin for nexus apps. |
|
visitors
Package visitors counts page views on a nexus app and exposes the totals over a small public API the frontend polls.
|
Package visitors counts page views on a nexus app and exposes the totals over a small public API the frontend polls. |
|
Package graph provides a modern, secure GraphQL handler for Go with built-in authentication, validation, and an intuitive builder API.
|
Package graph provides a modern, secure GraphQL handler for Go with built-in authentication, validation, and an intuitive builder API. |
|
internal
|
|
|
apidocs
Package apidocs holds the Sphinx-style API reference generator.
|
Package apidocs holds the Sphinx-style API reference generator. |
|
Package manifest is the deploy-time self-description surface for a nexus app.
|
Package manifest is the deploy-time self-description surface for a nexus app. |
|
Package middleware defines nexus's cross-transport middleware model.
|
Package middleware defines nexus's cross-transport middleware model. |
|
Package multi routes N named instances of the same type behind a single .Using(name) dispatcher.
|
Package multi routes N named instances of the same type behind a single .Using(name) dispatcher. |
|
Package pubsub is nexus's typed pub/sub primitive.
|
Package pubsub is nexus's typed pub/sub primitive. |
|
rabbit
Package rabbit is the production RabbitMQ adapter for nexus's pubsub primitive.
|
Package rabbit is the production RabbitMQ adapter for nexus's pubsub primitive. |
|
Package registry stores metadata about every endpoint a nexus app exposes.
|
Package registry stores metadata about every endpoint a nexus app exposes. |
|
Package resource defines the abstractions nexus uses to know about databases, caches, message queues, and other external dependencies so they show up in the dashboard's Architecture view with health status.
|
Package resource defines the abstractions nexus uses to know about databases, caches, message queues, and other external dependencies so they show up in the dashboard's Architecture view with health status. |
|
storage
|
|
|
gorm
Package gorm is the GORM-backed nexus.Store[T] adapter referenced by crud_store.go's doc comment.
|
Package gorm is the GORM-backed nexus.Store[T] adapter referenced by crud_store.go's doc comment. |
|
Package trace captures request-lifecycle events (start, end, downstream calls, logs) into an in-memory ring buffer and fans them out to subscribers such as the dashboard.
|
Package trace captures request-lifecycle events (start, end, downstream calls, logs) into an in-memory ring buffer and fans them out to subscribers such as the dashboard. |
|
transport
|
|
|
gql
Package gql mounts a GraphQL schema (typically assembled by github.com/paulmanoni/nexus/graph) onto Gin and introspects its operations into the nexus registry.
|
Package gql mounts a GraphQL schema (typically assembled by github.com/paulmanoni/nexus/graph) onto Gin and introspects its operations into the nexus registry. |
|
rest
Package rest wires REST endpoints onto a Gin engine and records metadata about them in the nexus registry.
|
Package rest wires REST endpoints onto a Gin engine and records metadata about them in the nexus registry. |
|
ws
Package ws wires WebSocket endpoints onto a Gin engine using gorilla/websocket and records metadata about them in the nexus registry.
|
Package ws wires WebSocket endpoints onto a Gin engine using gorilla/websocket and records metadata about them in the nexus registry. |

