entrest

API Docs

How to replace the embedded Scalar docs UI with your own OpenAPI viewer.

How-to guide

This page shows how to customize or replace the embedded API reference UI. Runtime toggles (DisableSpecHandler, DisableDocsHandler, BaseURL, server URL injection, and so on) are documented in Runtime Configuration.

When entrest.Config.DisableSpecHandler is false at generation time, entrest embeds the OpenAPI spec in your generated rest package and registers GET {BasePath}/openapi.json and GET {BasePath}/docs (an embedded Scalar UI). With the stdlib handler, GET / (after BasePath is stripped) redirects to /docs. See Configuration — Base URL and path for how mount paths affect these URLs.

Scalar OpenAPI UI

Default embedded UI

The /docs route is implemented by Server.Docs. It renders a single inline HTML document on each request and loads Scalar from the jsDelivr CDN — there is no separate static site.

There is no runtime option to tweak Scalar's theme or layout on the embedded /docs handler. To change the UI, disable the embedded docs route and serve your own page (see below).

Route registration, spec serving, and DisableDocsHandler are covered in Configuration — OpenAPI spec endpoint and API reference docs.

Replace the embedded UI

Any OpenAPI viewer that accepts a spec URL can replace the embedded Scalar page:

  1. Set DisableDocsHandler to true so /docs is not registered by the generated handler.
  2. Keep /openapi.json enabled (unless you serve the spec from another origin).
  3. Register your own GET handler that returns a single HTML document.
  4. Point the viewer at {BasePath}/openapi.json (or an absolute URL if the UI is on another host).

The examples below use RapiDoc and mount custom docs at /v1/docs.

Stdlib (http.ServeMux)

This example serves one self-contained HTML page from Go with fmt.Fprintf, loads RapiDoc from the unpkg CDN, and overrides component attributes for a focused dark layout.

main.go
// [...]

func rapidocDocs(baseURL string) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        uri := strings.TrimSuffix(baseURL, "/")

        w.Header().Set("Content-Type", "text/html")
        w.WriteHeader(http.StatusOK)
        fmt.Fprintf(w, `<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="referrer" content="no-referrer">
  <script type="module" src="https://unpkg.com/rapidoc@9.3.8/dist/rapidoc-min.js"></script>
  <title>Pet Store API</title>
</head>
<body>
  <rapi-doc
    allow-server-selection="false"
    bg-color="#18181C"
    fill-request-fields-with-example="false"
    font-size="large"
    layout="row"
    load-fonts="false"
    mono-font="Consolas, monaco, monospace"
    nav-bg-color="#0F0F0F"
    nav-hover-bg-color="#1B1B1B"
    primary-color="#10B981"
    regular-font="Consolas, monaco, monospace"
    render-style="focused"
    schema-description-expanded="true"
    show-curl-before-try="true"
    show-header="false"
    show-method-in-nav-bar="as-colored-text"
    spec-url=%q
    text-color="#BBBBBB"
    theme="dark"
    use-path-in-nav-bar="true"
    persist-auth="true"
  ></rapi-doc>
</body>
</html>`, uri+"/openapi.json")
    }
}

func main() {
    // [...]

    mux := http.NewServeMux()
    mux.HandleFunc("GET /v1/docs{$}", rapidocDocs(cfg.BaseURL))
    mux.Handle("/", srv.Handler())

    log.Fatal(http.ListenAndServe(":8080", mux))
}

Chi

Register the docs route on the same sub-router where you mount the generated API. Set BasePath to match the mount point so spec URLs stay correct — see Configuration — Stdlib vs chi.

main.go
cfg := &rest.ServerConfig{
    BaseURL:            "https://api.example.com/v1",
    BasePath:           "/v1",
    DisableDocsHandler: true,
}
srv, err := rest.NewServer(db, cfg)
if err != nil {
    panic(err)
}

r := chi.NewRouter()
r.Route("/v1", func(r chi.Router) {
    r.Get("/docs", rapidocDocs(cfg.BaseURL))
    srv.Handler(r)
})
http.ListenAndServe(":8080", r)

Last updated on

On this page