Some of the senior developers in my team have been looking into using GraphQL in the backend API for the UI. It is currently a REST API. The team believes GraphQL will provide a better experience for the frontend developers and could help with some performance. In this post, I’m going to show how we built a quick proof of concept for adding GraphQL to backend with little changes and impact. .NET 5 is the version as of the time of writing.
Existing REST API
Suppose we have a simple web API like this

It has two DTO models, BookDto and AuthorDto.
public class BookDto
{
public Guid Id { get; set; }
public string Name { get; set; }
public DateTime PublishedDate { get; set; }
}
public class AuthorDto
{
public Guid Id { get; set; }
public string GivenName { get; set; }
public string FamilyName { get; set; }
}
Let’s assume these two DTOs come from two external services (in our case, these are from two different micro services). For simplicity sake, we have two services that return some hardcoded book and author DTOs.

We also have two view models Book and Author that we return in the REST API
public class Book
{
public Guid Id { get; set; }
public string Name { get; set; }
public DateTime PublishedDate { get; set; }
public IEnumerable<Author> Authors { get; set; }
public static Book FromDto(BookDto book, IEnumerable<AuthorDto> authors)
=> new Book
{
Id = book.Id,
Name = book.Name,
PublishedDate = book.PublishedDate,
Authors = authors.Select(Author.FromDto)
};
}
public class Author
{
public Guid Id { get; set; }
public string GivenName { get; set; }
public string FamilyName { get; set; }
public static Author FromDto(AuthorDto author)
=> new Author
{
Id = author.Id,
GivenName = author.GivenName,
FamilyName = author.FamilyName
};
}
Finally, we have a BooksController that returns a book for a given GUID.

We get this result when running it

Adding GraphQL
Required Nuget packages
Firstly, we need to install the following Nuget packages:
GraphQL
GraphQL.Server.Core
GraphQL.Server.Transports.AspNetCore
GraphQL.Server.Transports.AspNetCore.SystemTextJson
GraphQL.Server.Ui.Playground
Type, Query and Schema
Next we need to add the type for our Book and Author models which are then used the in Query. The Query will then be used to define the Schema. Create a new folder named Schema and add the following files.
AuthorType.cs
public class AuthorType : ObjectGraphType<Author>
{
public AuthorType()
{
Field(x => x.Id);
Field(x => x.GivenName);
Field(x => x.FamilyName);
}
}
BookType.cs
public class BookType : ObjectGraphType<Book>
{
public BookType()
{
Field(x => x.Id);
Field(x => x.Name);
Field(x => x.PublishedDate);
FieldAsync<ListGraphType<AuthorType>>(
name: "Authors",
resolve: async ctx =>
{
var result = await ctx.RequestServices
.GetRequiredService<IAuthorService>()
.GetAuthorsByBookId(ctx.Source.Id);
return result.Select(Author.FromDto);
});
}
}
BooksQuery.cs
public class BooksQuery : ObjectGraphType<object>
{
public BooksQuery()
{
Name = "Query";
FieldAsync<BookType>(
"bookById",
arguments: new QueryArguments(new QueryArgument<NonNullGraphType<StringGraphType>> { Name = "bookId" }),
resolve: async ctx =>
{
return ViewModels.Book.FromDto(await ctx.RequestServices
.GetRequiredService<IBookService>()
.GetBook(new Guid(ctx.Arguments["bookId"].Value.ToString())), null);
});
}
}
BooksSchema.cs
public class BooksSchema : GraphQL.Types.Schema
{
public BooksSchema(BooksQuery query)
{
Query = query;
}
}
Configuring GraphQL middleware
Finally, we need to configure GraphQL middleware Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Other configurations go here ...
services.AddSingleton<BooksSchema>();
services
.AddGraphQL()
.AddSystemTextJson()
.AddGraphTypes(typeof(BooksSchema));
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Other configurations go here ...
app.UseGraphQL<BooksSchema>("/graphql");
app.UseGraphQLPlayground();
}
After adding the configuration, run the application and add /ui/playground to the URL and you should see this

If you replace the content on the left with this query and click Play, it should return the same response as the REST API above
query getBook {
bookById(bookId: "FAEBD971-CBA5-4CED-8AD5-CC0B8D4B7827") {
id
name,
authors {
givenName
}
}
}

One of the benefits of using GraphQL is if you don’t want to include Authors, it will not call the Author service (in our project this would be a HTTP request to a micro service). Whereas REST API will always call it whether the UI needs it or not.
query getBook {
bookById(bookId: "FAEBD971-CBA5-4CED-8AD5-CC0B8D4B7827") {
id
name
}
}
Other benefits are fewer roundtrips to the server, no over-fetching or under-fetching, no versioning problems, reduced bandwidth and documentation (similarly to Swagger).