Categories: ASP.NET CoreGraphQL

Introduction to GraphQL and Designing Simple Query API with ASP.NET Core 2.0

Hi, all.

I couldn’t find enough time to write a new article for a few months. In fact, I wrote some part of this article in August, but I could not complete it. 🙂 Finally, I did!

Anyway, I guess GraphQL (at the same time ASP.NET Core 2.0) is among the most popular topics of recent times on data access and query operations. Against the rapidly developing business needs of today, GraphQL especially provides us the ability to develop applications rapidly and efficiently by leaving the data access and advanced query operations to the client-side.

First, is new for me. I don’t have any production experience on it yet. But, especially for our mobile API‘s, we’re planning to use data access and query operations with GraphQL as a gateway. (Currently, we’re using it at test phase.)

In this article, I’ll try to show based on my some experience how we can perform query operations efficiently in the ASP.NET Core 2.0 using the GraphQL.

So, Why GraphQL?

Before I answer the why GraphQL question, I want to mention a topic. A lot of people say REST will die and GraphQL will be the new generation. I personally don’t agree with this idea. My idea is REST and GraphQL shouldn’t be confused with each other.

First, it is possible to use both REST and GraphQL together. In addition, if GraphQL is used at the right time and right business needs, it provides the data access and querying flexibility to the clients. (Especially for the mobile side)

Now, let’s talk about the why GraphQL question. In today’s technologies, it is obvious that the usage rate of mobile applications is quite high. Usually most online users were almost mobile users, at the companies I have worked with. If we look at the development side, we use APIs that form the building blocks of our applications for both web and mobile sides.

At this point, we’ll face some other challenges on mobile side relative to size of data we receive, battery consumption and network traffic. Usually, a quick workaround is to choose to implement a Gateway API to perform aggregation operations in there instead. Unfortunately, story goes on and now we face versioning and backward compatibility issues as we implement new features.

There are some advantages GraphQL provides to us, such as:

  • It provides the ability to retrieve the data with a single request to clients
  • It reduces the need for tailored endpoints such as API gateways which doing data aggregation operations
  • Most importantly, the teams are able to develop rapidly and easily

In addition to rapidly and easily development topic, development teams of many companies are divided into small domains, and each team develops the application in their own responsibilities. It was like this at the companies I work with.

Let’s think, we need product detail of the id “1“. Normally, in a RESTful API, we can retrieve the product detail with a GET request from the endpoint “api/products/1“. But, the mobile team needs to the just “name“, “description” and “price” fields on the product detail, and in addition to that, also needs to the “id” and “name” fields on the product category detail.

Usually, we have two different options we can do.

  1. We can do two different requests – roundtrips! (I don’t think so we can choose to do roundtrips while the topic is about the mobile side)
  2. Or we can create a tailored endpoint

In addition to this, if each team has its own sprints, at this point, the mobile team will also wait for the other team which are responsible from the development of the Product API. In this age of technology, of course, we want to add new features quickly, isn’t it?

In this case, if the GraphQL is used, the mobile team will be able to retrieve the requested data, with a single request efficiently and to develop feature rapidly without the depending on the other team.

Let’s look at the following sample GraphQL query to retrieve product and category details:

{
  product(id: 1){
    name
    description
    price
    category{
      id
      name
    }
  }
}

The client can retrieve the requested data easily with a single query as the above.

NOTE: Of course, we can provide these operations with a RESTful API. (Querying, filtering, sorting, etc…) But, the requested features always should be developed and implemented in the RESTful API or handled by the client side. (Response aggregations etc…) At this point, while the GraphQL comes into prominence, we shouldn’t forget that GraphQL needs to have a correct mapping structure to be able to do what we want.

Implementation in ASP.NET Core 2.0

Now we’ll design a basic sample API that includes the “product” and “category” for the implementation phase.

First let’s create a new ASP.NET Corewebapi” project with the following code line.

mkdir aspnetcoregarphql
dotnet new webapi

After creation of the project, create a new folder called “Models” and add “Category” and “Product” models into the that.

namespace aspnetcoregraphql.Models
{
    public class Category
    {
        public int Id { get; set; }
        public string Name { get; set; }

