GraphiQL logo

Backstage GraphiQL Plugin

Created by Spotify

GraphiQL is a browser IDE for GraphQL. It lets you explore a schema. You can write queries with autocomplete. You can add variables. You can see results fast. It is a simple way to learn an API and to debug it.

The GraphiQL Backstage plugin brings that experience into your portal. You get a page in Backstage where engineers can switch between approved endpoints. Queries run with the same auth that your portal already uses. You can supply custom headers. You can plug in your own fetchers. The plugin can host GraphiQL plugins too, so teams can extend the editor with tools they rely on.

This is handy for day to day work. You can inspect internal schemas without leaving your portal. You can prove changes before wiring a client. You can share a query with a teammate next to the docs of the service. You can keep a stable way for support and SRE to reproduce issues against a live graph. The plugin also includes a helper for the GitHub GraphQL API, which makes it easy to test against that graph if your org uses it.

If you already run Backstage, this plugin gives you a safe home for GraphQL exploration inside the same place your teams use for docs and APIs.

A screenshot of the GraphiQL plugin.

Installation steps

These instructions apply to self-hosted Backsgage only. To use this plugin on Roadie, visit the docs.

Install the frontend package

Copy
yarn --cwd packages/app add @backstage-community/plugin-graphiql

Add the GraphiQL page to your app

Add the route so the page renders.

Copy
// packages/app/src/App.tsx
import React from 'react';
import { FlatRoutes, Route } from '@backstage/core-app-api';
import { GraphiQLPage } from '@backstage-community/plugin-graphiql';

export const routes = (
  <FlatRoutes>
    <Route path="/graphiql" element={<GraphiQLPage />} />
  </FlatRoutes>
);

Provide GraphQL endpoints to the plugin

The plugin reads endpoints from the GraphQLBrowse API. Implement it in your app APIs.

This example shows two endpoints. One static GitLab endpoint. One custom internal endpoint that uses fetch.

Copy
// packages/app/src/apis.ts
import {
  createApiFactory,
  errorApiRef,
  githubAuthApiRef,
} from '@backstage/core-plugin-api';
import {
  graphQlBrowseApiRef,
  GraphQLEndpoints,
} from '@backstage-community/plugin-graphiql';

// add this factory to your exported apis array
export const apis = [
  createApiFactory({
    api: graphQlBrowseApiRef,
    deps: { errorApi: errorApiRef, githubAuthApi: githubAuthApiRef },
    factory: ({ errorApi, githubAuthApi }) =>
      GraphQLEndpoints.from([
        // static endpoint with optional extra headers
        GraphQLEndpoints.create({
          id: 'gitlab',
          title: 'GitLab',
          url: 'https://gitlab.com/api/graphql',
          headers: { Extra: 'Header' },
        }),
        // custom endpoint using your own fetcher
        {
          id: 'hooli-search',
          title: 'Hooli Search',
          fetcher: async (params: any) => {
            return fetch('https://internal.hooli.com/search', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify(params),
            }).then(res => res.json());
          },
          plugins: [],
        },
      ]),
  }),
];

You can also add a GitHub endpoint that uses the GitHub auth API.

Copy
// packages/app/src/apis.ts
import {
  createApiFactory,
  errorApiRef,
  githubAuthApiRef,
} from '@backstage/core-plugin-api';
import {
  graphQlBrowseApiRef,
  GraphQLEndpoints,
} from '@backstage-community/plugin-graphiql';

export const apis = [
  createApiFactory({
    api: graphQlBrowseApiRef,
    deps: { errorApi: errorApiRef, githubAuthApi: githubAuthApiRef },
    factory: ({ errorApi, githubAuthApi }) =>
      GraphQLEndpoints.from([
        GraphQLEndpoints.github({
          id: 'github',
          title: 'GitHub',
          githubAuthApi,
          errorApi,
        }),
      ]),
  }),
];

Add a nav item so users can open the page.

