nexus

package module
v0.15.2 Latest Latest
Warning

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

Go to latest
Published: Apr 26, 2026 License: MIT Imports: 39 Imported by: 0

README

nexus

A Go framework over Gin that lets you write plain handler functions, wires them into REST + GraphQL + WebSocket transports, and ships a live Vue dashboard that renders your service topology, per-endpoint traffic, rate limits, errors, and cron jobs as they happen.

Architecture dashboard

func main() {
    nexus.Run(
        nexus.Config{Addr: ":8080", EnableDashboard: true},
        nexus.ProvideResources(NewMainDB, NewCacheManager),
        auth.Module(auth.Config{Resolve: resolveBearer}),
        advertsModule,
        nexus.AsWorker("cache-invalidation", NewCacheInvalidationWorker),
    )
}

var advertsModule = nexus.Module("adverts",
    nexus.ProvideService(NewAdvertsService),
    nexus.AsQuery(NewGetAllAdverts),
    nexus.AsMutation(NewCreateAdvert,
        auth.Required(),
        auth.Requires("ROLE_CREATE_ADVERT"),
        nexus.Use(ratelimit.NewMiddleware(store, "adverts.createAdvert",
            ratelimit.Limit{RPM: 30, Burst: 5})),
    ),
)

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

Highlights

  • Reflective controllers — write func(svc, deps..., args) (*T, error); nexus's AsRest / AsQuery / AsMutation introspect the signature and wire the transport. No graph.NewResolver[T](...).With... boilerplate.
  • Module-first architecture viewnexus.Module("name", opts...) groups endpoints as one card on the dashboard; services appear as typed dependency nodes that both handlers and service constructors can point at. nexus.ProvideService inspects the constructor's params and draws service → resource / service → service edges automatically.
  • Built-in authauth.Module ships a pluggable authentication surface (bearer / cookie / api-key extraction, cached identity resolution, per-op auth.Required / auth.Requires(perms) bundles, admin invalidation + live auth.reject trace events on the dashboard's Auth tab).
  • Background workersnexus.AsWorker wraps long-lived listeners (DB LISTEN/NOTIFY, queue consumers, sweepers) with framework-owned lifecycle, ctx cancellation, panic recovery, and a card on the architecture view that shows their dep graph.
  • One middleware API for three transportsmiddleware.Middleware bundles a gin.HandlerFunc + graph.FieldMiddleware from a single definition; nexus.Use(mw) attaches it to any kind of endpoint.
  • Live dashboard — Architecture, Endpoints, Crons, Rate limits, Auth, Traces tabs. Traffic animations pulse on real requests — inbound lanes, per-op resource edges, and service-level edges all light up in sync. Error dialog shows recent errors with IP + timestamp (scales to 1000 events via virtualized scrolling).
  • Per-op observability, free — every handler gets a request + error counter and streams a request.op event, all without any user code.
  • Layered rate limiting — global (engine-root gin middleware) + per-op (graph field middleware), hot-swappable from the dashboard at runtime.
  • Cross-transport cron jobs — schedule bare handlers; control (pause/resume/trigger-now) lives on the dashboard.
  • fx under the hood, not in your importsnexus.Run/Module/Provide/Invoke wrap fx so you get DI + lifecycle without importing go.uber.org/fx.

Install

go get github.com/paulmanoni/nexus
go install github.com/paulmanoni/nexus/cmd/nexus@latest   # optional CLI

Requires Go 1.25+.

CLI

The nexus binary is a thin convenience wrapper — everything it does is reachable through plain go commands, but having one entry-point for the common-case loop keeps muscle memory short.

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 http://localhost:8080/__nexus/
Command What it does
nexus new <dir> Scaffolds a minimal app (reflective AsRest + dashboard + a heavily-commented nexus.deploy.yaml). --module <path> overrides the go.mod path.
nexus init [dir] Adds nexus.deploy.yaml to an existing project. Scans for nexus.DeployAs(...) tags and pre-populates a deployments + peers block. --force overwrites; refuses by default.
nexus dev [dir] Runs go run <dir> (default .), probes :8080, opens the dashboard as soon as it responds. With --split boots one subprocess per deployment unit (per-unit binary built via the overlay path), streams trace events with cross-service spans, prints a per-unit ready dot. --addr host:port, --no-open, --base-port.
nexus build Builds one deployment binary using go build -overlay. --deployment <name> (required) names the unit from nexus.deploy.yaml. The framework generates per-deployment shadow source (HTTP-stub Service for non-owned modules) and a deploy-init file that bakes the manifest's port + peer table into the binary — main.go contains zero deployment code.
nexus version Prints the CLI version.

Quick start

package main

import (
    "context"

    "github.com/paulmanoni/nexus"
)

// Service wrapper — distinct Go type per logical service so fx can
// route by type (no 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, compile-time
// routing, no string lookups.
type MainDB struct{ *DB }

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

// Every dep the handler declares shows up on the dashboard:
//   - *AdvertsService  →  grounds the op under the "adverts" service
//   - *MainDB          →  draws an edge from adverts → main resource
//   - 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{
            Addr:            ":8080",
            DashboardName:   "Adverts",
            TraceCapacity:   1000,
            EnableDashboard: true,
        },
        nexus.ProvideResources(NewMainDB),
        nexus.Module("adverts",
            nexus.Provide(NewAdvertsService),
            nexus.AsQuery(NewListAdverts),
        ),
    )
}

Open http://localhost:8080/__nexus/. Fire a request → packet animation on the Architecture tab.

Core concepts

App and Config

nexus.Run(cfg, opts...) builds and runs the app. Block until SIGINT/SIGTERM, then gracefully shuts down. Config covers environment-level knobs; options are the building blocks of your graph.

nexus.Run(nexus.Config{
    Addr:            ":8080",
    DashboardName:   "Adverts",
    TraceCapacity:   1000,
    EnableDashboard: true,

    // GraphQL toggles — one switch, all services
    DisablePlayground: false,
    GraphQLDebug:      false,

    // App-wide rate limit (applies to every HTTP path)
    GlobalRateLimit: ratelimit.Limit{RPM: 600, Burst: 50},
})

Option builders:

Option Produces
nexus.Module(name, opts...) Named group of options. Stamps module name onto every endpoint for the architecture view.
nexus.Provide(fns...) Constructor(s) into the dep graph.
nexus.ProvideService(fn) Provide + introspect: detects resource / service deps from the constructor's params and records them for the Architecture view.
nexus.ProvideResources(fns...) Like Provide, but auto-registers resources via NexusResourceProvider.
nexus.Supply(vals...) Ready-made values into the dep graph.
nexus.Invoke(fn) Side-effect at start-up; receives deps via function params.
nexus.AsRest(method, path, fn, opts...) REST endpoint from a reflective handler.
nexus.AsQuery(fn, opts...) / AsMutation(fn, opts...) GraphQL op, auto-mounted by the framework.
nexus.AsWS(path, type, fn, opts...) WebSocket endpoint scoped to one envelope message type; multiple AsWS on the same path share one hub.
nexus.AsWorker(name, fn) Long-lived background task; framework manages lifecycle + records status.
nexus.Use(middleware.Middleware) Cross-transport middleware — works on REST + GraphQL.
auth.Module(auth.Config{Resolve: ...}) Built-in auth surface: extraction + cached resolution + per-op enforcement bundles.
Reflective handlers

Write handlers as plain Go functions. Signature convention:

func NewOp(svc *XService, deps..., p nexus.Params[ArgsStruct]) (*Response, error)
  • First *Service-wrapper dep grounds the op under that service. Auto-routing picks the single-service default when omitted.
  • context.Context anywhere in the deps list is special-cased (filled from p.Context).
  • Last param (if it's a struct, or nexus.Params[T]) carries user-supplied args.
  • Return must be (T, error) — T becomes the GraphQL return type, flow-through for REST.
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)
}

Tags drive the schema + validators:

  • graphql:"name,required" — field name + NonNull marker
  • validate:"required,len=3|120"graph.Required() + graph.StringLength(3, 120), introspected by the dashboard as chips
Service + typed resource wrappers

Resources (DBs, caches, queues) are typed wrappers that own their dashboard metadata. No resourcesModule, no string matching.

type MainDB struct{ *DB }

func (m *MainDB) NexusResources() []resource.Resource {
    return []resource.Resource{
        resource.NewDatabase("main", "GORM — sqlite",
            map[string]any{"engine": "sqlite", "schema": "main"},
            m.IsConnected, resource.AsDefault()),
    }
}

Any handler that takes *MainDB as a dep auto-draws the service → main edge on the Architecture graph — no Using(...) call required.

Cross-transport middleware
// Build once — bundle carries Gin + Graph realizations.
authMw := middleware.Middleware{
    Name:        "auth",
    Description: "Bearer token validation",
    Kind:        middleware.KindBuiltin,
    Gin:         authGinHandler,
    Graph:       authResolverMiddleware,
}

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

// Global — every HTTP path (REST + GraphQL + WS upgrade + dashboard)
nexus.Config{
    GlobalMiddleware: []middleware.Middleware{requestID, logger, cors},
}

Built-ins that ship:

  • ratelimit.NewMiddleware(store, key, limit) — token-bucket with per-IP option
  • metrics — auto-attached to every op, no user code
Rate limits

Layered by default:

Layer How Where
Global Config.GlobalRateLimit gin middleware on engine root
Per-op nexus.Use(ratelimit.NewMiddleware(...)) per-handler
Runtime override Rate limits tab in dashboard hot-swappable without redeploy

Store swap for multi-replica:

nexus.Config{
    RateLimitStore: ratelimit.NewRedisStore(redisClient), // not yet shipped
    // or just
    Cache: cache.NewManager(cfg, logger), // app auto-uses this for the store
}
Metrics + error dialog

Every op automatically gets:

  • Request counter (atomic, ~70 ns)
  • Error counter
  • Ring of recent error events (IP, timestamp, message) capped at 1000
  • request.op trace event emitted on every handler exit

The Architecture tab shows ⚡N (request count) and ⚠N (errors) chips per op. Click the error chip → paginated dialog with filter over IP/message + virtualized scrolling that stays snappy at thousands of events.

Auth (built-in)

auth.Module owns the plumbing — token extraction, identity caching, per-op enforcement, context propagation — and leaves resolution (token → Identity) as the single plug you wire:

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

nexus.Run(nexus.Config{...},
    auth.Module(auth.Config{
        // Required: turn a raw token into an Identity.
        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,   // user-defined payload, typed-accessible later
            }, nil
        },
        Cache: auth.CacheFor(15 * time.Minute),

        // Optional: match your existing error envelope
        OnUnauthenticated: func(c *gin.Context, err error) {
            c.AbortWithStatusJSON(401, pkg.Response[any]{Success: false, Message: "UnAuthorized"})
        },
    }),
    advertsModule,
)

Per-op gating (cross-transport):

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

Resolver access:

func NewListAdverts(db *MainDB, p nexus.Params[struct{}]) (*Response, error) {
    user, ok := auth.User[MyUser](p.Context)
    if !ok {
        // Required() would have caught this earlier, but a direct
        // check is idiomatic for handlers using auth.Optional().
    }
    return fetch(p.Context, db, user.ID)
}

Token extraction strategies ship ready-made — auth.Bearer(), auth.Cookie(name), auth.APIKey(header), auth.Chain(...) — plus the typed auth.User[T] generic accessor, auth.AnyOf/auth.AllOf permission helpers, and a *auth.Manager handle (fx-injected) for logout flows:

func NewLogoutHandler(am *auth.Manager) func(context.Context, nexus.Params[TokenArgs]) (OK, error) {
    return func(ctx context.Context, p nexus.Params[TokenArgs]) (OK, error) {
        am.Invalidate(p.Args.Token)         // single-session logout
        // or am.InvalidateByIdentity(userID) → sweeps every cached session for that user
        return OK{}, nil
    }
}

Dashboard's Auth tab renders the cached identity table, recent 401/403 rejections (live via auth.reject trace events), and per-row "invalidate" buttons — all driven off GET /__nexus/auth + POST /__nexus/auth/invalidate.

Workers

nexus.AsWorker wraps long-lived background tasks (DB LISTEN/NOTIFY loops, queue consumers, sweepers) with framework-owned lifecycle:

nexus.AsWorker("cache-invalidation",
    func(ctx context.Context, db *OatsDB, cache *CacheManager, logger *zap.Logger) error {
        // Wait for dependencies to come up
        for !db.IsConnected() {
            select {
            case <-ctx.Done(): return ctx.Err()
            case <-time.After(time.Second):
            }
        }

        listener := pq.NewListener(db.ConnectionString(), 10*time.Second, time.Minute, nil)
        defer listener.Close()
        if err := listener.Listen("cache_invalidation"); err != nil { return err }

        for {
            select {
            case <-ctx.Done():
                return nil                  // clean stop on fx.Stop
            case n := <-listener.Notify:
                handleInvalidation(ctx, cache, n)
            }
        }
    })

The framework starts the function on its own goroutine at fx.Start, cancels ctx at fx.Stop, recovers panics, and records Status / LastError on the registry. The worker appears as a dedicated card on the Architecture view with its dep graph (resources + services it took as params) drawn as outgoing edges — same visual language as services.

Signature requirements:

  • First param MUST be context.Context.
  • Remaining params are fx-injected deps (resources, services, loggers, whatever's in the graph).
  • Optional error return sets LastError on the registry; context.Canceled / nil is a clean stop.
Cron jobs
app.Cron("refresh-cache", "*/5 * * * *").
    Describe("Refresh advert cache").
    Handler(func(ctx context.Context) error {
        return refreshCache(ctx)
    })

Dashboard Crons tab: schedule, last run, last result, pause/resume, trigger-now.

WebSocket (AsWS)

AsWS(path, messageType, fn) registers a reflective WebSocket handler scoped to one inbound envelope type. Multiple AsWS calls for the same path share one connection pool — the framework dispatches by the envelope's type field.

type ChatPayload struct {
    Text string `json:"text"`
}

func NewChatSend(svc *ChatService, 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
}

var chat = nexus.Module("chat",
    nexus.Provide(NewChatService),
    nexus.AsWS("/events", "chat.send",   NewChatSend,   auth.Required()),
    nexus.AsWS("/events", "chat.typing", NewChatTyping),
)

Wire protocol — every message is wrapped in the envelope:

{ "type": "chat.send", "data": { "text": "hello" }, "timestamp": 1700000000 }

The built-in types ping, authenticate, subscribe, unsubscribe are handled by the framework hub and never reach user handlers. Custom types dispatch to the matching AsWS registration; unknown types are dropped silently.

Session API (*nexus.WSSession) mirrors the fan-out semantics of the oats_applicant hub pattern:

Call Scope
sess.Send(type, data) Unicast back to the originating connection.
sess.Emit(type, data) Broadcast to every connection on this endpoint.
sess.EmitToUser(type, data, userID...) Every connection authed as one of the listed users.
sess.EmitToRoom(type, data, room) Every connection subscribed to the room.
sess.EmitToClient(type, data, clientID...) Specific ClientIDs.
sess.JoinRoom(room) / sess.LeaveRoom(room) Server-side room membership (client can also use the built-in subscribe/unsubscribe protocol messages).
sess.ClientID() / sess.UserID() / sess.Metadata() / sess.Context() Connection-scoped accessors.
sess.SendRaw(bytes) Escape hatch for non-envelope payloads.

Identity at upgrade time: the framework picks ?userId= from the URL or an auth-middleware-set user value in gin.Context (anything satisfying interface{ GetID() string }). Handlers read it via sess.UserID(); auth.Required() and friends work as middleware on the upgrade route.

Middleware rulenexus.Use(mw), auth.Required(), etc. on the first AsWS call for a path install on the HTTP upgrade route. Middleware on later calls for the same path is ignored with a warning log (every dispatch shares one upgrade route).

Handler errors come back as an error envelope on the same connection and keep the socket open:

{ "type": "error", "data": { "type": "chat.send", "message": "too long" }, "timestamp": ... }

For the full hub API (custom upgrader, typed event envelopes, connection worker-pool sizing), the imperative builder is still available: (*Service).WebSocket(path).WithHub(hub).Mount().

Cache

cache.Manager is go-cache in-memory by default; switches to Redis when env is configured. Always present on App — nexus uses it for metrics persistence automatically.

mgr := app.Cache()
_ = mgr.Set(ctx, "k", value, 5*time.Minute)
Deployment-ready modules

Write the application as a monolith; ship as N independent services with no source changes. The framework swaps cross-module *Service struct bodies between the local impl and an HTTP-stub shadow at compile time, driven by a single declarative file:

# nexus.deploy.yaml
deployments:
  monolith:                       # owns every module by default
    port: 8080
  users-svc:
    owns: [users]
    port: 8081
  checkout-svc:
    owns: [checkout]
    port: 8080

peers:
  users-svc:
    timeout: 2s
    auth:
      type: bearer
      token: ${USERS_SVC_TOKEN}
  checkout-svc:
    timeout: 2s
Module declaration — unchanged across deployments
var Module = nexus.Module("users",
    nexus.DeployAs("users-svc"),
    nexus.Provide(NewService),
    nexus.AsRest("GET", "/users/:id", NewGet),
    nexus.AsRest("GET", "/users",     NewList),
    nexus.AsQuery(NewSearch),
)

DeployAs("users-svc") names the deployment unit; the manifest decides whether this module's source is compiled locally or replaced with an HTTP stub for a given binary.

Consumer — same Go in every binary
type Service struct {
    *nexus.Service
    users *users.Service          // local in monolith / users-svc, HTTP stub in checkout-svc
}

func NewService(app *nexus.App, u *users.Service) *Service {
    return &Service{Service: app.Service("checkout"), users: u}
}

func NewSubmit(svc *Service, p nexus.Params[SubmitArgs]) (*Receipt, error) {
    u, err := svc.users.Get(p.Context, users.GetArgs{ID: p.Args.UserID})
    if err != nil { return nil, fmt.Errorf("lookup user: %w", err) }
    return &Receipt{...}, nil
}

Cross-module calls read like normal struct-method calls. No Client interface, no per-deployment branching, no env-var lookups in user code. The framework auto-Provides *users.Service via an init()-time registry so consumers don't need a manual Provide line either.

Build per deployment
nexus build --deployment monolith       # ./bin/monolith   — every module local
nexus build --deployment users-svc      # ./bin/users-svc  — checkout shadowed
nexus build --deployment checkout-svc   # ./bin/checkout-svc — users shadowed

Each command:

  1. Reads nexus.deploy.yaml, scans modules for DeployAs tags, computes which to shadow.
  2. For every non-owned module, parses each .go file in the package, preserves exported types verbatim, and synthesizes a zz_shadow_gen.go containing an HTTP-stub Service whose plain methods route through nexus.PeerCaller. Multi-file modules (types.go + service.go + handlers.go + module.go) are handled the same way as single-file ones.
  3. Generates a zz_deploy_gen.go whose init() calls nexus.SetDeploymentDefaults(...) with the manifest's port + peer table baked in (URL, timeout, auth closure for bearer tokens, retries, min-version skew floor).
  4. Writes everything under .nexus/build/<deployment>/ (gitignored), then runs go build -overlay=overlay.json so the compiler picks up shadow + deploy-init files without touching the source tree.
Run all units locally with one command
$ nexus dev --split

  nexus dev (split mode)
  ──────────
  checkout-svc  port 8080  →  http://localhost:8080
  users-svc     port 8081  →  http://localhost:8081
  ● starting · ctrl-c to stop all

  building checkout-svc
  building users-svc
checkout-svc nexus: listening on [::]:8080
  ● checkout-svc ready · http://localhost:8080
users-svc    nexus: listening on [::]:8081
  ● users-svc    ready · http://localhost:8081

  ● all ready · 2 units · ctrl-c to stop

checkout-svc → POST /checkout                              [9c9090]
checkout-svc   ↳ remote GET /users/:id ok 5ms → http://localhost:8081  [9c9090]
users-svc    → GET /users/u1                               [9c9090]
users-svc    ← GET /users/u1 200 1ms                       [9c9090]
checkout-svc ← POST /checkout 200 7ms                      [9c9090]

Each unit is built via the overlay path (real production binary, not go run), gets a per-unit ready dot when its port responds, and the trace bus on each subprocess streams into the dev terminal — request lines plus colored cross-service spans threaded by 6-char trace ID so you can read concurrent requests at a glance. Ctrl-C kills every subprocess cleanly.

Friendly framework errors

Every framework error is a *nexus.UserError with op / hint / notes / cause fields, formatted multi-line with a hint section:

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/
  body: [{"id":"u1","Name":"Alice"},{"id":"u2","Name":"Bob"}]
  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; if the URL above looks wrong, check the args struct for empty path params

Status-specific hints fire for 401/403/404/405/408/429/5xx. 3xx redirects from the peer surface as fail-fast errors (the remote client deliberately doesn't auto-follow so wrong-endpoint cases land loud, not as a confusing decode error). Path-expansion catches empty parameters before any HTTP request. Boot-time topology validation runs before fx spins up. All error fields propagate to the dashboard waterfall as span attrs (error.op, error.hint, etc.) so the UI can render them as separate elements.

Two ways to start
Starting from Command
Empty directory nexus new myapp — full scaffold with nexus.deploy.yaml already in place
Existing nexus codebase nexus init — drops a manifest, pre-populates from discovered DeployAs tags

The manifest is heavily commented as a learning artifact: anyone reading it can see exactly how to add a new deployment, wire a peer, configure auth, etc.

Design notes
  • Path 3 architecture. The transport switch lives at compile time, not runtime. There's no if dep == "users-svc" branch in user code; the type's body is genuinely different per binary, enforced by Go's compiler. A method that exists on the local impl but not on the remote stub fails the split build immediately — no silent runtime degradation.
  • -overlay not source rewriting. Original source on disk never changes. Per-deployment shadows live in .nexus/build/<deployment>/ and feed go build -overlay=overlay.json. Same Go toolchain, same debugger story, no custom build pipeline to maintain.
  • No more nexus gen clients. The previous codegen path that produced UsersClient interfaces and usersClientLocal/Remote impls is gone. The new flow has fewer concepts (just *Service) and removes the regenerate-after-each-handler-change friction.
  • Scope vs Service Weaver. Same goal — write monolith, deploy as N — without Weaver's Implements[T] mixin or rewrite tax. Untagged modules stay exactly as they are today; the deployment story is opt-in per module via DeployAs.

Dashboard

Mounted at /__nexus/ when EnableDashboard: true. Six tabs:

Tab What it shows
Architecture Module containers + endpoints, service-dep nodes, worker cards, resource nodes, external "Clients" node, dashed system boundary. Per-op and service-level edges both pulse on live traffic (green for success, red ✕ for rejections).
Endpoints REST path and GraphQL op-name list; per-endpoint tester (curl + Playground for GraphQL), arg validators rendered as chips.
Crons Schedule table, pause/resume, trigger-now.
Rate limits Declared vs effective limit per endpoint, inline edit (RPM/burst/perIP) with save/reset.
Auth Cached identities (redacted tokens), live 401/403 stream, per-identity invalidation. Renders a "not configured" prompt when auth.Module isn't wired.
Traces WebSocket stream of request events, filterable.

Tab selection persists via ?tab= in the URL — shareable, bookmarkable, survives refresh.

Traces tab

Gating the dashboard

Opt the whole /__nexus/* surface (JSON APIs, WebSocket events, embedded UI) into your own auth / permission chain by passing middleware bundles:

nexus.Run(
    nexus.Config{
        EnableDashboard: true,
        DashboardMiddleware: []middleware.Middleware{
            {Name: "auth",  Kind: middleware.KindBuiltin, Gin: bearerAuthGin},
            {Name: "admin", Kind: middleware.KindCustom,  Gin: requireAdminGin},
        },
    },
    // ...
)

Middleware runs on the /__nexus route group before any dashboard handler, so one chain covers every dashboard request. Bundles with a nil Gin realization are ignored (the dashboard is HTTP-only). nexus.WithDashboardMiddleware(...) is the equivalent AppOption for callers using nexus.New.

HTTP surface:

Route Returns
GET /__nexus/ Embedded Vue UI
GET /__nexus/endpoints Services + endpoints (services carry ResourceDeps / ServiceDeps from ProvideService)
GET /__nexus/resources Resource snapshots (health probed live)
GET /__nexus/workers AsWorker registrations + live Status / LastError / deps
GET /__nexus/middlewares { middlewares: [...], global: [ordered names] }
GET /__nexus/stats Per-endpoint counters (RecentErrors stripped)
GET /__nexus/stats/:service/:op/errors Full error ring for one endpoint
GET /__nexus/ratelimits Store snapshot
POST /__nexus/ratelimits/:service/:op Override a limit
DELETE /__nexus/ratelimits/:service/:op Reset to declared
GET /__nexus/crons, POST /.../:name/{trigger,pause,resume} Cron control
GET /__nexus/auth { identities, cachingEnabled } — cached auth state
POST /__nexus/auth/invalidate Body `{id?
GET /__nexus/events WebSocket: trace + request.op + auth.reject events
Developing the UI
cd dashboard/ui
npm install
npm run dev       # Vite dev server
npm run build     # dist/ gets embedded into Go binaries via //go:embed

Benchmarks

Microbenchmarks of the per-request hot paths on an Apple M1 Pro:

Path ns/op allocs
metrics.Record (success, single key) 73 0
metrics.Record (parallel, 10 cores) 238 0
ratelimit.Allow (single key) 134 1
callHandler (reflective invoke) 477 5
bindGqlArgs (map → struct) 250 4
direct function call (baseline) 0.3 0

A request going through AsQuery with args, metrics, and one rate limit therefore pays on the order of 73 + 134 + 477 + 250 ≈ 1 μs of nexus-side work. The surrounding cost (Gin routing, graphql-go query parsing, JSON encoding, your handler, any DB/cache roundtrip) is measured by your own load test, not by this README.

Run the microbenchmarks:

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

For end-to-end numbers on your own workload, load-test a real endpoint:

vegeta attack -rate=10000 -duration=30s -targets=targets.txt | vegeta report
Monolith vs split — what does deployment shape cost?

Benchmark of the examples/microsplit /checkout endpoint, which exercises the full cross-module call path (checkout calls users). 32 concurrent clients, 20k requests per run, on a 10-core M1 Pro. Same source, two different binaries via nexus build --deployment.

Monolith Split Ratio
Throughput 56,618 req/s 16,380 req/s 3.5×
p50 latency 450 µs 1.66 ms 3.7×
p95 latency 1.29 ms 3.65 ms 2.8×
p99 latency 1.75 ms 7.25 ms 4.2×

The monolith routes the cross-module call in-process through LocalInvoker (an httptest.Recorder against the same gin engine — auth, rate-limit, metrics, and traces all fire identically). Split mode replaces that with a real HTTP roundtrip via PeerCaller, plus traceparent propagation and two extra JSON encode/decodes.

The 3.5× factor is mostly TCP loopback, not framework overhead. Per call, monolith ≈ 17 µs of nexus-side work; split ≈ 60 µs total, of which ~43 µs is the kernel + HTTP path and ~17 µs is the framework. The extra ~43 µs is the actual cost of going to the network.

When the handler does real work

The trivial users.Get returns from an in-memory map. With a 5ms time.Sleep standing in for a real DB/cache/external call, the deployment-mode delta vanishes:

Monolith (5ms handler) Split (5ms handler)
Throughput 5,286 req/s 5,376 req/s
p50 latency 5.93 ms 5.51 ms
p95 latency 6.97 ms 7.19 ms
p99 latency 8.55 ms 14.7 ms

Identical at p50/p95. When the handler dominates total latency, the split's framework + HTTP overhead becomes a rounding error. p99 still widens because of HTTP connection-pool contention under heavy concurrency — tunable via Peer.Timeout and the underlying http.Client.

Practical rule
Handler does Split overhead is
< 1ms (in-memory) 3-4× hit on throughput — cross-service hop dominates
1-5ms (cache, fast DB) 1.5-2× hit — noticeable but acceptable
≥ 5ms (real DB, external API) invisible — handler absorbs it

Most real handlers do ≥ 10ms of work (a single Postgres query is 3-15ms; an external HTTP call is 50-300ms). So the split-vs-monolith choice is almost never about per-request performance — it's about operational concerns (independent scaling, separate on-call, team boundaries, blast radius). Ship as monolith on day one without paying a future tax: when you split later, the per-request cost increase is a rounding error against your actual handler work.

Reproduce
cd examples/microsplit
nexus build --deployment monolith
nexus build --deployment users-svc
nexus build --deployment checkout-svc

# Monolith
PORT=9000 ./bin/monolith &
hey -n 20000 -c 32 -m POST -T application/json \
    -d '{"userId":"u1","orderId":"o7"}' \
    http://localhost:9000/checkout

# Split
NEXUS_DEPLOYMENT=users-svc    PORT=9001 ./bin/users-svc &
NEXUS_DEPLOYMENT=checkout-svc PORT=9000 \
    USERS_SVC_URL=http://localhost:9001 ./bin/checkout-svc &
hey -n 20000 -c 32 -m POST -T application/json \
    -d '{"userId":"u1","orderId":"o7"}' \
    http://localhost:9000/checkout

Examples

Path Shows
examples/petstore Minimal REST + WebSocket + tracing.
examples/fxapp Multi-domain app wired via nexus.Module (fx hidden).
examples/graphapp GraphQL via reflective AsQuery/AsMutation, typed DB wrappers, rate limits, validators, metrics.
examples/wstest WebSocket echo playground (imperative (*Service).WebSocket(...) path).
examples/wsecho Typed WebSocket via AsWS — two message types on one path, envelope protocol, session fan-out.
examples/microsplit Two-module demo (users + checkout) showing manifest-driven deployments. users is split across types.go + service.go + handlers.go + module.go to exercise the multi-file shadow path. nexus build --deployment X produces three different binaries from the same source.

Run any example:

go run ./examples/graphapp

Package layout

nexus/                top-level App, Run, Module, Provide, ProvideService, AsWorker, Use, Cron, options
├── auth/             built-in authentication surface (extractors, identity cache, per-op bundles, dashboard routes)
├── graph/            absorbed go-graph resolver builder + validators
├── registry/         services, endpoints, resources, workers, middleware metadata
├── resource/         Database/Cache/Queue abstractions + health probing
├── trace/            ring-buffer bus + per-request middleware + op events
├── transport/
│   ├── rest/         REST builder
│   ├── gql/          GraphQL HTTP adapter (Playground, auth hook)
│   └── ws/           WebSocket builder + Hub
├── middleware/       Info + cross-transport Middleware bundle
├── metrics/          per-endpoint counters, error ring, cache-backed store
├── ratelimit/        token-bucket store, Gin + Graph middleware factories
├── cron/             scheduler, dashboard HTTP, event emission
├── cache/            Redis + in-memory hybrid (nexus uses it as a default)
├── multi/            N named instances behind .Using(name) (legacy pattern)
├── db/               opinionated GORM helpers (Manager.DB(ctx), ConnectionString, GetCtx)
├── dashboard/        /__nexus HTTP surface + embedded Vue UI
└── examples/         runnable demos

Testing

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

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

This section is empty.

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

Types

type App

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

func New

func New(opts ...AppOption) *App

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

type AppOption func(*App)

AppOption is the functional-option type for nexus.New. Named AppOption (not Option) so nexus.Option can be reserved for the top-level fx-wrapping builder type used by nexus.Provide / Module / Invoke / Run.

func WithCache added in v0.3.0

func WithCache(m *cache.Manager) AppOption

WithCache installs a user-provided cache.Manager instead of the default one nexus creates. Pass the same Manager users' app code receives from fx (e.g. via cache.Module) so every cache consumer — user code, metrics, rate-limit overrides — hits one store.

func WithDashboard

func WithDashboard() AppOption

WithDashboard mounts /__nexus/endpoints (always) and /__nexus/events (if tracing is on).

func WithDashboardMiddleware added in v0.3.1

func WithDashboardMiddleware(bundles ...middleware.Middleware) AppOption

WithDashboardMiddleware gates the /__nexus surface behind one or more middleware bundles. Each bundle's Gin realization runs in registration order before any dashboard handler. Typical use:

nexus.WithDashboardMiddleware(
    middleware.Middleware{Name: "auth",  Gin: bearerAuth},
    middleware.Middleware{Name: "admin", Gin: requireAdminRole},
)

Bundles without a Gin realization are skipped silently — the dashboard is an HTTP surface, so graph-only bundles don't apply.

func WithDashboardName

func WithDashboardName(name string) AppOption

WithDashboardName sets the brand shown in the dashboard header and the browser tab title. Defaults to "Nexus". The name is served over /__nexus/config so the client picks it up without a rebuild.

func WithDeployment added in v0.9.0

func WithDeployment(name string) AppOption

WithDeployment names the deployment unit this binary runs as. Empty = monolith. Mirrors Config.Deployment for callers using the lower-level nexus.New(...) entry point.

func WithEngine

func WithEngine(e *gin.Engine) AppOption

WithEngine supplies a pre-configured Gin engine. Without it, nexus builds a bare engine with just Recovery so the caller can bring their own logger.

func WithGraphQLPath added in v0.7.7

func WithGraphQLPath(path string) AppOption

WithGraphQLPath overrides the default GraphQL mount path used by services that don't call (*Service).AtGraphQL themselves. Empty falls back to DefaultGraphQLPath ("/graphql").

func WithMetricsStore added in v0.3.0

func WithMetricsStore(s metrics.Store) AppOption

WithMetricsStore swaps the default cache-backed metrics store. Useful when you want a Prometheus- or StatsD-backed implementation; the built-in dashboard /__nexus/stats endpoint reads from whichever Store is installed.

func WithRateLimitStore added in v0.3.0

func WithRateLimitStore(s ratelimit.Store) AppOption

WithRateLimitStore swaps the default in-memory rate-limit store for a custom implementation — typically ratelimit.NewRedisStore(...) in a multi-replica deploy. Pass this to nexus.New / via nexus.Config when you want limit state (counters, overrides) to survive restarts or be shared across processes.

func WithTopology added in v0.14.0

func WithTopology(t Topology) AppOption

WithTopology installs the peer table consulted by codegen'd remote clients. Mirrors Config.Topology for callers using the lower-level nexus.New(...) entry point. Each entry binds a DeployAs tag to a Peer with URL / Timeout / Auth / MinVersion / Retries.

func WithTracing

func WithTracing(capacity int) AppOption

WithTracing enables per-request trace events, buffered in a ring of the given capacity. Required for the dashboard's event stream to show anything.

func WithVersion added in v0.9.0

func WithVersion(v string) AppOption

WithVersion stamps the binary's version onto /__nexus/config. Used by generated clients to detect peer-version skew across services in a split deployment.

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 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 {
	// Addr is the HTTP listen address (default ":8080").
	Addr string

	// DashboardName is the brand shown in the dashboard header and tab title
	// (default "Nexus"). Served over /__nexus/config so you can change it
	// per-environment without rebuilding the UI.
	DashboardName string

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

	// EnableDashboard mounts /__nexus/* if true.
	EnableDashboard bool

	// GraphQLPath overrides the default mount path for auto-generated
	// GraphQL services. Empty falls back to "/graphql". Per-service
	// paths via (*Service).AtGraphQL(p) still win over this default;
	// use this Config field to change where the auto-mount fallback
	// service (the one created when no *Service dep is present on a
	// handler) and any other service that doesn't call AtGraphQL
	// will mount their schema.
	//
	//    nexus.Config{GraphQLPath: "/api/graphql"}
	GraphQLPath 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

	// GraphQLDebug skips query validation + response sanitization in
	// go-graph. Dev-only. Default false.
	GraphQLDebug bool

	// GraphQLPretty pretty-prints JSON responses. Convenient while
	// exploring; ship off in prod.
	GraphQLPretty bool

	// GlobalRateLimit applies across every endpoint — the whole app
	// consumes from one 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.
	//
	// Set PerIP to scope the global bucket per caller IP.
	GlobalRateLimit ratelimit.Limit

	// GlobalMiddleware 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, global rate limit, 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.
	GlobalMiddleware []middleware.Middleware

	// RateLimitStore 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 at boot (or cache-backed when
	// Cache is set — see below).
	RateLimitStore ratelimit.Store

	// MetricsStore replaces the default metrics store. Parallel to
	// RateLimitStore — explicit wins over Cache-driven defaults.
	MetricsStore metrics.Store

	// DashboardMiddleware 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.
	//
	//	nexus.Config{
	//	    EnableDashboard: true,
	//	    DashboardMiddleware: []middleware.Middleware{
	//	        {Name: "auth",  Gin: bearerAuth},
	//	        {Name: "admin", Gin: requireAdminRole},
	//	    },
	//	}
	//
	// Bundles whose Gin field is nil are ignored for the dashboard (no
	// graph-only protection makes sense here — the dashboard itself
	// isn't GraphQL).
	DashboardMiddleware []middleware.Middleware

	// Cache is an optional nexus cache.Manager. When set, nexus uses it
	// as the default backing for metrics + rate-limit stores so counters
	// and overrides benefit from the app's cache tier (Redis when
	// configured via env, go-cache otherwise) without extra wiring.
	//
	// Explicit RateLimitStore / MetricsStore settings still win — this
	// is just the default when those are nil.
	Cache *cache.Manager

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

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

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

func Provide(fns ...any) Option

Provide registers one or more constructor functions with the dep graph. Return types are entered into the graph; parameter types are resolved from it. Same semantics as fx.Provide.

nexus.Provide(NewDBManager, NewCacheManager)

func ProvideResources added in v0.3.0

func ProvideResources(fns ...any) Option

ProvideResources is like Provide but also auto-registers each constructed instance's resources at boot. For any fn whose returned value implements NexusResourceProvider, every resource.Resource it reports is passed to app.Register; if it also satisfies UseReporter (e.g. *multi.Registry), app.OnResourceUse is wired automatically so resolver→resource edges appear on first UsingCtx call.

This replaces the old pattern of a "resources" module full of resource.NewDatabase / NewCache calls — managers now describe their resources themselves via NexusResources() []resource.Resource, and a single ProvideResources does all the wiring.

nexus.ProvideResources(ProvideDBs, NewCacheManager)

Types that don't implement either interface fall through to a plain Provide — so it's safe to pass mixed providers.

func ProvideService added in v0.6.0

func ProvideService(fn any) Option

ProvideService is like Provide but inspects the constructor's parameters for resources (NexusResourceProvider) and other services (service-wrapper types — pointer to a struct that anonymously embeds *nexus.Service) and records them onto the service's registry entry. The dashboard uses those records to draw architecture edges at the SERVICE layer:

func NewAdvertsService(app *nexus.App, users *UsersService, db *DBManager) *AdvertsService {
    return &AdvertsService{Service: app.Service("adverts")}
}

nexus.ProvideService(NewAdvertsService)
// → registry sees: adverts.ServiceDeps = ["users"]
//                  adverts.ResourceDeps = [<whatever db.NexusResources() returns>]

The constructed service still flows into fx's dep graph the same way fx.Provide would wire it, so downstream consumers (other services, resolvers) get it injected normally. The ONLY side effect is the registry metadata that feeds the architecture view — nothing observable happens at runtime.

Only the first return value is inspected; trailing (T, error) returns are supported. Constructors that don't return a service wrapper are still Provide'd (same as plain Provide), but no service deps are recorded — the option gracefully no-ops in that case.

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 RemoteService added in v0.14.6

func RemoteService(name, tag string) Option

RemoteService is the Option-flavored variant kept for back-compat with builds that already emitted nexus.RemoteService(...) inside their shadow Module declaration. It runs as an fx.Invoke, so it only fires when the shadow's enclosing Module isn't filtered out by NEXUS_DEPLOYMENT — which means it MISSES exactly the case nexus dev --split needs (each subprocess has the env var set, and every shadow module's tag mismatches the active deployment by definition). Prefer the init()-driven RegisterRemoteServicePlaceholder path; this stays here for older codegen output that may still be in build caches.

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{Addr: ":8080"})   // rare — Run takes Config directly
nexus.Supply(myAlreadyBuiltClient)          // typical

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 {
	// URL is the base URL for HTTP calls when this peer is remote.
	// Required when the active deployment is not this peer's tag;
	// ignored for the active peer's own entry.
	URL 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: BaseURL points at the peer's HTTP root, the embedded http.Client is wrapped via trace.HTTPClient so traceparent is auto-injected on every call (request stitching across services is free).

Generated client code never constructs this directly — see NewRemoteCaller / NewRemoteCallerFromEnv / 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, replacing the older NewRemoteCallerFromEnv path that read a hard-coded env var.

Field mapping:

  • peer.URL → base URL (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 base URL with default settings: 30s timeout, trace.HTTPClient for traceparent injection, default auth propagator (Authorization header forwarding).

Trailing slashes on baseURL are trimmed so callers don't double-slash when their handler paths begin with "/".

func NewRemoteCallerFromEnv added in v0.10.0

func NewRemoteCallerFromEnv(envVar string, opts ...RemoteCallerOption) *RemoteCaller

NewRemoteCallerFromEnv reads the named env var for the base URL. Generated clients use this so a deployment can wire peer URLs through the standard Kubernetes-style envFrom pattern. Panics at boot if the env var is unset — fail fast beats a runtime nil-deref on the first cross-service 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 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 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 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 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/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.
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.
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