        List<Product> Products { get; set; }
    }
}
namespace aspnetcoregraphql.Models
{
    public class Product
    {
        public int Id { get; set; }
        public int CategoryId { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public double Price { get; set; }

        Category Category { get; set;}
    }
}

And now, we need to create a “Data” folder, and add “ICategoryRepository” and “IProductRepository” interfaces into the that.

namespace aspnetcoregraphql.Data
{
    public interface ICategoryRepository
    {
        Task<List<Category>> CategoriesAsync();
        Task<Category> GetCategoryAsync(int id);
    }
}
namespace aspnetcoregraphql.Data
{
    public interface IProductRepository
    {
        Task<List<Product>> GetProductsAsync();
        Task<List<Product>> GetProductsWithByCategoryIdAsync(int categoryId);
        Task<Product> GetProductAsync(int id);
    }
}

Let’s implement these interfaces with a few sample data like as below.

namespace aspnetcoregraphql.Data
{
    public class CategoryRepository : ICategoryRepository
    {
        private List<Category> _categories;

        public CategoryRepository()
        {
            _categories = new List<Category>{
                new Category()
                {
                    Id = 1,
                    Name = "Computers"
                },
                new Category()
                {
                    Id = 2,
                    Name = "Mobile Phones"
                }
            };
        }

        public Task<List<Category>> CategoriesAsync()
        {
            return Task.FromResult(_categories);
        }

        public Task<Category> GetCategoryAsync(int id)
        {
            return Task.FromResult(_categories.FirstOrDefault(x => x.Id == id));
        }
    }
}
namespace aspnetcoregraphql.Data
{
    public class ProductRepository : IProductRepository
    {
        private List<Product> _products;

        public ProductRepository()
        {
            _products = new List<Product>{
                new Product()
                {
                    Id = 1,
                    CategoryId = 1,
                    Name = "Apple Macbook Pro 2016",
                    Description = "Touchbar, 3.2GHZ",
                    Price = 5000
                },
                new Product()
                {
                    Id = 2,
                    CategoryId = 1,
                    Name = "Apple Macbook Air",
                    Description = "2.3GHZ 8GB RAM",
                    Price = 2000
                },
                new Product()
                {
                    Id = 3,
                    CategoryId = 1,
                    Name = "Dell XPS 13",
                    Description = "3.3GHZ 12GB RAM",
                    Price = 4000
                }
            };
        }

        public Task<Product> GetProductAsync(int id)
        {
            return Task.FromResult(_products.FirstOrDefault(x => x.Id == id));
        }

        public Task<List<Product>> GetProductsAsync()
        {
            return Task.FromResult(_products);
        }
       
