nexus

package module
v1.17.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 16, 2026 License: MIT Imports: 52 Imported by: 0

README

nexus

A Go framework over Gin that lets you write plain handlers, wires them into REST + GraphQL + WebSocket from one signature, and ships a live dashboard at /__nexus/.

Release Go Reference Go version Go Report Card

Why · Install · Quick start · Handlers · Dashboard · Peer mesh · Config

func main() {
    nexus.Run(
        nexus.Config{
            Server:    nexus.ServerConfig{Addr: ":8080"},
            Dashboard: nexus.DashboardConfig{Enabled: true, Name: "Adverts"},
        },
        nexus.ProvideResources(NewMainDB),
        adverts.Module,
    )
}

var Module = nexus.Module("adverts",
    nexus.Provide(NewService),
    nexus.AsQuery(NewListAdverts),
    nexus.AsMutation(NewCreateAdvert,
        auth.Required(),
        auth.Requires("ROLE_CREATE_ADVERT"),
    ),
)

Architecture dashboard


Why nexus

  • One handler, three transports. AsRest / AsQuery / AsMutation / AsWS all read the same reflective signature — func(svc, deps..., p nexus.Params[Args]) (T, error) — and wire the right transport.
  • Live architecture view. nexus.Module groups endpoints; constructor introspection draws service → service / service → resource edges automatically. Real traffic pulses on the edges.
  • Built-in auth, rate limits, metrics, traces. Cross-transport bundles via nexus.Use. Per-op observability is free — every handler gets counters + traces with no user code.
  • Typed peer mesh. peer.AsCall exposes a handler to other apps; peer.Call[T] calls one. HTTP/2 + JSON over mTLS, persistent multiplexed connections, schema drift detection, trace stitching across binaries. See Peer mesh below.
  • Configuration server. Spring-Cloud-Config-style distribution: config.Server hosts plaintext TOML (local folder or git); config.Client fetches signed snapshots with a sealed cache; nexus.Get[T]("key", default) reads typed values from anywhere. WS push for sub-second hot reload. ${VAR} placeholders inside nexus.toml resolve from the process env (and optionally a .env file via nexus.LoadDotenvIfPresent()). See Configuration below.
  • Guided tours for any frontend. extension/tour mounts a Shadow-DOM overlay on every HTML response — record click-by-click walkthroughs with auto-screenshots (multi-scene capture for dropdowns + modals), edit step text inline on the preview, play back as numbered-badge highlights, export to PDF or Word for handoff docs. Works on React, Vue, Angular, vanilla — host CSS can't leak in. See Tours below.
  • Viteless frontend, embedded — no Node required. nexus new --frontend vue|react scaffolds a web/ SPA served by the embedded viteless engine (a zero-Node "Vite for Go": esbuild + a WASM QuickJS runtime). nexus dev gives you HMR; nexus build produces web/dist and go build embeds it via //go:embed. No npm, no node_modules at dev, build, or run time — deps are fetched from the esm.sh CDN (cached). If real Vite or Node is present, viteless transparently uses it for higher fidelity. See Frontend.
  • GraphQL IDE built in. Open /graphql in a browser and you get Apollo Sandbox — schema explorer + query builder against the live app, no wiring. On by default in dev; hide it in prod with disable_playground = true.
  • fx under the hood, not in your imports. nexus.Run/Module/Provide/Invoke wrap fx so you get DI + lifecycle without the import.

Install

go install github.com/paulmanoni/nexus/cmd/nexus@latest

Go 1.26+. The CLI is pure Go — no cgo, no C toolchain, no build tags — so it cross-compiles to a single static binary. The frontend uses the embedded viteless engine, so no Node.js or npm is required at dev, build, or run time. (If Node or a real Vite install is on PATH, viteless uses it automatically for higher fidelity.)

CLI

