10 min read

Building GraphQL Server on ASP.NET Core

Justin Yoo

GraphQL is a query language led by Facebook, for API. Many use cases are providing GraphQL based API and GitHub is one of them. GraphQL is useful, especially for front-end applications and mobile apps that cover most user interactions on the client-side. This post and its series discuss building GraphQL server/client using .NET Core based application, including ASP.NET Core.

  • Building GraphQL Server on ASP.NET Core
  • Building GraphQL Server on ASP.NET Core for Headless Wordpress
  • Migrating GraphQL Server to Azure Functions in .NET Core
  • Building GraphQL Server on Azure Functions in JavaScript
  • Building Simple CMS with Blazor Web Assembly, with GraphQL and Headless Wordpress

The sample code used in this post can be found at this GitHub repository.

How Attractive Is GraphQL, Comparing to REST API?

If I am asked this question,

Well, probably yes? I'm not too sure for now.

Would be my answer. One thing I noticed on GraphQL is that it solves both over-fetching and under-fetching issues that REST API has. As REST API has its fixed schema, when I call an API request to a specified endpoint, I expect that the endpoint always returns the data with the same data structure. Here's an example of REST API endpoints for a blogging system.

GET /posts
GET /posts/{postId}
GET /authors
GET /authors/{authorId}
GET /tags
...
view raw 01-api-list.txt hosted with ❤ by GitHub

In many cases, I should call multiple endpoints to aggregate data that I want. For example, sending a request to /posts returns the list of posts with authorId, not the author details. Therefore, I should send another request to /authors/{authorId} with the authorId value multiple times. This is called under-fetching because of the API's flat structure. To solve this under-fetching issue, either BFF (Backends for Frontends) pattern or Gateway Aggregation pattern, or combination of both is used. But still, there are "multiple requests" happening for aggregation.

On the other hand, if the API has nested structures, the payload might be too verbose. For example, sending a request to /posts doesn't only return authorId but also includes all author details. I only need the authors' ID and name, but other details are also returned. This is called over-fetching. GitHub REST API is a typical example of over-fetching. The problem of over-fetching is too verbose and expensive due to the high network IO consumption.

What if I can compose an API request that I want to receive? What if the front-end application has an ability to compose data request structure, rather than relying on the API server-side? I think GraphQL changes the controllability from the server-side to client-side.

So, why not building a GraphQL server then? It sounds fun!

Building GraphQL Server on ASP.NET Core Application

As GraphQL is another type of API server, we can use any programming language. We're going to use ASP.NET Core and there are several .NET implementations. Let's use the most popular one, graphql-dotnet.

If you are not familiar with GraphQL with ASP.NET Core, I would recommend Glenn Block's awesome online lecture on LinkedIn Learning. I reorganised my app based on his instruction.

First of all, let's create a C# class library project. It has all the logic for GraphQL server.

dotnet new classlib -n PostsQL

Then add a NuGet package. That's it for the project setting. The latest stable version of GraphQL is 2.4.0.

dotnet add package GraphQL

Defining Models

Let's define Post and Author like below:

public class Author
{
public Author(int id, string name)
{
this.Id = id;
this.Name = name;
}
public int Id { get; set; }
public string Name { get; set; }
}
public class Post
{
public Post(int id, string title, string slug, DateTimeOffset published, string authorId, PostStatus status)
{
this.Id = id;
this.Title = title;
this.Slug = slug;
this.Published = published;
this.AuthorId = authorId;
this.Status = status;
}
public int Id { get; set; }
public string Title { get; set; }
public string Slug { get; set; }
public DateTimeOffset Published { get; set; }
public string AuthorId { get; set; }
public PostStatus Status { get; set; }
}
public enum PostStatus
{
Created = 1,
Published = 2,
Deleted = 3
}
view raw 04-models.cs hosted with ❤ by GitHub

Defining Services to Data

Let's write a service layer to access data storage. In fact, data storage can be anything, but this time we just use a hard-coded memory DB, which is sufficient for now. As you can see, both AuthorService and PostService are nothing that special.