        public Task<List<Product>> GetProductsWithByCategoryIdAsync(int categoryId)
        {
            return Task.FromResult(_products.Where(x => x.CategoryId == categoryId).ToList());
        }
    }
}

We defined the models and repositories, now we can start to implementation .NET client of GraphQL.

Let’s include the GraphQL package from the NuGet to the project with the following code line.

dotnet add package GraphQL

After including the package to the project, firstly we will create Object Types of GraphQL. Object types and fields are the main components of GraphQL schema. This means it specifies which object we can get through the service that we will create and what fields it has.

Now we will create two ObjectGraphType into the “Models” folder, and these are “CategoryType” and “ProductType” classes.

namespace aspnetcoregraphql.Models
{
    public class CategoryType : ObjectGraphType<Category>
    {
        public CategoryType(IProductRepository productRepository)
        {
            Field(x => x.Id).Description("Category id.");
            Field(x => x.Name, nullable: true).Description("Category name.");

            Field<ListGraphType<ProductType>>(
                "products", 
                resolve: context => productRepository.GetProductsWithByCategoryIdAsync(context.Source.Id).Result.ToList()
            );
        }
    }
}
namespace aspnetcoregraphql.Models
{
    public class ProductType : ObjectGraphType<Product>
    {
        public ProductType(ICategoryRepository categoryRepository)
        {
            Field(x => x.Id).Description("Product id.");
            Field(x => x.Name).Description("Product name.");
            Field(x => x.Description, nullable: true).Description("Product description.");
            Field(x => x.Price).Description("Product price.");

            Field<CategoryType>(
                "category", 
                resolve: context => categoryRepository.GetCategoryAsync(context.Source.CategoryId).Result
             );
        }
    }
}

If we look at the two classes, each class derived from the “ObjectGraphType” class. In here, we defined the requested fields using the fluent methods of GraphQL library. And then, we defined the resolve functions for the relations.

Let’s create a root type to use the query operations. For that, create a new class called “EasyStoreQuery“.

namespace aspnetcoregraphql.Models
{
    public class EasyStoreQuery : ObjectGraphType
    {
        public EasyStoreQuery(ICategoryRepository categoryRepository, IProductRepository productRepository)
        {
            Field<CategoryType>(
                "category",
                arguments: new QueryArguments(
                    new QueryArgument<NonNullGraphType<IntGraphType>> {Name = "id", Description = "Category id"}
                ),
                resolve: context => categoryRepository.GetCategoryAsync(context.GetArgument<int>("id")).Result
            );

            Field<ProductType>(
                "product",
                arguments: new QueryArguments(
                    new QueryArgument<NonNullGraphType<IntGraphType>> {Name = "id", Description = "Product id"}
                ),
                resolve: context => productRepository.GetProductAsync(context.GetArgument<int>("id")).Result
            );
        }
    }
}

In the “EasyStoreQuery” root type, we configured the schema like in the “CategoryType” and “ProductType“. By the way, with the “arguments” parameter, we have also specified the query arguments.

NOTE: Instead of configuring the category or product repositories as the source of the “resolve” function in the “CategoryType“, “ProductType” and “EasyStoreQuery“, we could configure the REST endpoints.

Okay, now we can create the schema which will describe the structure of the data. First, let’s create a new class called “EasyStoreSchema“, and then derive it from the “Schema” class.

namespace aspnetcoregraphql.Models
{
    public class EasyStoreSchema : Schema
    {
        public EasyStoreSchema(Func<Type, GraphType> resolveType)
            :base(resolveType)
        {
            Query = (EasyStoreQuery)resolveType(typeof(EasyStoreQuery));
        }
    }
}

There are two types of schema has. First one is the “Query” and the other one is “Mutation” type. At this point, we used the only “Query” type. When we inject the “EasyStoreSchema” class, we will pass the “EasyStoreQuery” type as a parameter.

Now there are two last things that we have to do. First one is to prepare GraphQL endpoint, another one is to perform service injection operations. So, let’s create a request class called “GraphQLQuery” into the “Models” folder as below.

namespace aspnetcoregraphql.Models
{
    public class GraphQLQuery
    {
        public string OperationName { get; set; }
        public string NamedQuery { get; set; }
        public string Query { get; set; }
        public string Variables { get; set; }
    }
}

After creating the request object, we can prepare the controller. Therefore, create a “GraphQLController” class as following below.

namespace aspnetcoregraphql.Controllers
{
    [Route("graphql")]
    public class GraphQLController : Controller
    {
        private readonly IDocumentExecuter _documentExecuter;
        private readonly ISchema _schema;

        public GraphQLController(IDocumentExecuter documentExecuter,ISchema schema)
        {
            _documentExecuter = documentExecuter;
            _schema = schema;
        }

        [HttpPost]
        public async Task<IActionResult> Post([FromBody]GraphQLQuery query)
        {
            if (query == null) { throw new ArgumentNullException(nameof(query)); }

            var executionOptions = new ExecutionOptions { Schema = _schema, Query = query.Query };

            try
            {
                var result = await _documentExecuter.ExecuteAsync(executionOptions).ConfigureAwait(false);

                if (result.Errors?.Count > 0)
                {
                    return BadRequest(result);
                }

                return Ok(result);
            }
            catch(Exception ex)
            {
                return BadRequest(ex);
            }
        }
    }
}

First, we specified the endpoint using the route attribute and then injected the “IDocumentExecuter” and “ISchema” interfaces. At this point, the “IDocumentExecuter” will execute the “ExecutionOptions” parameter to create the data that the client wants. Btw, client-driven application development looks like great, isn’t it?

Now, the last thing is the dependency injection. Let’s inject the services in the “Startup” class as follows.

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    services.AddScoped<EasyStoreQuery>();   
    services.AddTransient<ICategoryRepository, CategoryRepository>();
    services.AddTransient<IProductRepository, ProductRepository>();   
    services.AddScoped<IDocumentExecuter, DocumentExecuter>();
    services.AddTransient<CategoryType>();
    services.AddTransient<ProductType>();
    var sp = services.BuildServiceProvider();
    services.AddScoped<ISchema>(_ => new EasyStoreSchema(type => (GraphType) sp.GetService(type)) {Query = sp.GetService<EasyStoreQuery>()});
}

