github.com/Protocol-Lattice/neo

Neo Type-friendly RPC for Go backends.

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
internal/api/router.go
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.
Request GET /neo/user.get?input={"id":1}
Response {"result":{"id":1}}
Client client.User.GetByID.Query(ctx, input)
query mutation subscription middleware metadata neo-gen gateway binary codec

Procedure-first backend design

The API stays shaped like the thing you are building.

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.

01

Register

Queries, mutations, and subscriptions are ordinary typed Go handlers.

02

Compose

Use middleware, nested routers, and merge to grow by module.

03

Serve

Mount into your mux or run Neo with hardened server options.

04

Generate

Use metadata to emit typed Go clients, TypeScript clients, docs, and schema.

Small HTTP surface, typed application core

JSON for inspection. Binary for Go-to-Go unary calls.

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.

Typed handlers Input and output types stay visible at registration time.
Middleware Wrap procedure execution for auth, logging, rate limits, tracing, or recovery.
Safe errors Machine-readable codes reach clients while plain internal errors stay server-side.
Validation Inputs can implement Validate() error before the handler runs.

Gateway when services split

One public API can route to many Neo services.

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.

Clients
Generated Go and TypeScript clients Call users.getByID, orders.create, and auth.me.
Gateway
/neo public API Prefixes metadata, forwards headers, and strips service names before proxying upstream.
Services
users getByID
orders create
auth login / me
client orders.create enters the gateway as one public procedure.
gateway Routes to the orders service as local create.
orders Calls users or auth with the same typed Neo client surface.

Generated clients without another schema language

Metadata turns registered procedures into typed clients.

neo-gen scans local registrations or a running /_meta endpoint. It can write Go clients, TypeScript clients, Markdown docs, and schema-style metadata exports.

Go client Namespaced query, mutation, NDJSON subscription, and WebSocket helpers.
TypeScript Interfaces from Go structs and JSON tags, plus fetch and WebSocket helpers.
Remote metadata Generate clients from a live server or gateway metadata endpoint.
neo-gen local or remote metadata
# 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

Start boring, then swap the parts that need power.

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.

JSON HTTP

Readable requests and responses for browsers, curl, and debugging.

Neo binary

Optional compact envelope for unary Go client queries and mutations.

NDJSON streams

Simple HTTP subscription streams with one envelope per event.

WebSocket

Browser-friendly subscription transport using the same response envelope.

Quick start

First endpoint, no framework ceremony.

Neo can own the server or sit next to existing handlers in your http.ServeMux.

main.go query procedure
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

Procedure-first APIs without leaving idiomatic Go.