nexus

package module
v0.22.3 Latest Latest
Warning

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

Go to latest
Published: May 2, 2026 License: MIT Imports: 46 Imported by: 0

README

nexus

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/.

Architecture dashboard

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

No fx import. No schema assembly. No middleware plumbing. The handler is plain Go, the dashboard is at /__nexus/.

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; ProvideService introspects constructor params and 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 request/error counters and a trace event with no user code.
  • Manifest-driven deployment. Write the app as a monolith, declare a few split units in nexus.deploy.yaml, ship N independent binaries from the same source. go build -overlay swaps cross-module *Service bodies for HTTP stubs at compile time — your code never branches on deployment.
  • 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 get github.com/paulmanoni/nexus
go install github.com/paulmanoni/nexus/cmd/nexus@latest   # CLI

Requires Go 1.25+.

CLI

nexus new my-app          # scaffold main.go + module.go + go.mod + nexus.deploy.yaml
cd my-app && go mod tidy
nexus dev                 # go run + auto-open the dashboard
Command Description
nexus new <dir> Scaffold a minimal app with a heavily-commented manifest.
nexus init [dir] Add nexus.deploy.yaml to an existing project (scans DeployAs tags).
nexus dev [dir] go run, probe the port, open the dashboard. --split boots one subprocess per deployment unit and streams cross-service traces.
nexus build --deployment <name> Build one deployment binary using go build -overlay (HTTP-stub shadows for non-owned modules + manifest-baked port/peers).
nexus docs [topic] Inline reference for any feature (handlers, deploy, frontend, auth, …). --web opens the GitHub README; --list prints topic names for shell completion.

Quick start

package main

import (
    "context"

    "github.com/paulmanoni/nexus"
)

// Service wrapper — distinct Go type per logical service so fx can
// route by type without named tags.
type AdvertsService struct{ *nexus.Service }

func NewAdvertsService(app *nexus.App) *AdvertsService {
    return &AdvertsService{app.Service("adverts").Describe("Job adverts catalog")}
}

// Typed DB handle — same pattern; fx resolves by type.
type MainDB struct{ *DB }

func NewMainDB() *MainDB { /* open, migrate, return wrapper */ }

// Every dep shows up on the dashboard:
//   *AdvertsService → grounds the op under "adverts"
//   *MainDB         → draws an edge from adverts → main
//   nexus.Params[T] → resolve context + typed args bundle
func NewListAdverts(svc *AdvertsService, db *MainDB, p nexus.Params[struct{}]) (*Response, error) {
    return fetch(p.Context, db)
}

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

Open http://localhost:9080/__nexus/ (admin port = Addr + 1000). Fire a request → packet animation on the Architecture tab.

Reflective handlers

func NewOp(svc *XService, deps..., p nexus.Params[Args]) (*Response, error)
  • First *Service-wrapper dep grounds the op under that service. Single-service apps may omit it; multi-service apps either supply it or pin with nexus.OnService[*Svc](). Service-less handlers (e.g. a public HelloWorld) mount on a synthesized default service partition.
  • Last param (nexus.Params[T] or a trailing struct) carries args. Params[T] exposes Context + Args.
  • Return must be (T, error); T becomes the GraphQL return type.
type CreateArgs struct {
    Title        string `graphql:"title,required"        validate:"required,len=3|120"`
    EmployerName string `graphql:"employerName,required" validate:"required,len=2|200"`
}

func NewCreateAdvert(svc *AdvertsService, db *MainDB, p nexus.Params[CreateArgs]) (*AdvertResponse, error) {
    return create(p.Context, db, p.Args.Title, p.Args.EmployerName)
}

graphql: builds the schema; validate: builds validators that the dashboard renders as chips.

CRUD generator

AsCRUD[T] collapses the five-endpoint CRUD shape into a single declaration. Reflection on T derives the URL prefix, the GraphQL types, and the primary key; a per-request resolver yields the Store[T] the generated handlers run against.

import nxgorm "github.com/paulmanoni/nexus/storage/gorm"

type Note struct {
    ID    string `json:"id"`
    Title string `json:"title" validate:"required,len=1|120"`
    Body  string `json:"body"`
}

var Module = nexus.Module("notes",
    nexus.Provide(NewNotesDB),
    nexus.AsCRUD[Note](
        func(ctx context.Context, db *DB) (nexus.Store[Note], error) {
            return nxgorm.New[Note](db.GetDB())
        },
        nexus.WithGraphQL(),
    ),
)
Surface Endpoints
REST (default) GET /notes · GET /notes/:id · POST /notes · PATCH /notes/:id · DELETE /notes/:id
GraphQL (WithGraphQL()) listNotes(limit, offset, sort) · getNote(id) · createNote(...) · updateNote(id, ...) · deleteNote(id)
  • WithoutREST() — pair with WithGraphQL() for a GraphQL-only resource.
  • validate: tags on T flow into Create/Update — invalid bodies are 400 on REST, resolver errors on GraphQL.
  • The resolver runs once per request, so multi-tenancy / read-replica routing is just "build the right Store from ctx."
  • Any resolver dep that satisfies NexusResources() auto-links its resource node to every generated endpoint on the architecture canvas — no manual edge declarations.
  • Auth, rate limits, Use(...) — all the per-op options work the same way they do on AsRest.
Stores
Adapter Use
nexus.MemoryResolver[T](nil, nil) In-process map; prototyping and tests.
nexus/storage/gorm.New[T](db) Production GORM adapter — UUID assignment, paginated Search, sort whitelisting, and gorm.ErrRecordNotFoundnexus.ErrCRUDNotFound so 404s map cleanly.

Custom backends (Redis, sqlc, …) implement nexus.Store[T] directly:

type Store[T any] interface {
    Find(ctx context.Context, id string) (*T, error)
    Search(ctx context.Context, opts nexus.ListOptions) (items []T, total int, err error)
    Save(ctx context.Context, item *T) error
    Remove(ctx context.Context, id string) error
}

