Arch Reactor

Frameworks

How to mount archview, and how routes are detected per framework.

archview serves its UI on its own path (/graph). How you mount it depends on your framework; how it detects your routes is handled by per-framework extractors that run automatically (each fires only on a package that uses its framework).

Mounting archview

av.Handler() is a standard net/http.Handler, so it drops into any router. archview also excludes its own routes from the graph, so /graph never shows up as an endpoint.

mux := http.NewServeMux()
av.Mount(mux)                 // serves /graph and /graph/data
r.GET("/graph", gin.WrapH(av.Handler()))
r.GET("/graph/data", gin.WrapH(av.Handler()))
e.GET("/graph", echo.WrapHandler(av.Handler()))
e.GET("/graph/data", echo.WrapHandler(av.Handler()))
import "github.com/gofiber/fiber/v2/middleware/adaptor"

app.Get("/graph", adaptor.HTTPHandler(av.Handler()))
app.Get("/graph/data", adaptor.HTTPHandler(av.Handler()))
// chi, gorilla/mux, or any net/http-compatible router
r.Handle("/graph", av.Handler())
r.Handle("/graph/data", av.Handler())
// works with any framework — run archview on its own port
go func() {
	m := http.NewServeMux()
	av.Mount(m)
	http.ListenAndServe(":9000", m)
}()

Any HTTP router (gin, echo, fiber, chi, httprouter, …)

Most Go routers share one shape — router.GET/Get/POST/...("/path", handler). The generic router extractor matches it by the verb method name, a string path, and a function-typed handler argument — not by the router's concrete type — so most frameworks work with no configuration. Group("/api") prefixes are joined onto routes.

api := app.Group("/api")     // gin r.Group, echo e.Group, fiber app.Group
api.Get("/users", h.List)    // -> endpoint /api/users

net/http

Detects mux.HandleFunc(pattern, handler) on *http.ServeMux and the package-level http.HandleFunc. The Go 1.22+ method-prefixed pattern is parsed:

mux.HandleFunc("GET /catalog/items", h.ListItems)   // method GET, path /catalog/items

gorilla/mux

gorilla uses r.HandleFunc("/path", handler) with the method on a chained .Methods("GET") rather than r.GET(...), so it has its own extractor. Without .Methods the route is ANY.

r := mux.NewRouter()
r.HandleFunc("/users", h.List).Methods("GET")   // -> GET /users
r.HandleFunc("/ws", h.Stream)                    // -> WS /ws (see below)

gRPC

Detected structurally from the generated registration call — no import needed, so it works with real google.golang.org/grpc as-is. Each RPC method on the service interface becomes an endpoint bound to the implementing method:

pb.RegisterOrderServiceServer(grpcServer, &OrderServer{...})
// endpoints: /OrderService/CreateOrder, /OrderService/GetOrder, ...

GraphQL

Detects gqlgen-style resolvers: an interface named Query/Mutation/SubscriptionResolver implemented by a project type. Each field becomes an endpoint bound to the resolver method.

type QueryResolver interface {
    Order(ctx context.Context, id string) (*Order, error)   // endpoint /Query/order
}

Structural detection — no import required. Only root resolvers for now; nested field resolvers are on the roadmap.

ConnectRPC

Detects the generated New<Svc>Handler(impl) constructor (returns (string, http.Handler)) from connectrpc.com/connect. Each RPC method on the service handler interface becomes an endpoint — so a Connect service shows up even though it doesn't use grpc-go's Register…Server.

path, handler := orderv1connect.NewOrderServiceHandler(srv)
// endpoints: /OrderService/CreateOrder, ...

WebSocket

A route handler that performs a WebSocket upgrade — gorilla/websocket Upgrade, coder/nhooyr Accept — is labeled WS instead of its HTTP method. The connection entry is shown; per-message routing after the upgrade is dynamic (runtime dispatch) and isn't traced.

r.HandleFunc("/ws", h.Stream)   // h.Stream calls upgrader.Upgrade -> WS /ws

Adding a framework

Implement the Extractor interface and pass it via Options.Extractors:

type Extractor interface {
	Name() string
	Match(pkg *packages.Package) bool       // does this pkg use the framework?
	Extract(pkg *packages.Package) []Route  // walk syntax, return routes
}
av, _ := archview.New(archview.Options{
	Extractors: append(route.Default(), myExtractor{}),
})

On this page