Skip to content

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/

Published inASP.NET CoreGraphQL

22 Comments

  1. Kerim Kerim

    Güzel paylaştım. Teşekkürler

  2. Hüseyin Hüseyin

    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.

      • Hüseyin Hüseyin

        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. 🙂

  3. son son

    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.

  4. praneet praneet

    Thanks ! Great post. Exactly what I was looking for.

  5. Gin Gin

    So how to return a list Products?

    • 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.

  6. Paul Paul

    Great write up and project. Thank you for your time explaining this.

  7. Hemant Sathe Hemant Sathe

    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?

  8. Cem Cem

    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()});

  9. Sinan ACAR Sinan ACAR

    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.

  10. Yılmaz Karaağaç Yılmaz Karaağaç

    Kod paylaşılan yerlerde “” karakterlerinde eksiklikler var.
    Bilginize.

    • Yılmaz Karaağaç Yılmaz Karaağaç

      küçüktür veya büyüktür karakterlerinde

    • 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ü.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.