Skip to content

Router Selection

wiki edited this page Sep 4, 2026 · 1 revision

Router selection

Two independent questions: which routers' routes appear in the document, and which router serves it.

openapi.NewConfig(
	openapi.WithIncludeRouters("default", "partner"),
	openapi.WithExcludeRouters("internal"),
	openapi.WithServeOnRouter("default"),
	openapi.WithServePath("/openapi.json"),
)

What appears

IncludeRouters Result
empty (default) the default router only
"*" every router
a list of names exactly those

ExcludeRouters is applied after include, which is what makes the "everything except" case expressible:

openapi.WithIncludeRouters("*"),
openapi.WithExcludeRouters("internal", "metrics", "health"),

Why the default is not "everything"

The generator used to collect from every router, so a route on an internal-only listener was published in the document served on the public one.

A router existing on a separate port is usually a statement that its routes are not for the same audience. The document should not contradict that.

This is defence in depth rather than the only protection: a route also has to implement OpenAPIRoute to appear at all, so an internal endpoint that never declares itself is excluded twice over. But "the operator forgot" is a normal state of the world, and the safe default is the one that survives it.

Where it is served

openapi.WithServeOnRouter("docs")  // empty means the default router
openapi.WithServePath("/openapi.json")

The two are independent, and the useful combinations follow from that:

A public document on the public listener — the common case, and the default.

openapi.NewConfig() // include: default only; serve: default; /openapi.json

A partner-facing document, served internally. Generate from the partner router, serve on an internal one, so the specification is reviewable without being published:

openapi.NewConfig(
	openapi.WithIncludeRouters("partner"),
	openapi.WithServeOnRouter("internal"),
)

Everything, internally. A complete document including operational routes, reachable only from the private network:

openapi.NewConfig(
	openapi.WithIncludeRouters("*"),
	openapi.WithServeOnRouter("internal"),
	openapi.WithServePath("/full-openapi.json"),
)

Two documents

Register the extension twice with different configurations — the routes are different, and so are the serve paths:

app := rex.New(
	openapi.WithOpenAPI(openapi.NewConfig(
		openapi.WithTitle("Public API"),
		openapi.WithServePath("/openapi.json"),
	)),
	openapi.WithOpenAPI(openapi.NewConfig(
		openapi.WithTitle("Internal API"),
		openapi.WithIncludeRouters("*"),
		openapi.WithServeOnRouter("internal"),
		openapi.WithServePath("/openapi.json"),
	)),
)

Each extension instance holds its own configuration and generates its own document. The serve paths only need to differ within a router.

Serving to Swagger UI

app := rex.New(
	openapi.WithOpenAPI(openapi.NewConfig(openapi.WithServePath("/openapi.json"))),
	swagger.WithSwagger(nil),
)

Point the UI at wherever the document is served — including on a different router, if the UI is internal and the document is not.

Routes on a router that does not exist

A route registered to a router nobody created is a startup error from the framework, before the generator ever runs. Naming a nonexistent router in IncludeRouters is not an error — it simply matches nothing, and the document comes out smaller than expected. Check the route count if a document looks thin.

Clone this wiki locally