Sentinels ErrCRUDNotFound / ErrCRUDConflict / ErrCRUDValidation map to 404 / 409 / 400; anything else maps to 500.

Options

Option Produces
Module(name, opts...) Named group; stamps module name on every endpoint.
Path(p) Module-level public URL prefix — sets REST mount + per-service GraphQL path (<p>/graphql) in one declaration.
Provide(fns...) Constructor(s) into the dep graph.
ProvideService(fn) Provide + introspect: detects deps and draws Architecture edges.
ProvideResources(fns...) Provide + auto-register resources via NexusResourceProvider.
Supply(vals...) Ready-made values into the dep graph.
Invoke(fn) Side-effect at start-up.
AsRest(method, path, fn, opts...) REST endpoint.
AsCRUD[T](resolver, opts...) Five REST + five GraphQL CRUD ops in one declaration; resolver yields a Store[T] per request.
AsQuery(fn, opts...) / AsMutation(...) GraphQL op, auto-mounted.
AsWS(path, type, fn, opts...) WebSocket scoped to one envelope type.
AsWorker(name, fn) Long-lived background task; framework-managed lifecycle.
Use(middleware.Middleware) Cross-transport middleware (REST + GraphQL).
ServeFrontend(fs, root, opts...) Mount an embedded SPA bundle.
auth.Module(auth.Config{...}) Built-in auth surface.

Cross-transport middleware

authMw := middleware.Middleware{
    Name: "auth", Kind: middleware.KindBuiltin,
    Gin:   authGinHandler,
    Graph: authResolverMiddleware,
}

nexus.AsRest("POST", "/secure", NewSecureHandler, nexus.Use(authMw))
nexus.AsMutation(NewMutate,                       nexus.Use(authMw))

// Engine-root (every HTTP path)
nexus.Config{
    Middleware: nexus.MiddlewareConfig{
        Global:    []middleware.Middleware{requestID, logger, cors},
        RateLimit: ratelimit.Limit{RPM: 600, Burst: 50},
    },
}

Built-ins: ratelimit.NewMiddleware(...), framework-attached metrics.

Auth

import "github.com/paulmanoni/nexus/auth"

nexus.Run(nexus.Config{...},
    auth.Module(auth.Config{
        Resolve: func(ctx context.Context, tok string) (*auth.Identity, error) {
            u, err := myAPI.ValidateToken(ctx, tok)
            if err != nil { return nil, err }
            return &auth.Identity{ID: u.ID, Roles: u.Roles, Extra: u}, nil
        },
        Cache: auth.CacheFor(15 * time.Minute),
    }),
    advertsModule,
)

nexus.AsMutation(NewCreateAdvert,
    auth.Required(),                       // 401 if missing
    auth.Requires("ROLE_CREATE_ADVERT"),   // 403 if missing perm
)

Extractors: auth.Bearer(), auth.Cookie(name), auth.APIKey(header), auth.Chain(...). Typed access: user, ok := auth.User[MyUser](p.Context). Logout via the fx-injected *auth.Manager. Live 401/403 stream + cached identity table on the dashboard's Auth tab.

Workers, cron, WebSocket

Worker — first param context.Context, rest are fx deps. ctx cancels at fx.Stop; panics recover; appears as a card on the Architecture view.

nexus.AsWorker("cache-invalidation",
    func(ctx context.Context, db *OatsDB, cache *CacheManager) error {
        // listen + dispatch...
    })

Cron — schedule, last run, last result, pause/resume, trigger-now on the dashboard.

app.Cron("refresh", "*/5 * * * *").Handler(func(ctx context.Context) error { ... })

WebSocket — typed envelope {type, data}. Multiple AsWS for the same path share one connection pool; the framework dispatches by type.

func NewChatSend(svc *ChatService, sess *nexus.WSSession, p nexus.Params[ChatPayload]) error {
    sess.EmitToRoom("chat.message", p.Args, "lobby")
    return nil
}

nexus.AsWS("/events", "chat.send",   NewChatSend, auth.Required())
nexus.AsWS("/events", "chat.typing", NewChatTyping)

*WSSession exposes Send/Emit/EmitToUser/EmitToRoom/EmitToClient plus JoinRoom/LeaveRoom. Identity at upgrade flows from ?userId= or any gin.Context user value satisfying interface{ GetID() string }.

Frontend (embedded SPA)

Mount a built React/Vue/Svelte bundle from an embedded FS:

import "embed"

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