nexus new my-app && cd my-app
nexus dev                 # go run + live dashboard (add --open to launch a browser)
Command What it does
nexus new <dir> Scaffold a runnable app (prompts for frontend / db / cache / auth).
nexus init [dir] --frontend=vue|react Add a web/ frontend (embed) to an existing project.
nexus dev [dir] go run + the viteless HMR dev server (on :5173); serves the dashboard and proxies /__nexus, /graphql, /oauth, /ws to the app.
nexus build [package] viteless build → web/dist, then go build embeds it via //go:embed. No npm step.
nexus docs [topic] Inline reference (handlers, frontend, auth, …).
nexus client [--out dir] Write the embedded JS/TS client SDK to disk.
nexus generate <kind> Code-generation helpers (e.g. dockerfile).

Quick start

// main.go
package main

import "github.com/paulmanoni/nexus"

func main() {
    nexus.Run(
        nexus.Config{
            Server:    nexus.ServerConfig{Addr: ":8080"},
            Dashboard: nexus.DashboardConfig{Enabled: true, Name: "Demo"},
        },
        helloModule,
    )
}

// hello.go
type HelloService struct{}

func NewHelloService() *HelloService { return &HelloService{} }

type SayHelloArgs struct{ Name string }

func (s *HelloService) SayHello(_ context.Context, p nexus.Params[SayHelloArgs]) (string, error) {
    return "Hello, " + p.Input.Name, nil
}

var helloModule = nexus.Module("hello",
    nexus.Provide(NewHelloService),
    nexus.AsQuery((*HelloService).SayHello),
)

nexus dev runs it and serves the dashboard at http://localhost:8080/__nexus (add --open to launch a browser), exposing the handler over GraphQL ({ sayHello(name:"world") }) and REST (POST /hello/sayHello).

Reflective handlers

One signature, three transports:

// REST: POST /widgets
nexus.AsRest("POST", "/widgets", NewCreateWidget)

// GraphQL: mutation createWidget(input: WidgetInput!): Widget!
nexus.AsMutation(NewCreateWidget)

// GraphQL query: query listWidgets(filter: WidgetFilter): [Widget!]!
nexus.AsQuery(NewListWidgets)

// WebSocket event: emit { type:"chat.send", payload:{...} }
nexus.AsWS("/events", "chat.send", NewChatSend)

Each constructor returns a function with the canonical shape:

func NewCreateWidget(svc *WidgetService) func(context.Context, nexus.Params[CreateArgs]) (Widget, error)

The framework introspects: *WidgetService becomes an fx dependency; Params[CreateArgs] declares the input schema; the return type becomes the GraphQL field type. Args + return get auto-mapped to GraphQL input + object types — no schema duplication.

CRUD generator

type Advert struct {
    ID    uuid.UUID `nexus:"key"`
    Title string
    Body  string
}

var Module = nexus.Module("adverts",
    nexus.ProvideCRUD[Advert]("adverts"),
)

Mounts:

GET    /adverts          → list
GET    /adverts/:id      → get
POST   /adverts          → create
PUT    /adverts/:id      → update
DELETE /adverts/:id      → delete

Plus the corresponding GraphQL queries + mutations. Stores ship for postgres, mysql, sqlite, in-memory; pick via ProvideStore[Advert](store.Postgres{...}).

Cross-transport middleware

nexus.AsMutation(NewCreateAdvert,
    auth.Required(),
    auth.Requires("ROLE_CREATE_ADVERT"),
    ratelimit.PerUser(100, time.Minute),
)

Works identically on REST, GraphQL, and WS. Each middleware reads from nexus.Context rather than *gin.Context so it's transport-agnostic.

Frontend

The frontend is a web/ SPA served by the embedded viteless engine — a zero-Node implementation of the Vite dev/build model in Go (esbuild + a WASM QuickJS runtime). No npm, no node_modules is required: deps are fetched from the esm.sh CDN (cached) or, if you've run npm install, taken from web/node_modules for offline/exact-version builds. nexus build produces web/dist and go build embeds it into the binary; the runtime needs no Node. Vue, React, and Tailwind are handled natively. If a real Vite install or Node is present, viteless delegates to it for 100% fidelity.

nexus new my-app --frontend vue   # scaffold web/ (or --frontend react) — no install needed
cd my-app && nexus dev            # go run + viteless HMR dev server on :5173
nexus build                       # viteless build → web/dist, embedded via //go:embed

main.go embeds the build output and serves it:

//go:embed all:web/dist
var webFS embed.FS

