GraphQL with .NET Core (Part - IV: GraphiQL IDE)



Code samples used in this blog series have been updated to latest version of .NET Core (5.0.4) and GraphQL-Dotnet (4.2.0). Follow this link to get the updated samples.

GraphiQL (pronounced graphical) is an in-browser IDE for exploring GraphQL. I think it's a must-have tool for any server running behind GraphQL. With GraphiQL in place, you can easily give yourself or your team an in-depth insight into your API.

There are setups you have to do first. We need some packages installed. Create a package.json file and paste the following snippet,

{
  "dependencies": {
    "graphiql": "^1.0.0-alpha.8",
    "graphql": "^15.0.0",
    "isomorphic-fetch": "^2.2.1",
    "react": "^16.13.1",
    "react-dom": "^16.13.1"
  },
  "devDependencies": {
    "@babel/core": "^7.9.6",
    "@babel/plugin-proposal-class-properties": "^7.8.3",
    "@babel/plugin-syntax-dynamic-import": "^7.8.3",
    "@babel/preset-env": "^7.9.5",
    "@babel/preset-react": "^7.9.4",
    "babel-loader": "^8.1.0",
    "cross-env": "^7.0.2",
    "css-loader": "^3.5.1",
    "html-webpack-plugin": "^4.2.0",
    "react-dom": "^16.12.0",
    "style-loader": "^1.1.3",
    "webpack": "4.42.0",
    "webpack-cli": "^3.3.11",
    "webpack-dev-server": "^3.10.3"
  }
}
package.json

You might say, "Why do we need React!". GraphiQL is really just a React component. In order to use this component, we need a basic app skeleton similar to any React app, you will find online.

At this point, you can either use yarn or npm to install the packages.

yarn install

Or

npm install

Next, create a folder ApiExplorer and add the following two files with the code snippets,

  
<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, shrink-to-fit=no"
    />
    <title>GraphiQL</title>
  </head>

  <body>
    <style>
      body {
        padding: 0;
        margin: 0;
        min-height: 100vh;
      }
      #root {
        height: 100vh;
      }
    </style>
    <div id="root"></div>
  </body>
</html>
index.html.ejs
import React from "react";
import ReactDOM from "react-dom";
import GraphiQL from "graphiql";
import fetch from "isomorphic-fetch";
import "graphiql/graphiql.css";

const Logo = () => <span>{'Api Explorer'}</span>;

GraphiQL.Logo = Logo;

function graphQLFetcher(graphQLParams) {
  return fetch(`${window.location.protocol}//${window.location.host}/graphql`, {
    method: "post",
    headers: {
      "Accept": "application/json",
      "Content-Type": "application/json",
    },
    body: JSON.stringify(graphQLParams),
    credentials: "same-origin",
  })
    .then(function (response) {
      return response.text();
    })
    .then(function (responseBody) {
      try {
        return JSON.parse(responseBody);
      } catch (error) {
        return responseBody;
      }
    });
}

ReactDOM.render(
    <GraphiQL style={{ height: '100vh' }} fetcher={graphQLFetcher} />,
   document.getElementById("root")
);
index.jsx

GraphiQL is a client-side library which provides a React component i.e. <GraphiQL/>. It renders the whole graphical user interface of the IDE. The component has a fetcher attribute which can be attached to a function. The attached function returns an HTTP promise object which just mimics the POST requests that we have been making with Insomnia/Postman. All of these are done in the index.jsx.

The final bits is to bundle up and ship them to a publicly available static files folder. The task of bundling up is done by webpack. Add a webpack.config.js file and paste the following piece of code,

const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");
const isDev = process.env.NODE_ENV === "development";

module.exports = {
  entry: isDev
    ? [
        "react-hot-loader/patch",
        "webpack-dev-server/client?http://localhost:8080",
        "webpack/hot/only-dev-server",
        "./index.jsx",
      ]
    : "./index.jsx",
  context: path.resolve(__dirname, "./ApiExplorer"),
  output: {
    path: __dirname + "/wwwroot",
    filename: "bundle.js",
  },
  mode: "development",
  devtool: "inline-source-map",
  performance: {
    hints: false,
  },
  module: {
    rules: [
      {
        test: /\.html$/,
        use: ["file?name=[name].[ext]"],
      },
      {
        type: "javascript/auto",
        test: /\.mjs$/,
        use: [],
        include: /node_modules/,
      },
      {
        test: /\.(js|jsx)$/,
        use: [
          {
            loader: "babel-loader",
            options: {
              presets: [
                ["@babel/preset-env", { modules: false }],
                "@babel/preset-react",
              ],
            },
          },
        ],
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
      {
        test: /\.svg$/,
        use: [{ loader: "svg-inline-loader" }],
      },
    ],
  },
  resolve: {
    extensions: [".js", ".json", ".jsx", ".css", ".mjs"],
  },
  plugins: [new HtmlWebpackPlugin({ template: "index.html.ejs" })],
  devServer: {
    hot: true,
    allowedHosts: ["localhost:5000"],
  },
  node: {
    fs: "empty",
    module: "empty",
  },
};
webpack.config.js

The configuration is pretty much self-explanatory. It takes all the files under the ApiExplorer folder, the dependencies from the node_modules and compiles them into a single bundle.js file. It also generates an index.html file. Both of the compiled files are sent to the wwwroot to make them publicly available.

All done! Now run the webpack command in the terminal on the root of your project.

webpack --mode=development

On the server-side, in Startup.cs files, add the middleware to serve static files, specifically the default index.html file,

The Configure method should look like the following,

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseDefaultFiles();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseGraphQL();
}
Startup.cs

Now, run the application and you will be presented with the following interface,

GraphiQL IDE

If you get an error like the following,

System.InvalidOperationException: Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.

ASP.NET 3.0+ throws this error when some synchronous call is made. It means that GraphQL Dotnet library still depends on Newtonsoft.Json. And that library does some synchronous code execution. But for the time being, we can also overcome this situation. We only have to add these lines of code in ConfigureServices method.

services.AddGraphQL(options =>
{
    options.EndPoint = "/graphql";
});

services.Configure<KestrelServerOptions>(options =>
{
    options.AllowSynchronousIO = true;
});
Startup.cs

On the right-hand side is the documentation explorer pane, you can browse through different queries and have a deep understanding of what fields are available and what they are supposed to do

Some of the nice features this IDE has to offer are as follows,

  • Syntax highlighting
  • Intelligent type ahead of fields, arguments, types, and more.
  • Real-time error highlighting and reporting.
  • Automatic query completion.
  • Run and inspect query results.

Part IV

fiyazbinhasan/GraphQLCoreFromScratch
https://fiyazhasan.me/tag/graphql/. Contribute to fiyazbinhasan/GraphQLCoreFromScratch development by creating an account on GitHub.

Github repository for GraphiQL

Concepts of webpack

React.js Hello World

Babel.js installation guide for webpack