Copy
// packages/app/src/components/Root/Root.tsx
import React from 'react';
import { Sidebar, SidebarItem } from '@backstage/core-components';
import { GraphiQLIcon } from '@backstage-community/plugin-graphiql';

export const Root = ({ children }: { children?: React.ReactNode }) => (
  <>
    <Sidebar>
      <SidebarItem icon={GraphiQLIcon} to="/graphiql" text="GraphiQL" />
    </Sidebar>
    {children}
  </>
);

Notes on CORS and headers

The browser must be allowed to call your endpoint. If the endpoint does not allow cross origin requests, use a proxy or serve a backend that forwards requests. Add any auth headers in your endpoint setup as shown above.

Using the new frontend system

You can expose the page and a nav item with the alpha exports. Keep the same apis.ts setup shown earlier.

Export the page and nav item from your extensions file.

Copy
// packages/app/src/extensions.ts
export { graphiqlPage, graphiqlNavItem } from '@backstage-community/plugin-graphiql/alpha';

Add path config for the page in your app config if your app expects it.

Copy
# app-config.yaml
app:
  extensions:
    - id: page:graphiql
      config:
        path: /graphiql

Keep your sidebar link pointing to the same path.

Things to know

Adding multiple GraphQL APIs

Multiple APIs can be connected to the GraphiQL page. Each GraphQLEndpoint added to the API factory in apis.ts is rendered as a seperate tab. For example, let’s add a second API:

Copy
  createApiFactory({
    api: graphQlBrowseApiRef,
    deps: { },
    factory: () =>
      GraphQLEndpoints.from([
        GraphQLEndpoints.create({
          id: 'gitlab',
          title: 'GitLab',
          url: 'https://gitlab.com/api/graphql',
          headers: { 'Some-Key': 'Some-Value' }
        }),
        // add a second API endpoint...
        {
          id: 'spacex',
          title: 'SpaceX',
          fetcher: async (params: any) => {
            return fetch('https://api.spacex.land/graphql/', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify(params)
            }).then(res => res.json());
          }
        }
      ])
  })

Rather than using the GraphQLEndpoints.create function to create the GraphQLEndpoint, we creating an an object with a custom fetcher fucntion. This approach allows for more control over constructing the HTTP request to the API. This may be necessary in special cases where the GraphQL endpoint as unique requirements.

The end result is the same though, a new tab for the ‘SpaceX’ API:

Multiple GraphQL APIs in GraphiQL

Connecting to an authenticated API with GitHub authentication

A special github function is available on the GraphQLEndpoints class. This function allows you to connect to a GitHub-authenticated GraphQL endpoint using the built-in GitHub auth provider. This assumes that you are using the GitHub auth provider in your Backstage implementation.

Adding a GitHub-authenticated API is quite simple. For example, this is how you would setup a connection to the official GitHub GraphQL API:

Copy
import {
  AnyApiFactory,
  createApiFactory,
  errorApiRef,
  githubAuthApiRef
} from '@backstage/core';
import {
  graphQlBrowseApiRef,
  GraphQLEndpoints
} from '@backstage/plugin-graphiql'

export const apis: AnyApiFactory[] = [
  createApiFactory({
    api: graphQlBrowseApiRef,
    deps: { errorApi: errorApiRef, githubAuthApi: githubAuthApiRef },
    factory: ({ errorApi, githubAuthApi }) =>
        GraphQLEndpoints.github({
          id: 'github',
          title: 'GitHub',
          url: 'https://api.github.com/graphql',
          errorApi: errorApi,
          githubAuthApi: githubAuthApi
        })
      ])
  })
];

The current user logged into Backstage can now access information that is only available to authenticated GitHub users. For example, the user could query for their private repositories:

GitHub authenticated GraphQL API

The github function even attempts to request additional API scopes if the current user’s authentication token doesn’t have the scopes required to execute the GraphQL query or mutation.

Set up Backstage in minutes with Roadie