public interface IAuthorService
{
Task<Author> GetAuthorByIdAsync(int id);
Task<List<Author>> GetAuthorsAsync();
}
public class AuthorService : IAuthorService
{
private readonly List<Author> _authors;
public AuthorService()
{
this._authors = new List<Author>()
{
new Author(1, "Natasha Romanoff"),
new Author(2, "Carol Danvers"),
new Author(3, "Jean Grey"),
new Author(4, "Wanda Maximoff"),
new Author(5, "Gamora"),
};
}
public async Task<Author> GetAuthorByIdAsync(int id)
{
return await Task.FromResult(this._authors.SingleOrDefault(p => p.Id.Equals(id)));
}
public async Task<List<Author>> GetAuthorsAsync()
{
return await Task.FromResult(this._authors);
}
}
public interface IPostService
{
Task<Post> GetPostByIdAsync(int id);
Task<List<Post>> GetPostsAsync();
}
public class PostService
{
private readonly List<Post> _posts;
public PostService()
{
this._posts = new List<Post>()
{
new Post(1, "Post #1", "post-1", DateTimeOffset.UtcNow.AddHours(-4), 1, PostStatus.Deleted),
new Post(2, "Post #2", "post-2", DateTimeOffset.UtcNow.AddHours(-3), 2, PostStatus.Published),
new Post(3, "Post #3", "post-3", DateTimeOffset.UtcNow.AddHours(-2), 3, PostStatus.Published),
new Post(4, "Post #4", "post-4", DateTimeOffset.UtcNow.AddHours(-1), 4, PostStatus.Published),
new Post(5, "Post #5", "post-5", DateTimeOffset.UtcNow.AddHours(0), 5, PostStatus.Created),
};
}
public async Task<Post> GetPostByIdAsync(int id)
{
return await Task.FromResult(this._posts.SingleOrDefault(p => p.Id.Equals(id)));
}
public async Task<List<Post>> GetPostsAsync()
{
return await Task.FromResult(this._posts);
}
}
view raw 05-services.cs hosted with ❤ by GitHub

Defining GraphQL Schemas

We've got codes for data manipulation. Let's build GraphQL schemas that convert existing data models to GraphQL types. AuthorType inherits the ObjectGraphType<T> class with Author (line #1) and exposes its properties.

public class AuthorType : ObjectGraphType<Author>
{
public AuthorType()
{
this.Field(p => p.Id);
this.Field(p => p.Name);
}
}
view raw 06-schemas-type-1.cs hosted with ❤ by GitHub

PostType also inherits the ObjectGraphType<T> class with Post (line #13). We define the PostStatusEnum class inheriting EnumerationGraphType (line #1), which converts the PostStatus enum value (line #26). In addition to this, AuthorType is combined based on the authorId value (line #25).

public class PostStatusEnum : EnumerationGraphType
{
public PostStatusEnum()
{
this.Name = "PostStatus";
this.AddValue(new EnumValueDefinition() { Name = "Created", Description = "Post was created", Value = 1 });
this.AddValue(new EnumValueDefinition() { Name = "Published", Description = "Post has been published", Value = 2 });
this.AddValue(new EnumValueDefinition() { Name = "Deleted", Description = "Post was deleted", Value = 3 });
}
}
public class PostType : ObjectGraphType<Post>
{
private readonly IAuthorService _authorService;
public PostType(IAuthorService authorService)
{
this._authorService = authorService;
this.Field(p => p.Id);
this.Field(p => p.Title);
this.Field(p => p.Slug);
this.Field(p => p.Published);
this.FieldAsync<AuthorType>("author", resolve: async c => await this._authorService.GetAuthorByIdAsync(c.Source.AuthorId));
this.Field<PostStatusEnum>("status", resolve: c => c.Source.Status);
}
}
view raw 07-schemas-type-2.cs hosted with ❤ by GitHub

Let's build a query object, PostsQuery. It contains posts that returns all the list of PostType as an array (line #11-13). It also contains post that returns a single PostType corresponding to a specified ID (line #15-19).

public class PostsQuery : ObjectGraphType<object>
{
private readonly IPostService _postService;
public PostsQuery(IPostService postService)
{
this._postService = postService;
this.Name = "Query";
this.FieldAsync<ListGraphType<PostType>>(
"posts",
resolve: async c => await this._postService.GetPostsAsync());
this.FieldAsync<PostType>(
"post",
arguments: new QueryArguments(
new QueryArgument<NonNullGraphType<IntGraphType>>() { Name = "id", Description = "Post ID" }),
resolve: async c => await this._postService.GetPostByIdAsync(c.GetArgument<int>("id")));
}
}
view raw 08-schemas-query.cs hosted with ❤ by GitHub

Finally, let's expose all those schemas to UI, using PostsSchema. It injects the PostsQuery instance and IDependencyResolver instance that resolves other instances injected.

public class PostsSchema : Schema
{
public PostsSchema(PostsQuery query, IDependencyResolver resolver)
{
this.Query = query;
this.DependencyResolver = resolver;
}
}
view raw 09-schemas-schema.cs hosted with ❤ by GitHub

We've got all GraphQL data and service contract definitions. Let's build the server with ASP.NET Core!

Building ASP.NET Core UI

Create an empty ASP.NET Core project that hosts the GraphQL UI.

dotnet new web -n Server.WebApp

Add NuGet packages.

dotnet add package Microsoft.AspNetCore
dotnet add package GraphQL.Server.Core
dotnet add package GraphQL.Server.Transports.AspNetCore
dotnet add package GraphQL.Server.Transports.WebSockets

The installed GraphQL.Server.Core of version 3.4.0 contains GraphQL-Parser of version 3.0.0, but it doesn't get along with each other. Therefore, it should be installed separately. However, the latest version (5.x) of GraphQL-Parser is incompatible with the current GraphQL.Server.Core version of 3.4.0, which is the known issue. Therefore, we should install the parser version 4.1.2.

dotnet add package GraphQL-Parser -v 4.1.2

The last NuGet package is for UI. While there are other UI libraries, we use GraphiQL.

dotnet add package GraphQL.Server.Ui.GraphiQL

And add a reference to the PostsQL project.

dotnet add reference PostsQL

The ASP.NET Core project settings are over. Let's add dependencies to Startup.cs. First of all, add dependencies to the ConfigureServices() method (line #5-11). Make sure to add the IDependencyResolver instance so that other dependencies can be resolved within GrahpQL (line #13). And finally, add GraphQL schema objects (line #15-17).

public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IAuthorService, AuthorService>();
services.AddSingleton<IPostService, PostService>();
services.AddSingleton<AuthorType>();
services.AddSingleton<PostType>();
services.AddSingleton<PostStatusEnum>();
services.AddSingleton<PostsQuery>();
services.AddSingleton<PostsSchema>();
services.AddSingleton<IDependencyResolver>(p => new FuncDependencyResolver(type => p.GetRequiredService(type)));
services.AddGraphQL()
.AddWebSockets()
.AddGraphTypes(Assembly.GetAssembly(typeof(PostsSchema)));
}
}
view raw 15-startup.cs hosted with ❤ by GitHub