nexus.Run(nexus.Config{...},
    nexus.ServeFrontend(webFS, "web/dist"),
    helloModule,
)

Unknown paths fall through to index.html (SPA-aware); /assets/* gets immutable cache; REST/GraphQL/WS/dashboard routes win on conflict. Boot fails fast if index.html is missing, so the scaffold commits a web/dist/index.html stub — the first go build compiles before any nexus build. The frontend dir is web/ by default (override with NEXUS_FRONTEND_DIR); to mount an existing pre-built SPA, just point ServeFrontend at its embedded dist.

nexus dev — viteless HMR dev server + API proxy

nexus dev runs the viteless HMR dev server on http://localhost:5173/ alongside go run on :8080. viteless proxies every unmatched request (/__nexus, /graphql, /oauth, /ws, your API) straight to the Go app — no managed vite.config proxy block to maintain. Open :5173 for the SPA during dev; the embedded web/dist is served at the app port only in production.

Dev-reload watcher

Under nexus dev (NEXUS_DEV=1), the browser auto-reloads on source changes. The watcher ignores hidden files, sourcemaps, and runtime data artifacts out of the box — SQLite databases (.db / .sqlite / .sqlite3), their -wal / -shm / -journal sidecars, and .log files — so a request that writes the database can't trigger a reload loop.

Add app-specific ignore globs via Config.DevReload.Exclude (or [runtime.devreload] in nexus.toml). Each changed file is matched against its base name, its path relative to the watch root, and as a directory-subtree prefix:

[runtime.devreload]
exclude = ["uploads", "*.tmp", "cache/*.json"]

Invalid patterns are logged once at boot and skipped.

Custom banner

nexus dev prints a NEXUS wordmark on startup. Drop a banner.txt in the directory you run nexus dev from to replace it with your own — the file's contents are rendered verbatim above the target / starting rows, so a project can ship its own ASCII wordmark or message:

$ cat banner.txt
  ~ acme admin ~
  staging

An empty or missing banner.txt falls back to the built-in NEXUS banner.

Dashboard

Mounted at /__nexus/ when Dashboard.Enabled is true. Shows:

  • Architecture: services, resources, endpoints, dependency edges. Live updates as registry mutates.
  • Endpoints: per-op tester (curl + Apollo Sandbox for GraphQL), arg validator chips.
  • Traces: request waterfalls across services; cross-transport stitched.
  • Crons: scheduled jobs + last/next-run.
  • Rate limits: per-key bucket state.

Trace waterfall

Listeners, scopes, and TLS

In production you usually want the dashboard surface (/__nexus/*) reachable from operators but not from the public internet. Nexus does this with named listeners + scopes — one process binds multiple ports, each gated to a subset of routes.

Scopes

nexus.Config{
    Server: nexus.ServerConfig{
        Listeners: map[string]nexus.Listener{
            "public": {Addr: ":8080",          Scope: nexus.ScopePublic},
            "admin":  {Addr: "127.0.0.1:9090", Scope: nexus.ScopeAdmin},
        },
    },
}
  • ScopePublic — user routes (REST / GraphQL / WebSocket) + /__nexus/health + /__nexus/ready. Dashboard hidden.
  • ScopeInternal — same as public; intended for sidecar / peer traffic.
  • ScopeAdmin — everything: dashboard + user routes (so the in-page testers' relative fetches still work).

Routes outside the listener's scope return 404 — byte-equivalent to "not mounted", so scanners can't fingerprint the dashboard.

Introspection gate

Belt-and-braces on top of scopes — defaults to closed so a misconfigured listener doesn't leak the dashboard:

nexus.Config{
    Introspection:         false,                  // closed in prod (default)
    IntrospectionNetworks: []string{"10.0.0.0/8"}, // VPN bypass
}

When closed, every /__nexus/* request 404s unless the caller's TCP peer matches an allowlisted CIDR. The gate uses RemoteIP(), not ClientIP()X-Forwarded-For is spoofable, wrong default for a security gate. Behind an LB, prefer the admin-listener pattern over the CIDR allowlist.

GraphQL field introspection (the __schema query) is gated by the same Introspection flag — closing the dashboard also closes in-band GraphQL introspection.

TLS on a listener

Set Listener.TLS to terminate HTTPS on that port — the raw TCP listener gets wrapped at bind time:

adminTLS, err := nexus.ServerTLSConfig(
    "admin.crt",     // PEM cert
    "admin.key",     // PEM private key
    "admin-ca.crt",  // optional CA bundle; pass "" to skip mTLS
)
if err != nil { log.Fatal(err) }

nexus.Config{
    Server: nexus.ServerConfig{
        Listeners: map[string]nexus.Listener{
            "public": {Addr: ":8080",         Scope: nexus.ScopePublic},
            "admin":  {Addr: "10.0.0.5:9443", Scope: nexus.ScopeAdmin, TLS: adminTLS},
        },
    },
}

When the optional CA file is supplied, the listener requires clients to present a certificate signed by that CA — mTLS, enforced at the TLS layer before any HTTP request is dispatched. Pass "" for server-only TLS.

ServerTLSConfig sets MinVersion = TLS 1.2 by default. For custom cipher suites or SNI via GetCertificate, build a *tls.Config directly and assign it to the TLS field — anything crypto/tls supports works.

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. Listener.TLS is the right tool for admin/internal ports fronted by your own cert material (internal CA, mTLS-protected dashboard, etc.).

Typical production recipe

adminTLS, _ := nexus.ServerTLSConfig("admin.crt", "admin.key", "admin-ca.crt")

nexus.Run(
    nexus.Config{
        Introspection:         false,
        IntrospectionNetworks: []string{"10.0.0.0/8"},
        Server: nexus.ServerConfig{
            Listeners: map[string]nexus.Listener{
                "public": {Addr: ":8080",         Scope: nexus.ScopePublic},
                "admin":  {Addr: "10.0.0.5:9443", Scope: nexus.ScopeAdmin, TLS: adminTLS},
            },
        },
    },
    tls.Plugin(tls.Config{ // public :443 + :80, LE-issued cert for the user-facing port
        Domains: []string{"app.example.com"},
        Email:   "ops@example.com",
    }),
    // ... app modules
)

Result: world-facing :443 serves user traffic over LE-issued HTTPS. Dashboard is reachable only on the private 10.0.0.5:9443 with an mTLS client cert signed by admin-ca.crt. /__nexus/* is 404 on every other surface even if the network somehow leaks.

Peer mesh

Typed RPC between nexus apps over HTTP/2 + JSON. One persistent multiplexed connection per peer pair, mTLS by default, trace stitching across binaries, schema-drift detection — explicit, type-safe, no codegen.

// callee
peer.Module(peer.Config{Identity: "orders-svc", Listen: ":7000", ...})
// + nexus.AsCall("createOrder", NewCreateOrder)

// caller
peer.Module(peer.Config{Identity: "checkout-svc", Peers: map[string]peer.PeerSpec{...}})
order, err := peer.Call[*Order](ctx, peers, "orders-svc", "createOrder", args)

Full reference: extension/peer/README.md covers the safety nets (schema drift, trace stitching, health prober, concurrency cap), SRV discovery for replicas, three auth modes (mTLS / HMAC / none), and the nexus pki PKI toolchain.

Configuration

Two on-disk config surfaces, distinct purposes:

Surface Auto-loaded Purpose
nexus.toml yes (./nexus.toml) Deploy manifest — topology, declared inputs, plugin blocks. What the platform sees + provisions.
extension/config opt-in Runtime key/value store; nexus.Get[T]("key", default) from any handler.

nexus.toml supports ${VAR} / ${VAR:default} placeholders (strict mode) and an opt-in .env loader via nexus.LoadDotenvIfPresent() for dev workflows.

Full reference: extension/config/README.md covers both surfaces — config.Server / config.Client / config.Local, the sealed-cache + signed-snapshot security model, env-var expansion rules, and the dotenv contract.

Tours

Guided walkthroughs for any HTTP-served frontend. Record click-by-click sequences with auto-screenshots, edit inline, replay as numbered-badge highlights, export to PDF / Word for handoff docs. Closed Shadow DOM mount — works on React, Vue, Angular, vanilla without CSS bleed in either direction.

nexus.Run(nexus.Config{...},
    tour.Module(tour.WithGORM(db.DB()), tour.AutoInject(true)),
)

After restart, every HTML page carries a floating ● Tour pill; authoring dashboard is at /__nexus/tour.

Full reference: extension/tour/README.md covers recording semantics, multi-scene capture for dropdowns/modals, the preview-page editor, Word/PDF export with TOC, and the keyboard shortcuts.

Examples

The framework ships a few self-contained examples under examples/:

petstore REST + GraphQL CRUD; the canonical "small app"
petstore-spa Same with a frontend bundle served from embed.FS
pubsub Redis + RabbitMQ adapters
wsecho, wstest WebSocket patterns
graphapp GraphQL-first app with cross-resolver auth
fxapp Direct fx wiring without the nexus.Run wrapper
bigtopo Large-topology stress test for the architecture graph

Layout

.                          framework root + public API surface
├── cmd/nexus/             CLI binary
├── extension/             optional integrations (auth, oauth2, ratelimit,
│                          metrics, cron, dashboard, frontend, peer,
│                          config, tls, …)
├── graph/                 GraphQL builder + resolver introspection
├── transport/             gin/REST + WS adapters
├── manifest/              app self-description JSON (served at /__nexus/manifest)
├── registry/              endpoint + service + resource graph
└── examples/              self-contained sample apps

License

MIT.

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

View Source
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.

View Source
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).

View Source
const DefaultGraphQLPath = "/graphql"

DefaultGraphQLPath is the mount path nexus.AsQuery / AsMutation use when a service hasn't called AtGraphQL.

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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

View Source
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

func BindConfig(prefix string, dst any) error

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 BootFrom added in v1.12.4

func BootFrom(path string, opts ...Option)

BootFrom is Boot with an explicit nexus.toml path.

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

func ClientIPFromCtx(ctx context.Context) string

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

func EnvVars(path ...string) (map[string]string, error)

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

func Get[T any](key string, defaults ...T) T

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:

  1. Environment variable (CONFIG_SERVER_PORT for "config.server.port")
  2. Config-extension snapshot (config.Server / .Client / .Local)
  3. nexus.toml base layer (seeded by LoadConfig/Boot)
  4. 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

func HasConfig(key string) bool

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

func InstallConfigStore(values map[string]any, version string)

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

func LintRuntimeFile(path string) ([]manifest.Issue, error)

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

func MapCRUDError(err error) (status int, ok bool)

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

func MustGet[T any](key string) T

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

func OnConfigChange(key string, fn func(value any))

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

func RegisterGqlType(name string, t graphql.Input)

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 Run added in v0.3.0

func Run(cfg Config, opts ...Option)

func ServerTLSConfig added in v0.74.0

func ServerTLSConfig(certFile, keyFile, caFile string) (*tls.Config, error)

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

func SetGraphStatus(ctx context.Context, code int)

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

func UpdateConfigStore(values map[string]any, version string)

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

func WithClientIP(ctx context.Context, ip string) context.Context

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

func AppFromGin(c *gin.Context) (*App, bool)

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

func New(cfg Config) *App

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

func (a *App) AllowIntrospection(c *gin.Context) bool

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

func (a *App) Bus() *trace.Bus

func (*App) Cache added in v0.3.0

func (a *App) Cache() Cache

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

func (a *App) ClientHandler() *client.Handler

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

func (a *App) DeclareCORS(c manifest.CORSBlock)

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

func (a *App) DeclareEnv(e manifest.EnvVar)

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

func (a *App) DeclareFile(f manifest.File)

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

func (a *App) DeclareHooks(h manifest.Hooks)

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

func (a *App) DeclareOverride(env string, ov manifest.Override)

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

func (a *App) DeclareSecret(s manifest.Secret)

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

func (a *App) DeclareTLS(t manifest.TLSBlock)

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

func (a *App) EffectiveManifest() *manifest.Manifest

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

func (a *App) Engine() *gin.Engine

func (*App) Environment added in v0.40.0

func (a *App) Environment() string

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

func (a *App) LoadDeployManifest(path string) error

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) Metrics added in v0.3.0

func (a *App) Metrics() metrics.Store

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:

  1. reads the current trace.Span from ctx so we know which service made the call
  2. AttachResource(service, resource) on the registry — edge appears live
  3. 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

func (a *App) PrefixPath(p string) string

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 (a *App) RateLimiter() ratelimit.Store

func (*App) Register

func (a *App) Register(r resource.Resource)

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

func (a *App) Registry() *registry.Registry

func (*App) RoutePrefix added in v0.21.15

func (a *App) RoutePrefix() string

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

func (a *App) Run(addr string) error

func (*App) Scheduler added in v0.3.0

func (a *App) Scheduler() *cron.Scheduler

func (*App) SchemaRefs added in v0.28.0

func (a *App) SchemaRefs() map[string]registry.NamedType

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

func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request)

func (*App) Service

func (a *App) Service(name string) *Service

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

func (a *App) SetValue(key, value any)

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

func (a *App) Static(urlPrefix, dir string)

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

func (a *App) UseVolume(v manifest.Volume)

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.

func (*App) Value added in v1.15.3

func (a *App) Value(key any) (any, bool)

Value returns a previously SetValue'd value, or (nil, false) if absent.

func (*App) Version added in v0.9.0

func (a *App) Version() string

Version is the binary's release tag, defaulting to "dev". Surfaced on /__nexus/config.

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

type BoundHandler func(ctx context.Context, args any) (any, error)

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

type CRUDResolver[T any] func(ctx context.Context) (Store[T], error)

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

func LoadConfig(path ...string) (Config, error)

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

func MustLoadConfig(path ...string) Config

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

type DashboardConfigBlock struct {
	Enabled bool   `toml:"enabled"`
	Name    string `toml:"name"`
}

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

type ErrorRenderer interface {
	RenderError(c *gin.Context, err error) (handled bool, rerr error)
}

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

type ExtensionDecoder func(rawTOML []byte) ([]Option, error)

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

type GeneratedFile struct {
	Path string
	Body []byte
}

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

func Deprecated(reason string) GqlOption

Deprecated marks the field deprecated. The reason shows up in SDL and the dashboard "deprecated" badge.

func Desc added in v0.3.0

func Desc(s string) GqlOption

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

func OnService[S any]() GqlOption

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 Op added in v0.3.0

func Op(name string) GqlOption

Op overrides the inferred op name.

func RateLimit added in v0.3.0

func RateLimit(l ratelimit.Limit) GqlOption

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.

func WithArgValidator added in v0.3.0

func WithArgValidator(arg string, vs ...graph.Validator) GqlOption

WithArgValidator adds one or more validators to a named arg, beyond what the struct tags declare. Useful for project-specific rules (graph.Custom validators that call into other code).

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

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

type NexusResourceProvider interface {
	NexusResources() []resource.Resource
}

NexusResourceProvider is implemented by managers that know the external resources they front. A manager's NexusResources slice is used in two places:

  1. 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.
  2. 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

func (n *Notifier) AttachBus(b Bus) (func(), error)

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

func (n *Notifier) NotifyTopic(topic string)

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

func (n *Notifier) SubscribeTopic(topic string) (<-chan struct{}, func())

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

func AsCRUD[T any](resolver any, opts ...Option) Option

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

func AsMutation(fn any, opts ...GqlOption) Option

AsMutation is the mutation analogue of AsQuery.

func AsQuery added in v0.3.0

func AsQuery(fn any, opts ...GqlOption) Option

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

func AsSubscription(fn any, opts ...GqlOption) Option

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

func AsWS(path, msgType string, fn any, opts ...WSOption) Option

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

func AsWorker(name string, fn any) Option

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

func ClientUse(cfg client.Config) Option

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

func DeclareEnv(e manifest.EnvVar) Option

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

func DeclareEnvList(es []manifest.EnvVar) Option

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

func IfDev(opts ...Option) Option

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

func IfNotDev(opts ...Option) Option

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

func Invoke(fns ...any) Option

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

func LoadDotenvIfPresent(path ...string) Option

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

func LoadExtensionOptions(path ...string) ([]Option, error)

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

func Module(name string, opts ...Option) Option

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

func MustLoadDotenv(path ...string) Option

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

func MustLoadExtensions(path ...string) []Option

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

func Options(opts ...Option) Option

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

func Provide(fns ...any) Option

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

func Raw(opt fx.Option) Option

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

func Supply(values ...any) Option

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 UseVolume added in v0.22.1

func UseVolume(v manifest.Volume) Option

UseVolume produces an Option that registers one Volume.

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

func Path(path string) PathOpt

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

type RateLimitConfigBlock struct {
	RPM   int `toml:"rpm"`
	Burst int `toml:"burst"`
}

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

type ResponseRenderer interface {
	Render(c *gin.Context, result any) error
}

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

func (s *Service) AtGraphQL(path string) *Service

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

func (s *Service) Attach(r resource.Resource) *Service

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

func (s *Service) Describe(desc string) *Service

func (*Service) GraphQLPath added in v0.3.0

func (s *Service) GraphQLPath() string

GraphQLPath returns the mount path set via AtGraphQL (or the default). Read by the auto-mount Invoke; users rarely need this.

func (*Service) MountGraphQL

func (s *Service) MountGraphQL(path string, schema *graphql.Schema, opts ...gql.Option)

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

func (s *Service) Name() string

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

func (s *Service) REST(method, path string) *rest.Builder

func (*Service) Using

func (s *Service) Using(names ...string) *Service

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

func (s *Service) UsingDefaults() *Service

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.

func (*Service) WebSocket

func (s *Service) WebSocket(path string) *ws.Builder

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 TabRecord added in v0.38.0

type TabRecord struct {
	ID    string
	Label string
	Icon  string
}

TabRecord is the dashboard nav-tab metadata declared by a plugin.

type UseReporter

type UseReporter interface {
	OnUse(func(ctx context.Context, name string))
}

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

type UserDetailsFn func(ctx context.Context, token string) (context.Context, any, error)

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.

func (*UserError) Error added in v0.14.0

func (e *UserError) Error() string

func (*UserError) Unwrap added in v0.14.0

func (e *UserError) Unwrap() error

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

func (s *WSSession) ClientID() string

ClientID is the UUID the hub minted for this connection at upgrade time.

func (*WSSession) Context added in v0.9.0

func (s *WSSession) Context() context.Context

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

func (s *WSSession) Emit(eventType string, data any)

Emit broadcasts an envelope to every connection on this endpoint.

func (*WSSession) EmitToClient added in v0.9.0

func (s *WSSession) EmitToClient(eventType string, data any, clientIDs ...string)

EmitToClient sends an envelope to the connections with the given IDs.

func (*WSSession) EmitToRoom added in v0.9.0

func (s *WSSession) EmitToRoom(eventType string, data any, room string)

EmitToRoom sends an envelope to every connection subscribed to room.

func (*WSSession) EmitToUser added in v0.9.0

func (s *WSSession) EmitToUser(eventType string, data any, userIDs ...string)

EmitToUser sends an envelope to every connection authed as one of userIDs.

func (*WSSession) JoinRoom added in v0.9.0

func (s *WSSession) JoinRoom(room string)

JoinRoom subscribes this connection to a room. Matching server-side helper for the client's `{"type":"subscribe","room":"..."}` message.

func (*WSSession) LeaveRoom added in v0.9.0

func (s *WSSession) LeaveRoom(room string)

LeaveRoom unsubscribes this connection from a room.

func (*WSSession) Metadata added in v0.9.0

func (s *WSSession) Metadata() map[string]any

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

func (s *WSSession) Send(eventType string, data any) error

Send wraps data in an envelope and unicasts it to this connection.

func (*WSSession) SendRaw added in v0.9.0

func (s *WSSession) SendRaw(data []byte)

SendRaw writes bytes directly to this connection with no envelope. Use when you're speaking a non-nexus protocol (e.g. pre-marshalled binary data).

func (*WSSession) UserID added in v0.9.0

func (s *WSSession) UserID() string

UserID is the identity attached at upgrade (via `?userId=` query or a gin-context `user` interface) or later via the client-initiated `authenticate` protocol message. Empty when the connection is unauthed.

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL