Write a plain Go function. Get REST + GraphQL + WebSocket from it — plus a live dashboard.
Install · 60-second start · First handler · Dashboard · Going further
nexus is a Go framework. You write one plain function; nexus exposes it over
REST, GraphQL, and WebSocket from the same signature, wires up dependencies for
you, and ships a live dashboard at /__nexus/ that draws your whole app and shows
traffic in real time. No code generation, no schema files, and no Node.js for the
frontend. The HTTP router is pluggable behind a small seam — the default backend is the
standard library (net/http, zero third-party router deps); chi
and Gin are opt-in via nexus.WithRouter(...) (the Gin
adapter is a separate module, so gin never enters a default build's dependency graph).
go install github.com/paulmanoni/nexus/cmd/nexus@latestNeeds Go 1.26+. That's it — pure Go, no C toolchain, no npm.
nexus new my-app # scaffold a runnable app (answer a few prompts, or add --yes)
cd my-app
nexus dev # run it with live reload + dashboardOpen http://localhost:8080/__nexus to see the dashboard. Your API is live too — try the GraphQL IDE at http://localhost:8080/graphql.
Create main.go:
package main
import (
"context"
"github.com/paulmanoni/nexus"
)
// 1. A service — just a struct with a constructor.
type Greeter struct{}
func NewGreeter() *Greeter { return &Greeter{} }
// 2. A handler — a normal method. p.Args holds your typed input.
type HelloArgs struct{ Name string }
func (g *Greeter) SayHello(_ context.Context, p nexus.Params[HelloArgs]) (string, error) {
return "Hello, " + p.Args.Name + "!", nil
}
// 3. Wire it up.
func main() {
nexus.Run(
nexus.Config{
Server: nexus.ServerConfig{Addr: ":8080"},
Dashboard: nexus.DashboardConfig{Enabled: true, Name: "Demo"},
Introspection: true, // open /__nexus (nexus dev sets this for you)
},
nexus.Module("hello",
nexus.Provide(NewGreeter),
nexus.AsQuery((*Greeter).SayHello), // GraphQL
nexus.AsRest("POST", "/hello", (*Greeter).SayHello), // REST
),
)
}Run it with go run . (or nexus dev), then call it any way you like:
# GraphQL
curl localhost:8080/graphql -d '{"query":"{ sayHello(name:\"world\") }"}'
# REST
curl localhost:8080/hello -d '{"name":"world"}'Same function, three transports. Add nexus.AsMutation(...) for a GraphQL mutation or
nexus.AsWS(...) for a WebSocket event — all read the same signature.
Rather than maintain a nexus.Module(...) list, you can annotate handlers with //@
doc comments and let nexus generate the wiring. Same functions — registered from the
comment above them:
package users
import "github.com/paulmanoni/nexus"
//@provide
func NewService(app *nexus.App) *Service { return &Service{app.Service("users")} }
type GetArgs struct {
ID string `uri:"id"` // REST path params bind via the `uri` tag
}
//@rest GET /users/:id
func NewGetUser(s *Service, p nexus.Params[GetArgs]) (*User, error) { ... }
//@query
//@auth Requires("ADMIN")
func NewSearchUsers(s *Service, p nexus.Params[SearchArgs]) ([]User, error) { ... }Your main keeps no handler list at all:
func main() { nexus.Boot() } // or nexus.Run(cfg, …) — no nexus.Module(...) neededThen iterate or build:
nexus dev # injects the wiring on the fly — nothing written to disk
nexus generate handlers ./... # OR: commit nexus_handlers_gen.go for plain go build/installnexus generate handlers scans your packages, writes the nexus.AsRest/AsQuery/Provide
calls into a generated file per package, and wires every annotated package into the build
— so main stays nexus.Boot(). Each package becomes a dashboard module automatically.
Annotations: //@provide, //@rest M P, //@query, //@mutation, //@subscription,
//@ws P T, //@worker N, plus //@auth Required|Requires("X") and //@use <mw>. It's
purely additive — coexists with explicit nexus.Module(...), opt out per handler any time.
Extensions ship their own too, e.g. //@inertia.Page "GET" "/dashboard" "Dashboard".
Visit /__nexus/ (enabled above) and you get a live map of your app, built automatically
from what you registered — no setup:
- See everything — every module, endpoint, resource, worker, and cron, with the dependency edges between them. Real traffic pulses on the wires.
- Click any node — the Inspector shows its endpoints, auth rules, recent traces, crons, and rate limits, with a built-in tester to fire requests.
- It's live — updates stream over a WebSocket, so it's always current and costs your API nothing per request.
The dashboard is off by default in production (every
/__nexus/*path 404s) — turn it on withDashboard.Enabled+Introspection: true.nexus devturns it on for you.
Add cross-transport middleware right where you register a handler — it works the same on REST, GraphQL, and WS:
nexus.AsMutation(NewCreateOrder,
auth.Required(), // 401 if not logged in
auth.Requires("ROLE_ADMIN"), // 403 if missing the role
ratelimit.PerUser(100, time.Minute),
)Generate a full CRUD API (REST + GraphQL) from a struct:
type Todo struct {
ID uuid.UUID `nexus:"key"`
Title string
Done bool
}
nexus.Module("todos", nexus.ProvideCRUD[Todo]("todos"))
// → GET/POST /todos, GET/PUT/DELETE /todos/:id, plus GraphQL queries + mutationsAdd a frontend with no Node.js required — nexus ships an embedded "Vite for Go":
nexus new my-app --frontend vue # or react
nexus dev # SPA with hot-reload on :5173, API on :8080Flip one switch and nexus generates and serves a typed JS/TS client for all three transports — no npm package, no codegen step to run:
[runtime]
sdk = trueThen, in your frontend:
import { NexusClient } from 'nexus-client'
const nx = new NexusClient()
await nx.rest('GET', '/pets/:id', { id }) // REST
await nx.query('searchPets', { q: 'cat' }) // GraphQL
await nx.auth.login({ username, password }) // auth flow
nx.ws('/events').on('chat.message', render) // WebSocketVue composables (useQuery, useMutation, useAuth, …) and React hooks ship alongside.
sdk = true activates only under nexus dev or when introspection = true, so a
locked-down production binary never exposes the API surface from this flag alone.
Secure by default. Tokens live in memory (cleared on reload) so an XSS can't lift a
long-lived credential — opt into localStorage explicitly when you want persistence.
Cookie-based auth gets automatic CSRF double-submit, and the SDK routes
(/__nexus/client/*) sit behind the same introspection gate as the dashboard.
Tune the auth wiring from auth.Config — where the login token lives and the CSRF pair
(values shown are the defaults):
auth.Module(auth.Config{
// ... schemes ...
LoginTokenField: "data.token", // dotted path to the token in a login response
CSRFCookie: "csrftoken", // Django/Laravel convention
CSRFHeader: "X-CSRFToken",
})For production, vendor the SDK at build time instead of serving it at runtime:
nexus client --out ./web/sdk.
Three things every real app needs — each a config switch or one typed Bind, no
third-party SDKs pulled into your build.
Security headers + CSRF. The safe response headers (X-Frame-Options,
X-Content-Type-Options, Referrer-Policy) are on by default for every app. CSRF is
one line — off by default because a token-authenticated API isn't CSRF-vulnerable; turn it
on when you serve cookie/session HTML forms (a template engine, or Inertia):
[runtime.middleware.security]
csp = "default-src 'self'" # opt-in Content-Security-Policy
hsts_max_age = 31536000 # opt-in HSTS (once you serve https)
csrf = true # opt-in double-submit CSRFFile storage — local ↔ S3, no code change. Talk to one Disk interface; the backend
is chosen by config. The S3 driver speaks to any S3-compatible store (AWS, MinIO, R2,
Spaces) directly over HTTPS with built-in SigV4 signing — no AWS SDK is linked.
type Uploads struct{ *storage.Manager }
nexus.Run(cfg, storage.Bind[Uploads]("uploads", func() storage.Config {
return storage.Config{Driver: "local", Root: "./var/uploads"} // or Driver:"s3", Bucket, Region, …
}))
// in a handler (Uploads embeds the disk):
u.Put(ctx, "avatars/1.png", r, storage.WithContentType("image/png"))
url, _ := u.SignedURL(ctx, "avatars/1.png", 15*time.Minute) // presigned GETPassword auth — Django-style, fully swappable. Pluggable password hashers (bcrypt default; argon2id / pbkdf2 also verify, with transparent rehash-on-login), password validators, and credential-login backends tried in order:
store := auth.NewMemoryUserStore() // or your own GORM / API-backed UserStore
store.CreateUser("alice", "s3cret-pw", "ADMIN")
id, err := auth.Authenticate(ctx,
auth.Password{Username: "alice", Password: "s3cret-pw"}, auth.NewModelBackend(store))
// wrong password OR unknown user → auth.ErrInvalidCredentials (no user enumeration)
auth.ValidatePassword(ctx, pw, id, auth.DefaultValidators()...) // MinLength, NotCommon, …When you're ready for more, each area has its own focused guide:
| Topic | Where |
|---|---|
| Every feature, inline | nexus docs (e.g. nexus docs handlers, nexus docs dashboard) |
| Auth: passwords, login backends, OAuth2 | nexus docs auth, extension/auth, extension/oauth2 |
| Security headers + CSRF | nexus docs security |
| File & object storage (local + S3) | nexus docs storage |
| Production: ports, scopes, TLS, mTLS | hide the dashboard from the internet with named listeners + the introspection gate |
| Typed RPC between apps (peer mesh) | extension/peer/README.md |
Config server + nexus.toml |
extension/config/README.md |
| Runnable sample apps | the examples/ folder (petstore, petstore-spa, pubsub, wsecho, graphapp, bigtopo) |
A couple of handy extras:
nexus.HideFromDashboard()— drop an internal endpoint from the dashboard while it keeps serving.
MIT.