That’s all.

Okay, let’s test it! We need to start the project using “dotnet run” command and a few tests.

NOTE: I used the “Postman” to an API call.

Let’s POST the following query to the GraphQL endpoint and see the result.

{ 
 "query":
  "query{
     category(id:1){
       id 
       name
     }
   }"
}

If we look at the response, we can see the “id” and “name” fields of the category “1” as we see below.

So, what if we want to get products of this category with the “id“, “name” and “price” fields? It’s easy! All we have to do is modify our query as follows.

{ 
 "query":
  "query{
     category(id:1){
       id 
       name
       products{
        id
        name
        price
       }
     }
   }"
}

And the response is:

Conclusion

I really enjoyed while playing GraphQL for PoC. At this point, my opinion is, if our database schema and APIs design are suitable for use as a resource, GraphQL can be a good choice for the client-driven application development. On the other hand, GraphQL provides to reduce the size of the data between the server and the client.

Sample project: https://github.com/GokGokalp/ASP.NET-Core-2.0-GraphQL-Sample

Referanslar

https://github.com/graphql-dotnet/graphql-dotnet

http://graphql.org/learn/

Gökhan Gökalp

View Comments

  • Paylaşımız için teşekkür ederim ama kafamda oturmayan şeyler var :)
    GraphQL kullanarak web service üzerinde herhangi bir değişiklik yapmadan hızlıca bir query oluşturup istediğimiz columnları tanımlayarak data getirme işlemleri yapabiliyoruz, bu işlem bize ortam bazında avantajlar sağlıyor(response data size or extra columns).

    Peki burada bir spesifik bir filter vermek istediğimizde web service burada nasıl davranıyor. Örneğin şurada bir örnek var; https://www.howtographql.com/graphql-js/10-filtering/
    Queryde belirttiğimiz filter tanımları bir repository aracılığıyla db seviyesindemi filter yapıyor yoksa repositoryde örneğin GetProducts() methodundan dönen liste içerisindemi bir filter uyguluyor?

    Bu konuda biraz detay verebilir misiniz? Artı ve eksileri varmıdır?

    • Merhaba, teşekkür ederim öncelikle yorumunuz için. Bu işlemler aslında sizin ilgili source'u ve query argument'ları nasıl tanımladığınıza göre biraz değişiklik gösteriyor. Hatta relation'ların resolve kısımlarına yönelik bunun üzerine tartışılmış bir çok N+1 query sorusu, query optimizasyon vb. gibi çözümleri de mevcut. (Benim gördüğüm) Benim yapmış olduğum basit örnekte "id" argument'ı ile ilerlediğim için bir filtering söz konusu değil db tarafında, in-memory gerçekleşmektedir. Buradaki query argument'larını çoğaltabilir ve ya kendi query argument'larımız için graph type'lar oluşturabilir ve spesifik filter query'leri geçebilerek, predicate'ler ile de bir sorgu oluşturabiliriz db'ye hit etmeden önce veya ilgili servis'in bir filter endpoint'i varsa. Bu kullanmış olduğum .NET client'ı hala geliştirilmekte olan bir paket, ayrıca benim için de baya yeni bir konu süredir üzerinde çalışıyorum bu tarz concern'lere karşı, deneyimlerimi buradan paylaşmaya devam edeceğim.

      • Terimler arasında kaybolup gittim ama konuyla ilgili yeni makalelerinizi merakla bekliyorum :)
        Ama özetle istemciden sunucudaki ilgili endpointe gelen request içerisindeki filtreyi bir convert işlemiyle ilgili orm tool yada data çekmek için kullandığımız data toolu ne ise, onun filter yapısına uydurup data çekmek işi çözecektir diye anlıyorum. Tabi bu convert işlemindeki pradicateleri uyumlu bir şekilde otomatik çeviren bir modül yoksa durum biraz vahim olabilir.

        • Merhaba tekrar, evet. :) Örnek vermek gerekirse eğer: "EasyStoreQuery" içerisindeki argument'lere bakarsak, "new QueryArgument {Name = "id", Description = "Category id"}" şeklinde bir id parametresi ekliyoruz aslında. GraphQL'in .NET client'ı ile buraları şekillendirmek bize kalıyor biraz. :) Bende üzerinde çalışmaktayım halen, ilerleyen bölümlerde paylaşmaya devam edeceğim edinebildiğim farklı tecrübeleri. :)

  • Paylaşım için teşekkürler güzel açıklama. Ben yinede grapql in neden kullanılması gerektiğini tam anlayamadım. Linq le çekip filtring yapmamızdan bir farkı yok gibi görünnüyor. Belki burda linq expressioni direk mobilden geliyomuş gibi düşünebiliriz. Buda mobilci için backend geliştirme beklemesini azaltır gibi.

    • Hi Gin, What you mean by "a list Products"? We already return product list with the following query:

      {
      "query":
      "query{
      category(id:1){
      id
      name
      products{
      id
      name
      price
      }
      }
      }"
      }

      If you want to return a product list without category, you should implement it in "EasyStoreQuery" query class without "id" parameter. Then you can be querying.

  • I just listened to .netrocks podcast on graphql where it was mentioned that graphql never returns any other status than 200. Can you confirm and if required correct the code for error handling?

  • Merhaba, Öncelikle makale için teşekkür ederiz. Bu projeyi asp.net web api frameworkü ile çalıştıramadım.

    GraphQLController içerisinde Constructor içerisinde schema injection'ının nasıl yapmalıyız. Teşekkürler.
    var schema = new Schema { Query = new EasyStoreSchema(new Func(CategoryType))};

    • Merhaba ne tarz bir hata alıyorsunuz? Injection derken? Tanımlamasını Startup'da şu şekilde gerçekleştirdik makale için: services.AddScoped(_ => new EasyStoreSchema(type => (GraphType) sp.GetService(type)) {Query = sp.GetService()});

  • Merhabalar, Hangi sorguyu çalıştırmak istersem istiyim aşağıdaki gibi hata alıyorum.

    Sorgu :
    {
    "query":
    "query{
    category(id:1){
    Id
    name
    }
    }"
    }

    Aldığım hata :
    {
    "errors": [
    {
    "message": "Syntax Error GraphQL (2:2) Expected Name, found String \"query\"\n1: { \n2: \"query\":\n ^\n3: \"query{\n"
    }
    ]
    }

    Bir sorun mu var acaba

    • Merhaba kusura bakmayın geç cevap için, spam yorumlar arasında kaybolmuş. Eğer herşeyi aynı yaptı iseniz, kullandığınız NuGet paket versiyonu, benim kullandığım ile aynı mı? Bir değişiklikler olmuş olabilir farklı versiyon ise.

    • Merhaba evet, kullandığım editör'den kaynaklı. Bir çok kod'da > gibi semböller de otomatik siliniyor kullandığım multi-language editör'ünden kaynaklı. Çözemedim bir türlü.

