GraphiQL for ASP.NET Core
Jürgen Gutsch - 26 October, 2017
One nice thing about blogging is the feedback from the readers. I got some nice kudos, but also great new ideas. One idea was born out of a question about a "graphi" UI for the GraphQL Middleware I wrote some months ago. I never heard about "graphi", which actually is "GraphiQL", a generic HTML UI over a GraphQL endpoint. It seemed to be something like a Swagger UI, but just for GraphQL. That sounds nice and I did some research about that.
What is GraphiQL?
Actually it is absolutely not the same as Swagger and not as detailed as Swagger, but it provides a simple and easy to use UI to play around with your GraphQL end-point. So you cannot really compare it.
GraphiQL is a React component provided by the GraphQL creators, that can be used in your project. It basically provides an input area to write some GraphQL queries and a button to sent that query to your GrapgQL end-point. You'll than see the result or the error on the right side of the UI.
Additionally it provides some more nice features:
- A history of sent queries, which appears on the left side, if you press the history-button. To reuse previously used queries.
- It rewrites the URL to support linking to a specific query. It stores the query and the variables in the URL, to sent it to someone else, or to bookmark the query to test.
- It actually creates a documentation out of the GraphQL end-point. By clicking at the "Docks" link it opens a documentation about the types used in this API. This is really magic, because it shows the documentation of a type I never requested:
Implementing GraphiQL
The first idea was to write something like this by my own. But it should be the same as the existing GraphiQL UI. Why not using the existing implementation? Thanks to Steve Sanderson, we have the Node Services for ASP.NET Core. Why not running the existing GraphiQL implementation in a Middleware using the NodeServices?
I tried it with the "apollo-server-module-graphiql" package. I called this small JavaScript to render the graphiql UI and return it back to C# via the NodeSerices:
var graphiql = require('apollo-server-module-graphiql');
module.exports = function (callback, options) {
var data = {
endpointURL: options.graphQlEndpoint
};
var result = graphiql.renderGraphiQL(data);
callback(null, result);
};
The usage of that script inside the Middleware looks like this:
var file = _env.WebRootFileProvider.GetFileInfo("graphiql.js");
var result = await _nodeServices.InvokeAsync<string>(file.PhysicalPath, _options);
await httpCont
That works great, but has one problem. It wraps the GraphQL query in a JSON-Object that was posted to the GraphQL end-point. I would need to change the GraphQlMiddleware
implementation, because of that. The current implementation expects the plain GraphQL query in the POST body.
What is the most useful approach? Wrapping the GraphQL query in a JSON object or sending the plain query? Any Ideas? What do you use? Please tell me by dropping a comment.
With this approach I'm pretty much dependent to the Apollo developers and need to change my implementation, whenever they change their implementations.
This is why I decided to use the same concept of generating the UI as the "apollo-server-module-graphiql" package but implemented in C#. This unfortunately doesn't need the NodeServices anymore.
I use exact the same generated code as this Node module, but changed the way the query is send to the server. Now the plain query will be sent to the server.
I started playing around with this and added it to the existing project, mentioned here: GraphQL end-point Middleware for ASP.NET Core.
Using the GraphiqlMiddleware
The result is as easy to use as the GraphQlMiddleware
. Let's see how it looks to add the Middlewares:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
// adding the GraphiQL UI
app.UseGraphiql(options =>
{
options.GraphiqlPath = "/graphiql"; // default
options.GraphQlEndpoint = "/graph"; // default
});
}
// adding the GraphQL end point
app.UseGraphQl(options =>
{
options.GraphApiUrl = "/graph"; // default
options.RootGraphType = new BooksQuery(bookRepository);
options.FormatOutput = true; // default: false
});
As you can see the second Middleware is bound to the first one by using the same path "/graph". I didn't create any hidden dependency between the both Middlewares, to make it ease to use it in various combinations. Maybe you want to use the GraphiQL UI only in the Development or Staging environment as shown in this example.
Now start the web using Visual Studio (press [F5]). The web starts with the default view or API. Add "graphiql" to the URL in the browsers address bar and see what happens. You should see a generated UI for your GraphQL endpoint, where you can now start playing around with our API, testing and debugging it with your current data. (See the screenshots on top.)
I'll create a separate NuGet package for the GraphiqlMiddleware
. This will not have the GraphQlMiddleware
as a dependency and could be used completely separate.
Conclusion
This was a lot easier to implement than expected. Currently there is still some refactoring needed:
- I don't like to have the HMTL and JavaScript code in the C#. I'd like to load that from an embedded resource file, which actually is a HTML file.
- I should add some more configuration options. E.g. to change the theme, as equal to the original Node implementation, to preload queries and results, etc.
- Find a way to use it offline as well. Currently there's a connection to the internet needed to load the CSS and JavaScripts from the CDNs.
You wanna try it? Download, clone or fork the sources on GitHub.
What do you think about that? Could this be useful to you? Please leave a comment and tell me about your opinion.
Update [10/26/2017 21:03]
GraphiQL is much more powerful than expected. I was wondering how the GraphQL create IntelliSense support in the editor and how it creates the documentation. I had a deeper look into the traffic and found two more cool things about it:
First: GraphiQL sends a special query to the GraphQL to request for the GraphQL specific documentation. IN this case it looks like this:
query IntrospectionQuery {
__schema {
queryType { name }
mutationType { name }
subscriptionType { name }
types {
...FullType
}
directives {
name
description
locations
args {
...InputValue
}
}
}
}
fragment FullType on __Type {
kind
name
description
fields(includeDeprecated: true) {
name
description
args {
...InputValue
}
type {
...TypeRef
}
isDeprecated
deprecationReason
}
inputFields {
...InputValue
}
interfaces {
...TypeRef
}
enumValues(includeDeprecated: true) {
name
description
isDeprecated
deprecationReason
}
possibleTypes {
...TypeRef
}
}
fragment InputValue on __InputValue {
name
description
type { ...TypeRef }
defaultValue
}
fragment TypeRef on __Type {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
}
}
}
}
Try this query and sent it to your GraphQL API using Postman or a similar tool and see what happens :)
Second: GraphQL for .NET knows how to answer that query and sent the full documentation about my data structure to the client like this:
{
"data": {
"__schema": {
"queryType": {
"name": "BooksQuery"
},
"mutationType": null,
"subscriptionType": null,
"types": [
{
"kind": "SCALAR",
"name": "String",
"description": null,
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "Boolean",
"description": null,
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "Float",
"description": null,
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "Int",
"description": null,
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "ID",
"description": null,
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "Date",
"description": "The `Date` scalar type represents a timestamp provided in UTC. `Date` expects timestamps to be formatted in accordance with the [ISO-8601](https://en.wikipedia.org/wiki/ISO_8601) standard.",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "Decimal",
"description": null,
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
[ . . . ]
// many more documentation from the server
}
}
}
This is really awesome. With GraphiQL I got a lot more stuff than expected. And it didn't take more than 5 hours to implement this middleware.