nexus.Run(nexus.Config{...},
    nexus.ServeFrontend(webFS, "web/dist"),
    advertsModule,
)
  • / and unknown paths → index.html (SPA-aware: client-side routers work).
  • /assets/* → far-future immutable cache (Vite/Webpack/esbuild content-hash filenames).
  • Other dotted files (favicon.ico, robots.txt) → served directly.
  • REST/GraphQL/WebSocket/dashboard routes win on conflict.
  • Boot fails fast if index.html is missing.

Mount under a sub-path when the API lives at the root:

nexus.ServeFrontend(webFS, "web/dist", nexus.FrontendAt("/admin"))
// SPA at /admin/*, REST/GraphQL at the root.
Frontend-only deployment (web-svc)

Manifest pattern: a deployment with owns: [] (explicit empty) ships no backend modules — every module compiles as an HTTP stub, so the binary stays small but consumer code still type-checks against *uaa.Service etc. Pair it with IfDeployment to gate the SPA mount on the binaries that should serve it:

deployments:
  monolith:
    port: 9590                # owns: omitted → owns everything
  web-svc:
    owns: []                  # explicit empty → owns nothing
    port: 9000                # tiny SPA-only binary
  uaa-svc:
    owns: [uaa]
    port: 9001
  interview-svc:
    owns: [interview]
    port: 9002
nexus.Run(nexus.Config{...},
    nexus.IfDeployment([]string{"monolith", "web-svc"},
        nexus.ServeFrontend(distFS, "web/dist"),
    ),
    uaa.Module,
    interview.Module,
)

The same main.go builds N binaries; only the named deployments mount the SPA. The other split units stay API-only.

Deployment

Write the app as a monolith; ship as N binaries from the same source. The framework swaps cross-module *Service bodies between local impl and HTTP stub at compile time, all driven by one file.

# nexus.deploy.yaml
deployments:
  monolith:                       # owns every module by default
    port: 8080
  uaa-svc:
    prefix: /oats-uaa             # all routes mount under this on this binary
    owns: [uaa]
    port: 9591
  interview-svc:
    prefix: /oats-interview
    owns: [interview]
    port: 9592

peers:
  uaa-svc:
    timeout: 2s
    auth:
      type: bearer
      token: ${UAA_SVC_TOKEN}
Per-deployment route prefix

Each deployment can set prefix:. Every user route (REST + GraphQL + WebSocket + the SPA) mounts beneath it; framework routes (/__nexus, /health, /ready) stay unprefixed so probes and the dashboard never move.

Typical use: a single host fronts multiple services via path-based ingress — uaa-svc answers at /oats-uaa/*, interview-svc at /oats-interview/*, each with their own /graphql under the prefix.

Module declaration — unchanged across deployments
var Module = nexus.Module("uaa",
    nexus.DeployAs("uaa-svc"),
    nexus.Provide(NewService),
    nexus.AsRest("GET", "/users/:id", NewGet),
    nexus.AsRest("GET", "/users",     NewList),
    nexus.AsQuery(NewSearch),
)
Consumer — same Go in every binary
type Service struct {
    *nexus.Service
    uaa *uaa.Service          // local in monolith / uaa-svc, HTTP stub elsewhere
}

func NewSubmit(svc *Service, p nexus.Params[SubmitArgs]) (*Receipt, error) {
    u, err := svc.uaa.Get(p.Context, uaa.GetArgs{ID: p.Args.UserID})
    if err != nil { return nil, err }
    return &Receipt{...}, nil
}

No Client interface, no per-deployment branching, no env-var lookups. The framework auto-Provides peer *Service constructors via an init()-time registry.

Build / dev
nexus build --deployment monolith       # ./bin/monolith — every module local
nexus build --deployment uaa-svc        # ./bin/uaa-svc  — interview shadowed
nexus dev --split                       # all units, one terminal, cross-service traces

nexus build:

  1. Reads the manifest, scans DeployAs tags.
  2. For non-owned modules, emits a zz_shadow_gen.go HTTP-stub Service whose methods route through nexus.PeerCaller. Source files are kept untouched on disk.
  3. Emits a zz_deploy_gen.go whose init() calls nexus.SetDeploymentDefaults(...) with the manifest's port + prefix + peer table baked in.
  4. Runs go build -overlay=overlay.json so the compiler picks up the generated files without source rewrites.
Friendly errors

Framework errors are *nexus.UserError with op / hint / notes / cause:

nexus error [remote call]: GET /users/:id: peer responded but the JSON didn't fit the client's return type
  url:  http://localhost:8081/users/
  cause: json: cannot unmarshal array into Go value of type users.User
  hint: verify the peer's handler return type matches the client's expected shape

Status-specific hints fire for 401/403/404/405/408/429/5xx. Boot-time topology validation runs before fx spins up. All error fields propagate to the dashboard waterfall as span attrs.

Dashboard

Mounted at /__nexus/ when Dashboard.Enabled: true. Tabs: Architecture, Endpoints, Crons, Rate limits, Auth, Traces. Tab selection persists in ?tab=. Live traffic pulses on edges; click an op's ⚠N chip for paginated errors with IP/timestamp filters.

Gate the whole /__nexus/* surface behind your own auth chain:

nexus.Config{
    Dashboard: nexus.DashboardConfig{Enabled: true},
    Middleware: nexus.MiddlewareConfig{
        Dashboard: []middleware.Middleware{
            {Name: "auth",  Kind: middleware.KindBuiltin, Gin: bearerAuthGin},
            {Name: "admin", Kind: middleware.KindCustom,  Gin: requireAdminGin},
        },
    },
}

Selected HTTP surface:

Route Returns
GET /__nexus/ Embedded Vue UI
GET /__nexus/endpoints Services + endpoints with deps
GET /__nexus/stats Per-endpoint counters
GET /__nexus/auth Cached identities
POST /__nexus/auth/invalidate `{id?
GET /__nexus/events WebSocket: trace + request.op + auth.reject events

UI dev: cd dashboard/ui && npm install && npm run dev. npm run build updates the embedded bundle.

Performance

Per-request hot-path cost on an Apple M1 Pro:

Path ns/op allocs
metrics.Record (success) 73 0
ratelimit.Allow 134 1
callHandler (reflective) 477 5
bindGqlArgs (map → struct) 250 4

A request through AsQuery with args + metrics + one rate limit pays ≈ 1 µs of nexus-side work. Surrounding cost (Gin, graphql-go, JSON, your handler, DB) is measured by your own load test.

Monolith vs split

examples/microsplit /checkout (cross-module call to users), 32 concurrent clients, 20k requests:

Monolith Split Δ
Throughput 56,618 r/s 16,380 r/s 3.5×
p50 450 µs 1.66 ms 3.7×
p99 1.75 ms 7.25 ms 4.2×

The 3.5× gap is mostly TCP loopback — ~43 µs kernel + HTTP, ~17 µs framework. Add a 5 ms handler (real DB / external call) and the gap collapses:

Monolith (5 ms handler) Split
Throughput 5,286 r/s 5,376 r/s
p50 5.93 ms 5.51 ms

So split-vs-monolith is rarely a per-request decision — it's about scaling, on-call, and team boundaries. Ship as monolith on day one without paying a future tax.

go test ./... -bench=. -benchmem -run 'x^'

Examples

Path Shows
examples/petstore Minimal REST + WebSocket + tracing.
examples/fxapp Multi-domain app via nexus.Module (fx hidden).
examples/graphapp GraphQL via reflective AsQuery/AsMutation, typed DB wrappers, validators.
examples/wsecho Typed WebSocket via AsWS — two message types on one path.
examples/microsplit users + checkout (manifest-driven deployments, three binaries from one source) and notes (canonical AsCRUD demo).
go run ./examples/graphapp

Layout

nexus/                top-level App, Run, Module, Provide, AsWorker, ServeFrontend, options
├── auth/             extractors, identity cache, per-op bundles, dashboard routes
├── graph/            resolver builder + validators
├── registry/         services, endpoints, resources, workers, middleware metadata
├── resource/         Database/Cache/Queue + health probing
├── trace/            ring-buffer bus + per-request middleware
├── transport/{rest,gql,ws}/
├── middleware/       cross-transport bundle
├── metrics/          per-endpoint counters, error ring
├── ratelimit/        token-bucket + middleware factories
├── cron/             scheduler + dashboard control
├── cache/            Redis + in-memory hybrid
├── db/               opinionated GORM helpers
├── storage/gorm/     production Store[T] adapter for AsCRUD
├── dashboard/        /__nexus surface + embedded Vue UI
└── examples/         runnable demos

License

MIT

Documentation

Overview

Package nexus deployment annotations.

DeployAs marks a module as a candidate deployment unit — when the framework later supports independent rollout (`nexus gen clients` + `NEXUS_DEPLOYMENT` boot selector), the tag identifies which binary the module belongs to. In today's monolith mode the tag is metadata only: it surfaces on the dashboard so readers can see the planned split topology before any splitting happens.

var users = nexus.Module("users",
    nexus.DeployAs("users-svc"),
    nexus.Provide(NewUsersService),
    nexus.AsRest("GET", "/users/:id", NewGetUser),
)

Untagged modules are "always local" — they ride along with whichever deployment is active. Reserve DeployAs for modules with a real chance of being peeled out (separate codebase boundary, separate scaling needs, separate on-call rotation).

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 DefaultGraphQLPath = "/graphql"

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

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.

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 CallerAuthorization added in v0.10.0

func CallerAuthorization(ctx context.Context) string

CallerAuthorization returns the Authorization header captured for this request, or "" when none. Used by DefaultAuthPropagator and available for custom propagators that want to inspect/decorate it.

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 DeploymentFromEnv added in v0.9.0

func DeploymentFromEnv() string

DeploymentFromEnv reads NEXUS_DEPLOYMENT. The single-binary, multi-shape pattern: the same compiled binary boots as different deployment units based on the env var alone — no rebuild, no flags. Returns "" when unset, which the framework treats as monolith mode.

func main() {
    nexus.Run(nexus.Config{
        Deployment: nexus.DeploymentFromEnv(),
        // ...
    }, allModules...)
}

func GqlCall added in v0.13.0

func GqlCall[T any](ctx context.Context, callable ClientCallable, opType, opName string, args any, opts GqlOptions) (T, error)

GqlCall executes a GraphQL operation through callable. The query string is constructed from opName + args (inlined as JSON literals) + a selection set walked from T's exported struct fields. Returns the decoded data slot (or a typed error if the peer reported one).

Limitations of the v1 helper:

  • Args are inlined into the query body; no $variables pass. Works fine with graphql-go's permissive scalar parsing for strings/ints/bools/maps; complex inputs may need manual escaping. This keeps the helper schema-free at the cost of parameter type-safety from the server's side.
  • The selection set descends one struct level. Slices/pointers unwrap to their element. Scalars get no sub-selection.
  • Output type T must be a struct or pointer-to-struct (or a slice thereof) — scalars at the top level don't carry a selection set in GraphQL.

For richer cases (variables, fragments, custom scalars) call into the peer's /graphql endpoint directly with your own request body.

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 RegisterAutoClient added in v0.14.0

func RegisterAutoClient(fn any)

RegisterAutoClient stores a generated-client factory function so the framework can auto-Provide it at Run time. Generated client files (zz_*_client_gen.go) call this from their init() block; user code should not call it directly.

fn must be a function suitable for fx.Provide — typically `func(*nexus.App) SomeClient`. Passing a non-function will fail at fx.New time, not here, since the registry stays type-erased to keep init() ordering simple.

func RegisterCrossModuleDep added in v0.15.0

func RegisterCrossModuleDep(consumer, dep string)

RegisterCrossModuleDep records that a service field of type *<dep>.Service is referenced from <consumer>'s package (typically on the consumer's own Service struct). The build tool scans source statically and emits one call per detected dependency in zz_deploy_gen.go's init().

User code does not call this directly.

func RegisterModuleDeployment added in v0.14.1

func RegisterModuleDeployment(moduleName, tag string)

RegisterModuleDeployment associates a module name with a DeployAs tag. Called from a codegen'd init() block when the manifest declares this module under a split-unit deployment's owns list.

Re-registration replaces the previous mapping (last write wins), matching how SetDeploymentDefaults handles repeated calls.

func RegisterRemoteServicePlaceholder added in v0.14.8

func RegisterRemoteServicePlaceholder(name, tag string)

RegisterRemoteServicePlaceholder records a remote-module placeholder from a codegen'd init() block. Same pattern as RegisterAutoClient: the shadow generator's zz_shadow_gen.go emits one init() per non-owned module with a single call here, and the framework reconciles them with the *Registry at App construction time.

Why init() and not an fx.Invoke inside the shadow's nexus.Module: Module() in options.go runtime-filters modules whose DeployAs tag doesn't match NEXUS_DEPLOYMENT, dropping the whole option list (including any Invoke). init() runs at package load, before fx touches anything, so the registration always lands.

User code does not call this directly — codegen emits it.

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)

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.

func SetDeploymentDefaults added in v0.14.0

func SetDeploymentDefaults(d DeploymentDefaults)

SetDeploymentDefaults stores manifest-derived configuration that newApp consults when Config fields are zero. Called from a codegen'd init() block — user code should not call this directly.

Calling it twice replaces the previous defaults; the last write wins. That makes hot-restart in tests predictable.

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 WithCallerAuthorization added in v0.10.0

func WithCallerAuthorization(ctx context.Context, header string) context.Context

WithCallerAuthorization stamps the inbound Authorization header onto ctx so DefaultAuthPropagator can forward it. Called by the auth middleware on every authenticated request.

Generated client code never calls this directly — the auth surface in nexus.auth wires it for you. Exposed so custom middleware (or a non-auth.Module setup) can plug in.

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.

func WithRouteKey added in v0.16.0

func WithRouteKey(ctx context.Context, key string) context.Context

WithRouteKey decorates ctx with a routing key. When the call lands on a multi-replica RemoteCaller, the key is hashed to a stable replica index so calls with the same key land on the same replica. This is the load-bearing primitive for stateful workloads — WS hubs, per-user caches, idempotency stores — that need affinity across a split deployment.

Empty key is treated as no routing — falls back to round-robin.

Compose with the existing call site: a generated client method or hand-written caller wraps ctx before calling the RemoteCaller:

ctx = nexus.WithRouteKey(ctx, args.UserID)
return client.Call(ctx, "POST", "/messages", args, &out)

When the chosen replica is currently ejected (recent failure), the caller falls back to round-robin so the call still has a chance to land — affinity is best-effort, not a hard guarantee.

Types

type App

type App struct {
	// contains filtered or unexported fields
}

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

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

func (*App) Cache added in v0.3.0

func (a *App) Cache() *cache.Manager

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

func (a *App) DeclareVolumeProvider(p manifest.VolumeProvider)

DeclareVolumeProvider is the data-driven counterpart to UseVolume.

func (*App) Deployment added in v0.9.0

func (a *App) Deployment() string

Deployment is the deployment-unit name this binary boots as, or "" for monolith. Read by future cross-module clients to choose the in-process shortcut over HTTP.

func (*App) Engine

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

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) Peer added in v0.14.0

func (a *App) Peer(tag string) (Peer, bool)

Peer returns the topology binding for the given DeployAs tag. Generated remote clients call this at construction time to resolve the peer's URL/Timeout/Auth/MinVersion/Retries. The second return is false when the tag isn't declared in Config.Topology — codegen'd factories use that signal to fail fast with a precise error message.

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

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

func (*App) Service

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

func (*App) Topology added in v0.14.0

func (a *App) Topology() Topology

Topology returns the configured peer table. Read-only — modifying the returned map would not retroactively rewire already-constructed clients. Used by the dashboard and by health-check loops that probe every declared peer.

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) 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 so generated clients can detect peer-version skew.

type AuthPropagator added in v0.10.0

type AuthPropagator interface {
	Inject(ctx context.Context, req *http.Request) error
}

AuthPropagator decides what (if any) caller identity to attach to a cross-module request. The default forwards the inbound Authorization header from ctx so user identity threads through the call chain without any handler-side wiring. Service-to-service calls that need a service token instead swap in a custom implementation.

func DefaultAuthPropagator added in v0.10.0

func DefaultAuthPropagator() AuthPropagator

DefaultAuthPropagator forwards the inbound Authorization header unchanged. Most apps want this — the JWT (or bearer token) the edge service authenticated against follows the request to peer services for free.

type AuthPropagatorFunc added in v0.10.0

type AuthPropagatorFunc func(ctx context.Context, req *http.Request) error

AuthPropagatorFunc adapts a function to AuthPropagator. Useful for one-off custom propagators without a struct:

caller := nexus.NewRemoteCaller(url, nexus.WithAuthPropagator(
    nexus.AuthPropagatorFunc(func(ctx context.Context, req *http.Request) error {
        req.Header.Set("Authorization", "Bearer "+mintServiceToken())
        return nil
    }),
))

func (AuthPropagatorFunc) Inject added in v0.10.0

func (f AuthPropagatorFunc) Inject(ctx context.Context, req *http.Request) error

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 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 ClientCallable added in v0.13.0

type ClientCallable interface {
	Invoke(ctx context.Context, method, path string, body, out any) error
}

ClientCallable is the seam every generated cross-module client dispatches through. Both LocalInvoker (in-process via httptest) and RemoteCaller (HTTP) satisfy it, so the helpers below can build a single transport-agnostic call path. The codegen picks the concrete impl per-deployment in the constructor.

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

	// TraceCapacity is the ring-buffer size for request traces. 0 disables
	// tracing — the Traces tab will stay empty.
	TraceCapacity int

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

	// Deployment names the deployment unit this binary runs as. Empty
	// = monolith mode (every module is local — current behavior).
	// When set, the framework knows which DeployAs-tagged modules are
	// "local" vs "remote" so future codegen'd clients can pick the
	// in-process or HTTP path accordingly. Today this field is
	// metadata only — surfaced on /__nexus/config so the dashboard
	// can render the active deployment.
	//
	// Convention: pass DeploymentFromEnv() in main() so a single
	// binary can boot as different units across environments without
	// recompiling.
	Deployment 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

	// Topology declares the peer table for split deployments — one
	// entry per DeployAs tag the binary calls into. Codegen'd clients
	// look up the active peer here at construction time instead of
	// reading hard-coded env vars (USERS_SVC_URL, etc.), so peer URLs,
	// timeouts, auth, and version floors live in one declarative place.
	//
	// Empty is the monolith default — every module is local, no peer
	// lookups happen. When non-empty in split mode, the active
	// Deployment must be a key in Peers (Run fails fast otherwise).
	//
	//	nexus.Config{
	//	    Deployment: "checkout-svc",
	//	    Topology: nexus.Topology{
	//	        Peers: map[string]nexus.Peer{
	//	            "users-svc":    {URL: os.Getenv("USERS_SVC_URL"), Timeout: 2 * time.Second},
	//	            "checkout-svc": {},
	//	        },
	//	    },
	//	}
	Topology Topology
}

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.

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 DeploymentDefaults added in v0.14.0

type DeploymentDefaults struct {
	// Addr is the listen address (e.g. ":8081") baked from the
	// active deployment's manifest port. Empty when the manifest
	// omits port — Config.Addr / nexus's :8080 default takes over.
	Addr string

	// Deployment names the active unit. When non-empty, the
	// codegen'd init() set this from --deployment X so the binary
	// runs with the right tag without needing NEXUS_DEPLOYMENT.
	Deployment string

	// Topology is the peer table built from the manifest's `peers:`
	// block plus each peer deployment's port (so URLs default to
	// http://localhost:<peer-port> for local dev).
	Topology Topology

	// Listeners is the manifest-derived listener map. When non-nil
	// AND Config.Listeners is empty, newApp adopts this. Lets the
	// operator declare scope shape in nexus.deploy.yaml ("admin"
	// listener at port+1000, etc.) without main.go touching the
	// listener struct.
	Listeners map[string]Listener

	// RoutePrefix is prepended to every user-mounted route in this
	// binary — REST, GraphQL, and WebSocket. Framework routes
	// (/__nexus/*, /health, /ready) are unprefixed by design so
	// liveness probes and the dashboard mount stay at fixed paths.
	// Empty disables prefixing.
	RoutePrefix string
}

DeploymentDefaults is the manifest-derived configuration applied when Config.Addr / Config.Topology / Config.Deployment / Config.Version are not set explicitly. `nexus build --deployment X` codegens a zz_deploy_gen.go file in the main package whose init() calls SetDeploymentDefaults — so the binary boots with the right port and peer table without main.go declaring anything.

Explicit Config fields always win. Defaults fill the gaps; this is the same precedence Go's flag package uses for -flag-from-env-var patterns and matches the user's intuition about "main.go is the override, manifest is the baseline."

func Defaults added in v0.16.0

func Defaults() (DeploymentDefaults, bool)

Defaults returns the manifest-derived configuration (the same data New consults to fill in zero-valued Config fields). Exposed so main.go can read the active deployment's port — useful when the caller wants to derive listener addresses from the manifest's port without parsing nexus.deploy.yaml itself.

Returns the zero DeploymentDefaults and false when no codegen'd init() has run (typical of `go run` against a deployment-agnostic monolith). Callers should fall back to their own defaults in that case rather than treating the empty Addr as authoritative.

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

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 GqlOptions added in v0.13.0

type GqlOptions struct {
	Path string // defaults to "/graphql"
}

GqlOptions tunes a GraphQL invocation. Currently only the mount path; reserved space for future knobs (per-call headers, custom operationName, persisted-query IDs).

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 GraphQL Playground served on
	// GET <service>/<path>. Default is enabled — 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
}

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

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 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 LocalInvoker added in v0.10.0

type LocalInvoker struct {
	// contains filtered or unexported fields
}

LocalInvoker is the in-process variant of a generated cross-module client: instead of speaking HTTP to a peer service, it synthesizes an httptest.Recorder request and routes it directly through the same Gin engine that serves the real endpoints.

This costs ~10-20µs per call (request building + middleware chain) vs ~5ns for a direct function call, but buys behavior parity: auth, rate limits, metrics, and trace events all run identically to a real HTTP request. Monolith and split deployments produce the same dashboard signals — you can develop and debug the same way.

func NewLocalInvoker added in v0.10.0

func NewLocalInvoker(app *App, opts ...LocalInvokerOption) *LocalInvoker

NewLocalInvoker grabs the engine from the App. Generated client constructors call this when the target module's deployment matches the running binary's deployment (or both are blank — monolith mode).

func (*LocalInvoker) Invoke added in v0.10.0

func (li *LocalInvoker) Invoke(ctx context.Context, method, path string, args, out any) (rerr error)

Invoke runs args through the Gin engine as if it were a real HTTP request to (method, path), then decodes the response into out.

The body/query/path encoding rules match RemoteCaller exactly so a handler whose generated client targets either path produces the same wire shape — the framework guarantees parity.

Non-2xx responses become *RemoteError, same shape as the remote path; callers can type-assert and react identically.

type LocalInvokerOption added in v0.10.0

type LocalInvokerOption func(*LocalInvoker)

LocalInvokerOption tunes a LocalInvoker. Currently only auth propagation; kept as a functional option to leave room for future knobs (e.g. skip-middleware fast path) without changing the constructor signature.

func WithLocalAuthPropagator added in v0.10.0

func WithLocalAuthPropagator(p AuthPropagator) LocalInvokerOption

WithLocalAuthPropagator swaps the default auth propagator. Same reasoning as the remote variant: most apps forward the inbound Authorization header; service-token minters override.

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

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 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 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 DeployAs added in v0.9.0

func DeployAs(tag string) Option

DeployAs records the deployment tag for the enclosing nexus.Module. Multiple DeployAs calls in one Module are not allowed — the last one wins, matching the way Module() handles duplicated RoutePrefix today.

func IfDeployment added in v0.21.18

func IfDeployment(names []string, opts ...Option) Option

IfDeployment yields opts as a single Option only when the active deployment matches one of names. When it doesn't match, the returned Option is a no-op so all listed opts (and any Provide / Invoke / Module they wrap) are skipped — no constructors run, no routes register, no fx graph touched.

Active deployment, in priority:

  1. NEXUS_DEPLOYMENT env var (set per subprocess by `nexus dev --split`)
  2. DeploymentDefaults.Deployment (baked into the binary by `nexus build --deployment X` for any deployment whose manifest entry has an explicit `owns:` key — listed or empty []. Omitted owns: monolith leaves this empty.)
  3. Empty → matches "monolith" by convention so a plain `go run .` against an unannotated build still hits monolith-only opts.

The classic use is gating a frontend mount on the monolith / web-svc deployments while leaving uaa-svc / interview-svc as API-only binaries that ship no SPA bytes:

nexus.Run(nexus.Config{...},
    nexus.IfDeployment([]string{"monolith", "web-svc"},
        nexus.ServeFrontend(distFS, "web/dist"),
    ),
    uaa.Module,
    interview.Module,
)

Manifest pairing for the web-svc shape:

deployments:
  monolith:                  # owns: omitted → owns everything
    port: 8080
  web-svc:
    owns: []                 # explicit empty → owns nothing
    port: 9000               # tiny SPA-only binary
  uaa-svc:
    owns: [uaa]
    port: 9001

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 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 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 Path added in v0.21.20

func Path(path string) Option

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.

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
}

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 Peer added in v0.14.0

type Peer struct {
	// URLs is the replica list for this peer. The runtime round-
	// robins across entries and passively ejects any replica that
	// returns transport errors / 5xx for a cooldown window — single-
	// replica peers just declare a one-element slice.
	//
	//	"users-svc": {URLs: []string{
	//	    "http://users-1.cluster.local:8080",
	//	    "http://users-2.cluster.local:8080",
	//	    "http://users-3.cluster.local:8080",
	//	}},
	//
	// Required when the active deployment is not this peer's tag;
	// ignored for the active peer's own entry.
	URLs []string

	// Timeout caps each remote call. Zero falls back to the
	// RemoteCaller default (30s). Recommended: set to your
	// infrastructure-level timeout minus a small slack so client-side
	// errors fire before any LB resets the connection.
	Timeout time.Duration

	// Auth is invoked once per remote call to produce an
	// Authorization header value (e.g. "Bearer <token>"). Returning
	// an error aborts the call. Nil disables explicit auth — the
	// default forwarding propagator still threads the inbound
	// Authorization header from the request context, so most
	// edge-token flows work without setting this.
	Auth func(ctx context.Context) (string, error)

	// MinVersion is the lowest peer Version (read from the peer's
	// /__nexus/config) accepted on the first call. When non-empty
	// it replaces the local-binary version as the comparison floor
	// in the existing skew-probe path. Empty disables the floor and
	// falls back to comparing against the local binary's Version.
	// Soft-fail: a mismatch logs a single warning line and the call
	// proceeds, same as today's WithLocalVersion behavior.
	MinVersion string

	// Retries caps the number of automatic retries on transport
	// errors (connection reset, timeout, DNS failure). Only
	// idempotent verbs (GET, HEAD, PUT, DELETE, OPTIONS, TRACE)
	// retry — POST and PATCH never do, regardless of this value.
	// Zero disables retries entirely. Backoff between attempts is
	// 50ms * 2^n with full jitter.
	Retries int
}

Peer is the per-deployment binding consumed by codegen'd remote clients. Every field is optional — zero values map to the framework's default behavior for that knob.

type RemoteCaller added in v0.10.0

type RemoteCaller struct {
	// contains filtered or unexported fields
}

RemoteCaller is the HTTP variant of a generated cross-module client. One per peer service: replicas hold one base URL each, the embedded http.Client is wrapped via trace.HTTPClient so traceparent is auto-injected on every call (request stitching across services is free).

Multi-replica behavior: when len(replicas) > 1, calls round-robin across replicas and passively eject any replica that returns a transport error or 5xx for a cooldown window. Idempotent verbs retry on a different replica when their first pick errors; non- idempotent verbs (POST, PATCH) still don't retry but the chosen replica is updated for the next caller.

Generated client code never constructs this directly — see NewRemoteCaller / NewRemoteCallerWithReplicas / NewPeerCaller.

func NewPeerCaller added in v0.14.0

func NewPeerCaller(peer Peer, opts ...RemoteCallerOption) *RemoteCaller

NewPeerCaller builds a RemoteCaller from a Topology Peer entry. Generated client constructors call this when the active deployment differs from the target module's tag.

Field mapping:

  • peer.URLs → replica list (required; empty panics — codegen should resolve missing-peer cases before reaching this constructor)
  • peer.Timeout → WithRemoteTimeout
  • peer.Auth → wrapped as an AuthPropagator that sets the returned string on the Authorization header
  • peer.MinVersion→ WithMinVersion (overrides localVersion as the skew-probe floor)
  • peer.Retries → WithRetries (idempotent verbs only)

Additional opts run after the peer-derived options so callers can override on a per-client basis (e.g. WithLocalVersion to thread app.Version() in for the skew probe).

func NewRemoteCaller added in v0.10.0

func NewRemoteCaller(baseURL string, opts ...RemoteCallerOption) *RemoteCaller

NewRemoteCaller wraps a single base URL — sugar for the single- replica case. Equivalent to NewRemoteCallerWithReplicas with a one-element slice. Trailing slashes are trimmed so callers don't double-slash when handler paths begin with "/".

func NewRemoteCallerWithReplicas added in v0.16.0

func NewRemoteCallerWithReplicas(urls []string, opts ...RemoteCallerOption) *RemoteCaller

NewRemoteCallerWithReplicas wraps multiple replica base URLs with round-robin balancing and passive ejection. Calls pick the next replica in cursor order; transport errors and 5xx responses mark the replica ejected for the eject duration (30s default), so the next caller skips it.

At least one URL is required — zero URLs is a programming error, not a runtime case, and panics here so codegen surfaces the bug at boot rather than on the first cross-module call.

func (*RemoteCaller) Call added in v0.10.0

func (r *RemoteCaller) Call(ctx context.Context, method, path string, args, out any) (rerr error)

Call serializes args into the appropriate place (path, body, query), dispatches the request, and decodes the JSON response into out. Pointer-to-pointer is fine — ListUsers returns *[]*User, so callers pass &out where out is *[]*User.

Non-2xx responses become *RemoteError with the status code and the server's response body (best-effort JSON-decoded into the .Body field). 5xx errors are still considered "the call returned" — they don't trigger retries here; the caller decides.

First-call side effect: if WithLocalVersion was set, the caller fetches the peer's /__nexus/config once and logs a single warning line on version skew. The probe never fails the user's call — errors fetching config are swallowed so an unrelated transient on the peer doesn't masquerade as the caller's request failing.

func (*RemoteCaller) Invoke added in v0.13.0

func (r *RemoteCaller) Invoke(ctx context.Context, method, path string, args, out any) error

Invoke is an alias for Call exposed so RemoteCaller satisfies the same ClientCallable interface as LocalInvoker. New code should prefer Invoke for shape consistency across the in-process and HTTP paths; Call stays for backward compatibility with already-generated client files.

type RemoteCallerOption added in v0.10.0

type RemoteCallerOption func(*RemoteCaller)

RemoteCallerOption tunes a RemoteCaller. Functional-option pattern matches AppOption — keeps the constructor signature stable as new knobs land.

func WithAuthPropagator added in v0.10.0

func WithAuthPropagator(p AuthPropagator) RemoteCallerOption

WithAuthPropagator swaps the default Authorization-forwarding propagator for a service-token minter or any other custom strategy.

func WithEjectDuration added in v0.16.0

func WithEjectDuration(d time.Duration) RemoteCallerOption

WithEjectDuration overrides how long a replica stays ejected after a transport error or 5xx. Defaults to 30s. Tests use shorter values to exercise re-add behavior in seconds; production rarely needs to tune this — the default is conservative enough for typical pod restart times.

func WithLocalVersion added in v0.12.0

func WithLocalVersion(v string) RemoteCallerOption

WithLocalVersion stamps the caller's own version onto the RemoteCaller so it can detect peer-version skew on the first HTTP call. Generated client constructors thread app.Version() in here so deployments where service A is on v2 and service B on v1 surface a single warning line instead of being a silent source of "weird microservice bugs."

Empty version disables the probe (a binary without a stamped version can't meaningfully claim drift).

func WithMinVersion added in v0.14.0

func WithMinVersion(v string) RemoteCallerOption

WithMinVersion sets a comparison floor for the peer-version skew probe that's independent of the local binary's stamped version. When set, the probe compares the peer's reported Version against this value instead of localVersion. Soft-fail same as today — mismatch logs a single warning, the call proceeds.

func WithRemoteHTTPClient added in v0.10.0

func WithRemoteHTTPClient(c *http.Client) RemoteCallerOption

WithRemoteHTTPClient swaps the default http.Client. Callers that already wrap their own client in trace.HTTPClient (for custom transports, retries, mTLS) pass it through here. Note: nexus does not re-wrap — if you opt out of trace.HTTPClient you opt out of automatic traceparent injection.

func WithRemoteTimeout added in v0.14.0

func WithRemoteTimeout(d time.Duration) RemoteCallerOption

WithRemoteTimeout overrides the default 30s per-call timeout on the underlying http.Client. NewPeerCaller threads Peer.Timeout through here when non-zero. Setting to zero leaves the existing timeout in place — use a custom http.Client via WithRemoteHTTPClient if you genuinely want to remove timeouts.

func WithRetries added in v0.14.0

func WithRetries(n int) RemoteCallerOption

WithRetries caps automatic retries on transport errors. Only idempotent HTTP verbs (GET/HEAD/PUT/DELETE/OPTIONS/TRACE) retry — POST and PATCH never do. Backoff between attempts is 50ms * 2^n with full jitter. Zero disables retries entirely.

type RemoteError added in v0.10.0

type RemoteError struct {
	Status     int    // HTTP status code from the peer
	Method     string // request method
	TargetPath string // logical path before substitution (the AsRest path)
	TargetURL  string // the full URL we hit (for log debugging)
	Message    string // best-effort extracted from the body
	RawBody    []byte // raw response body when JSON decode didn't yield Message
}

RemoteError is what a non-2xx peer response unmarshals to. Generated clients return it through the function's `error` slot so callers can type-assert and react on Status without parsing strings.

func (*RemoteError) Error added in v0.10.0

func (e *RemoteError) Error() string

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.

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.deploy.yaml'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 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.
	Cache *cache.Manager
}

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 Topology added in v0.14.0

type Topology struct {
	// Peers is keyed by DeployAs tag. The active deployment's own
	// entry is permitted (URL is ignored for the active unit since
	// it's never called over HTTP from itself); a placeholder entry
	// keeps the map a complete inventory of every unit in the
	// deployment, which is convenient for dashboards and validation.
	Peers map[string]Peer
}

Topology is the peer table for cross-module HTTP calls in a split deployment. Each entry names a deployment unit (matching a DeployAs tag) and binds the transport details for reaching it.

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 auth is nexus's built-in authentication surface.
Package auth is nexus's built-in authentication surface.
Package cache provides a Redis + in-memory hybrid cache for nexus apps, ported from the oats_applicant implementation.
Package cache provides a Redis + in-memory hybrid cache for nexus apps, ported from the oats_applicant implementation.
cmd
nexus command
Command nexus is the developer CLI for the nexus framework.
Command nexus is the developer CLI for the nexus framework.
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.
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 dashboard mounts the nexus introspection surface under /__nexus.
Package dashboard mounts the nexus introspection surface under /__nexus.
Package db is nexus's driver-agnostic GORM manager.
Package db is nexus's driver-agnostic GORM manager.
examples
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.
microsplit command
Command microsplit is the runnable demo for the deployable-modules preview.
Command microsplit is the runnable demo for the deployable-modules preview.
microsplit/checkout
Package checkout demonstrates a module that *consumes* another module by importing its *Service directly.
Package checkout demonstrates a module that *consumes* another module by importing its *Service directly.
microsplit/notes
Package notes is the canonical AsCRUD demo: an entire module surface — five REST endpoints + five GraphQL ops, with paging, validation, and a per-request store — boils down to a single `nexus.AsCRUD[Note](...)` line.
Package notes is the canonical AsCRUD demo: an entire module surface — five REST endpoints + five GraphQL ops, with paging, validation, and a per-request store — boils down to a single `nexus.AsCRUD[Note](...)` line.
microsplit/users
Package users is the canonical "owns its data, exposes typed REST" module: a minimal user catalog tagged DeployAs("users-svc") so it can be peeled out into its own binary later.
Package users is the canonical "owns its data, exposes typed REST" module: a minimal user catalog tagged DeployAs("users-svc") so it can be peeled out into its own binary later.
petstore command
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 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 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.
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 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.
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