Register
Queries, mutations, and subscriptions are ordinary typed Go handlers.
github.com/Protocol-Lattice/neo
Ship procedure-based APIs with queries, mutations, subscriptions, middleware, generated clients, and one small Go-first mental model.
go get github.com/Protocol-Lattice/neo
router := neo.NewRouter()
router.Register("user.get", neo.Query(
func(ctx context.Context, in GetUserInput) (User, error) {
return db.GetUser(ctx, in.ID)
},
))
router.ListenAndServe(neo.ServerOptions{
Addr: ":8080",
Prefix: "/neo/",
})
router.Register("user.create", neo.Mutation(
func(ctx context.Context, in CreateUserInput) (User, error) {
user, err := db.CreateUser(ctx, in.Name)
if err != nil {
return User{}, err
}
router.Events().Publish("user.changes", user)
return user, nil
},
))
router.RegisterSubscription("user.changes", neo.Subscription(
func(ctx context.Context, in NoInput) (<-chan UserEvent, error) {
raw := router.Events().Subscribe(ctx, "user.changes")
return mapEvents[UserEvent](ctx, raw), nil
},
))
// NDJSON and WebSocket use the same envelope.
GET /neo/user.get?input={"id":1}
{"result":{"id":1}}
client.User.GetByID.Query(ctx, input)
Procedure-first backend design
Neo keeps product operations as named typed procedures. Register once, compose with Go functions, then call the same surface from raw clients, generated Go clients, or generated TypeScript clients.
Queries, mutations, and subscriptions are ordinary typed Go handlers.
Use middleware, nested routers, and merge to grow by module.
Mount into your mux or run Neo with hardened server options.
Use metadata to emit typed Go clients, TypeScript clients, docs, and schema.
Small HTTP surface, typed application core
Queries can use GET or POST, mutations use POST, and subscriptions
stream with NDJSON or WebSocket. Go clients can opt into
application/x-neo-bin without changing procedure code.
Validate() error before the handler runs.
Gateway when services split
Mount local routers for a modular monolith, or proxy remote services behind one gateway. Service prefixes stay public while each upstream keeps its local procedure keys.
users.getByID, orders.create, and auth.me.
/neo public API
Prefixes metadata, forwards headers, and strips service names before proxying upstream.
getByID
create
login / me
orders.create enters the gateway as one public procedure.
create.
Generated clients without another schema language
neo-gen scans local registrations or a running
/_meta endpoint. It can write Go clients, TypeScript
clients, Markdown docs, and schema-style metadata exports.
# typed Go client
go run ./cmd/neo-gen \
-dir ./examples \
-out ./examples/neo.gen.go
# from a running server
go run ./cmd/neo-gen \
-metadata-url http://localhost:8080/neo/_meta \
-package main \
-out ./neo.gen.go
# TypeScript runtime and client
go run ./cmd/neo-gen -target ts-runtime
go run ./cmd/neo-gen -target ts
Transport choices stay explicit
Neo keeps the default path inspectable and standard-library friendly. When the app grows, the event broker, HTTP client, headers, CORS, and gateway metadata can be configured without changing procedure code.
Readable requests and responses for browsers, curl, and debugging.
Optional compact envelope for unary Go client queries and mutations.
Simple HTTP subscription streams with one envelope per event.
Browser-friendly subscription transport using the same response envelope.
Quick start
Neo can own the server or sit next to existing handlers in your
http.ServeMux.
package main
import (
"context"
"log"
"github.com/Protocol-Lattice/neo"
)
type HelloInput struct {
Name string `json:"name"`
}
type HelloOutput struct {
Message string `json:"message"`
}
func main() {
router := neo.NewRouter()
router.Register("hello", neo.Query(
func(ctx context.Context, in HelloInput) (HelloOutput, error) {
return HelloOutput{Message: "Hello, " + in.Name}, nil
},
))
log.Fatal(router.ListenAndServe(neo.ServerOptions{
Addr: ":8080",
Prefix: "/neo/",
}))
}
Install Neo