We also need to configure the Configure() method for GraphiQL UI (line #15-18). And finally, add the default routing table to /ui/graphql (line #11).

public class Startup
{
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await Task.Run(() => context.Response.Redirect("/ui/graphql", permanent: true));
});
});
app.UseWebSockets();
app.UseGraphQLWebSockets<PostsSchema>();
app.UseGraphQL<PostsSchema>();
app.UseGraphiQLServer(new GraphiQLOptions() { GraphiQLPath = "/ui/graphql", GraphQLEndPoint = "/graphql" });
}
}
view raw 16-startup.cs hosted with ❤ by GitHub

Done! Let's build and run the app.

dotnet run Server.WebApp
view raw 17-dotnet-run.sh hosted with ❤ by GitHub

It seems to be running! Enter the following URL to access to the UI.

https://localhost:5001/
view raw 18-localhost.txt hosted with ❤ by GitHub

It's automatically redirected to https://localhost:5001/ui/graphql. But it throws an error!

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

This is because one of the dependencies, Newtonsoft.Json doesn't support async operations. To solve this issue, add one line of code to the ConfigureServices() method. If you use IIS, replace KestrelServerOptions with IISServerOptions (line #3).

public void ConfigureServices(IServiceCollection services)
{
services.Configure<KestrelServerOptions>(o => o.AllowSynchronousIO = true);
...
}
view raw 19-startup.cs hosted with ❤ by GitHub

Compile all the project again and run the app.

dotnet run Server.WebApp
view raw 17-dotnet-run.sh hosted with ❤ by GitHub

And enter the following URL through your browser.

https://localhost:5001/
view raw 18-localhost.txt hosted with ❤ by GitHub

Now, the GraphQL server is up and running as expected!

Let's run some queries. The first query takes one post with details of id, title, slug, author id and author name while the second query fetches all posts with id, title and published values.

query {
post(id: 3) {
id,
title,
slug,
author {
id,
name
}
}
posts {
id,
title,
published
}
}
view raw 20-query.txt hosted with ❤ by GitHub

And here's the result.

Let's recap what we defined in the PostsQL project – PostType and AuthorType. Although both contain everything we want, the GraphQL server only returns what client requests. That's how GraphQL works. I also wonder how the query request works in a browser. Open a developer tool, and you will find out the request details like:

With this information, we don't have to rely on the UI. Let's send an API request through Postman.

{
"query": "query { post(id: 3) { id, title, slug, author { id, name } } posts { id, title, published } }",
"variables": null
}
view raw 21-request.json hosted with ❤ by GitHub

It works as expected.


So far, we have built an ASP.NET Core server for GraphQL. As many libraries exist in the ecosystem, we were able to build the server easily. I hope this post will give you a high-level idea of building GraphQL server on ASP.NET Core. Let's wrap an existing REST API with GraphQL in the next post.