SpringBoot and GraphQL and GraphIql Explorer

TL;DR – it turns out the 2.0.3 version of spring-graphql has the correct index.html (with esm.sh, as shown below). To get the latest, update your gradle plugin org.springframework.boot to version 3.4.8.

Working through yet another Spring Boot + GraphQL tutorial (this time from tutorials), and hit the issue where the graphiql fails to load from http://localhost:8080/graphiql?path=/graphql

The “Loading” text would appear, but everything after that point would just hang.

The problem is that ‘index.html’ from the spring-graphql.jar (either 1.2.4 or 1.3.3) uses invalid unpkg.com links (for the graphiql React application), instead of the correct esm.sh links. The version that is pre-packaged with that artifact is completely out-of-date.

Since you are using Spring Boot, you can just drop a replacement ‘index.html’ into your src/main/resources/graphiql/ directory. Just restart your server ( ./gradlew bootRun ), and refresh the web page. You can tell your updated version is loaded because the title of the browser window will change from “GraphiQL” to “GraphiQLmod”.

That file is below. Note: I get to use the file as-is, since I wrote it. You, however, have to examine all 79 lines and make sure there is nothing nefarious in the file. Oh, and change the extension from .odt back to its original .html by opening the file in LibreOffice Writer. Therefore, I have put the whole thing at the bottom of this post as well. And, there is a .pdf version available to download as well. (It is hilarious that WordPress thinks “.txt” is a security risk, but “.pdf” and “.odt” are perfectly fine.)

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>GraphiQLmod</title>
    <style>
        body {
            margin: 0;
        }
        #graphiql {
            height: 100dvh;
        }
        .loading {
            height: 100%;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 4rem;
        }
    </style>
    <link rel="stylesheet" href="https://esm.sh/graphiql@5.2.1/dist/style.css"/>
    <link rel="stylesheet" href="https://esm.sh/@graphiql/plugin-explorer@5.1.1/dist/style.css"/>
    <script type="importmap">
        {
          "imports": {
            "react": "https://esm.sh/react@19.1.0",
            "react/": "https://esm.sh/react@19.1.0/",
            "react-dom": "https://esm.sh/react-dom@19.1.0",
            "react-dom/": "https://esm.sh/react-dom@19.1.0/",
            "graphiql": "https://esm.sh/graphiql@5.2.1?standalone&external=react,react-dom,@graphiql/react,graphql",
            "graphiql/": "https://esm.sh/graphiql@5.2.1/",
            "@graphiql/plugin-explorer": "https://esm.sh/@graphiql/plugin-explorer@5.1.1?standalone&external=react,@graphiql/react,graphql",
            "@graphiql/react": "https://esm.sh/@graphiql/react@0.37.2?standalone&external=react,react-dom,graphql,@graphiql/toolkit,@emotion/is-prop-valid",
            "@graphiql/toolkit": "https://esm.sh/@graphiql/toolkit@0.11.3?standalone&external=graphql",
            "graphql": "https://esm.sh/graphql@16.11.0",
            "@emotion/is-prop-valid": "data:text/javascript,"
          }
        }
    </script>
    <script type="module">
        import React from 'react';
        import ReactDOM from 'react-dom/client';
        import { GraphiQL, HISTORY_PLUGIN } from 'graphiql';
        import { createGraphiQLFetcher } from '@graphiql/toolkit';
        import { explorerPlugin } from '@graphiql/plugin-explorer';
        import 'graphiql/setup-workers/esm.sh';

        const params = new URLSearchParams(window.location.search);
        const path = params.get('path') || '/graphql';
        const url = `${location.protocol}//${location.host}${path}`;
        const wsPath = params.get('wsPath') || '/graphql';
        const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
        const subscriptionUrl = `${wsProtocol}//${location.host}${wsPath}`;
        const gqlFetcher = createGraphiQLFetcher({'url': url, 'subscriptionUrl': subscriptionUrl});
        const plugins = [HISTORY_PLUGIN, explorerPlugin()];
        const xsrfToken = document.cookie.match(new RegExp('(?:^| )XSRF-TOKEN=([^;]+)'));
        const initialHeaders = xsrfToken ? `{ 'X-XSRF-TOKEN' : '${ xsrfToken[1] }' }` : undefined;

        function App() {
            return React.createElement(GraphiQL, {
                fetcher: gqlFetcher,
                defaultEditorToolsVisibility: true,
                headerEditorEnabled: true,
                shouldPersistHeaders: true,
                initialHeaders: initialHeaders,
                plugins: plugins,
            });
        }

        const container = document.getElementById('graphiql');
        const root = ReactDOM.createRoot(container);
        root.render(React.createElement(App));
    </script>
</head>
<body>
    <div id="graphiql"><div class="loading">Loading...</div></div>
</body>
</html>

This entry was posted in Software Engineering, Software Project. Bookmark the permalink.