Recent Posts

Securing the Supply Chain of Containerized Applications to Reduce Security Risks (Security Scanning, SBOMs, Signing&Verifying Artifacts) – Part 1

{:tr}Bildiğimiz gibi modern yazılım geliştirme ortamında containerization'ın benimsenmesi, uygulamaların oluşturulma ve dağıtılma şekillerini oldukça değiştirdi.…

1 month ago

Delegating Identity & Access Management to Azure AD B2C and Integrating with .NET

{:tr}Bildiğimiz gibi bir ürün geliştirirken olabildiğince farklı cloud çözümlerinden faydalanmak, harcanacak zaman ve karmaşıklığın yanı…

5 months ago

How to Order Events in Microservices by Using Azure Service Bus (FIFO Consumers)

{:tr}Bazen bazı senaryolar vardır karmaşıklığını veya eksi yanlarını bildiğimiz halde implemente etmekten kaçamadığımız veya implemente…

1 year ago

Providing Atomicity for Eventual Consistency with Outbox Pattern in .NET Microservices

{:tr}Bildiğimiz gibi microservice architecture'ına adapte olmanın bir çok artı noktası olduğu gibi, maalesef getirdiği bazı…

1 year ago

Building Microservices by Using Dapr and .NET with Minimum Effort – 02 (Azure Container Apps)

{:tr}Bir önceki makale serisinde Dapr projesinden ve faydalarından bahsedip, local ortamda self-hosted mode olarak .NET…

1 year ago

Some Awesome News of .NET 7

{:tr}Bildiğimiz gibi .NET Conf 2022, 8-10 kasım arasında gerçekleşti. Konferans sırasında ise .NET 7 ve…

1 year ago