Files
skills/docs/api-stack/api/api-graphql-ui-graphiql/src/factory/create-graphiql-ui.md
2026-05-29 08:20:56 +01:00

3.4 KiB

factory/create-graphiql-ui

(no submodules)

Purpose

Public entry point of the package. createGraphiqlUi(opts) returns a RouterPlugin — a plain value with name, version, parsed options, the OTel instrument bundle, and a register(router) method that mounts two routes on a host Router:

  • GET <uiEndpoint> — serves the GraphiQL HTML shell with the configured CSP header.
  • GET <uiEndpoint>/assets/:filename — serves bundled static assets with an immutable Cache-Control header.

The plugin is opt-in: the host's plugin wiring decides whether to call register(router). Mount-time validation runs Zod options parsing AND checks the host router for an existing route at the same path; collision raises GRAPHQL_UI_GRAPHIQL.ENDPOINT_COLLISION so operators see it at boot.

Features

  • Single-call factory — createGraphiqlUi(input) returns a frozen RouterPlugin.
  • Endpoint-collision detection at register time via router.collidesWith plus a defence-in-depth catch of ROUTER.ROUTE_COLLISION from the router.get call itself.
  • Stale-hash-tolerant asset routing — the same :filename param matches every served permutation of the asset name.
  • Counter increments only on HTML hits, never on asset hits.
  • Custom OTel MeterProvider accepted via the optional meterProvider field.

Usage: Basic

import { Router } from "@suaveplan/api-router";
import { createGraphiqlUi } from "@suaveplan/api-graphql-ui-graphiql";

const router = new Router({ plugin: "host" });
createGraphiqlUi({}).register(router);
// GET /graphql/ui now serves the explorer.

Usage: Advanced

import { MeterProvider } from "@opentelemetry/sdk-metrics";
import { Router } from "@suaveplan/api-router";
import { createGraphiqlUi } from "@suaveplan/api-graphql-ui-graphiql";

const meterProvider = new MeterProvider({ /* ... */ });
const plugin = createGraphiqlUi({
    uiEndpoint: "/api/explorer",
    graphqlEndpoint: "/api/gql",
    subscriptionEndpoint: "/api/gql/ws",
    theme: "dark",
    defaultQuery: "{ viewer { id } }",
    defaultVariableEditorOpen: true,
    editorOptions: { lineWrapping: true },
    meterProvider,
});

const router = new Router({ plugin: "host" });
plugin.register(router);

API Reference

  • interface RouterPlugin { name; version; options; instruments; register(router) }.
  • interface CreateGraphiqlUiInput extends GraphiqlUiOptionsInput { meterProvider?: MeterProvider }.
  • interface RouterResponseValue { status; headers; body } — the shape returned by the route handlers.
  • type GraphiqlRouteHandler — the handler signature consumed by the in-memory Router.
  • createGraphiqlUi(input?): RouterPlugin.
  • listGraphiqlAssetNames(): readonly string[].

Implementation Notes

The handler signature is intentionally adapter-agnostic — it accepts a plain { method, path } request and returns { status, headers, body }. Adapter routers (api-router-hono, worker bridges) wrap this with their native request context. The in-memory Router from @suaveplan/api-router uses the same shape directly, which is what the package's unit tests exercise. The RouterError -> GraphqlUiGraphiqlError translation in register() exists because router.collidesWith is best-effort (it only checks the UI endpoint, not the asset sub-route); the second router.get for assets may still throw ROUTE_COLLISION if some other plugin owns the asset prefix.