Developing API In .NET Core With GraphQL

Introduction 

 
GraphQL is a query language for APIs which provides a more efficient and flexible alternative to REST. It was originally built by Facebook but it’s open-sourced and is maintained by a large community.
 
To design web APIs, Restful architecture has become the standard over a couple of years. However, REST APIs have shown to be too inflexible to keep up with the rapidly changing requirements of the clients that access them.
 
To solve many of the shortcomings and inefficiencies that developers experience when interacting with REST APIs, GraphQL was developed to cope with the need for more flexibility and efficiency. So, GraphQL is nothing but a better REST.
 
In REST, we generally have multiple endpoints to perform CRUD operations on an entity and also there are overfetching and underfetching problems and it’s return fixed data structures which is a little inflexible in nature.
  • Overfetching means that endpoints return additional information that’s not needed in the UI.
  • Underfetching means that a specific endpoint doesn’t provide enough of the required information. The client will have to make an additional call to fetch everything it needs.
Let’s consider TechEvent as an entity - A tech event can have multiple participants and similarly, a participant can enroll in multiple tech events.Here you need to call 3 endpoints to fetch the required data in REST.
  1. GET/api/techEvents => To get all tech events
  2. GET/api/techEvents/{id} => To get single tech event info by id
  3. GET/api/techEvents/{id}/participants => To get participants details for an event
But using GraphQL, a single endpoint POST/graphql will be sufficient to archive this data requirement instead of 3 different endpoints and we  can do it by just modifying query as like below.
  1. {       
  2.  "query":      
  3.   "query{      
  4.      event(eventId:1){      
  5.        eventId       
  6.        speaker      
  7.      }      
  8.    }"      
  9. }     
  1. {     
  2.  "query":    
  3.   "query{    
  4.      events{    
  5.        eventId    
  6.        eventName    
  7.        participants{    
  8.             participantName    
  9.             phone    
  10.         }    
  11.      }    
  12.    }"    
  13. }     
Below Illustration on Rest API vs GraphQL with respect to endpoints,
 
 

Implementation of GraphQL with ASP.Net Core

 
 In this article, we're going to build a GraphQL server from scratch. The server will expose a TechEvent management graph QL endpoint which will allow querying on data. 
 For demo purposes, let’s create a local DB called “TechEventDB” in MS SQL server (or you can use InMemoryDatabase) and add below tables with some dummy data,
  1. Table - TechEventInfo: This table consists of technical event information like event ID, name, speaker, etc.
  2. Table - Participant: This table consists of participant information like name, phone, email, etc.
  3. Table - EventParticipants: This is a mapping table b/w event and participants with many to many relationships.
  1. CREATE TABLE dbo.TechEventInfo(    
  2.     EventId int IDENTITY(1,1) NOT NULL,    
  3.     EventName varchar(100) NOT NULL,    
  4.     Speaker varchar(100) NOT NULL,    
  5.     EventDate Date NULL  
  6.  CONSTRAINT [PK_TechEventInfo] PRIMARY KEY CLUSTERED     
  7. (    
  8.     EventId ASC    
  9. )  
  10. WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ONON [PRIMARY]    
  11. ON [PRIMARY]  
  1. CREATE TABLE dbo.Participant(    
  2.     ParticipantId int IDENTITY(1,1) NOT NULL,    
  3.     ParticipantName varchar(100) NOT NULL,    
  4.     Email varchar(100) NOT NULL,    
  5.     Phone varchar(20) NULL,  
  6.  CONSTRAINT [PK_Participant] PRIMARY KEY CLUSTERED     
  7. (    
  8.     ParticipantId ASC    
  9. )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ONON [PRIMARY]    
  10. ON [PRIMARY]  
  1. CREATE TABLE [dbo].EventParticipants  
  2. (  
  3.     EventParticipantId INT NOT NULL IDENTITY(1,1) ,  
  4.         EventId INT NOT NULL,  
  5.     ParticipantId INT NULL,  
  6.     CONSTRAINT [PK_EventParticipants] PRIMARY KEY CLUSTERED  
  7.     (  
  8.         EventParticipantId ASC  
  9.     )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)   
  10.     ON [PRIMARY]  
  11. ,CONSTRAINT [FK_EventParticipants_TechEventInfo_EventId] FOREIGN KEY (EventId) REFERENCES [TechEventInfo] (EventId)   
  12. ,CONSTRAINT [FK_EventParticipants_Participant_ParticipantId] FOREIGN KEY ([ParticipantId]) REFERENCES [Participant] ([ParticipantId])   
  13. ON [PRIMARY]  
Now we have to insert some sample data into all the tables.
 
Once we are done with these steps, we can create an ASP.NET core WebAPI project called “GraphQL.API” and add a DB connection string in appsettings.json.
  1. "ConnectionStrings": {  
  2.     "GraphQLDBConnection""Server=Anupam-PC\\SQLEXPRESS;Database=TechEventDB;Trusted_Connection=True;MultipleActiveResultSets=true"  
  3.   }  
Add the DbContext under ConfigureServices in Startup.cs
  1. services.AddDbContext<TechEventDBContext>  
  2.                 (options => options.UseSqlServer(Configuration.GetConnectionString("GraphQLDBConnection")));  
To generate entity context class, now we need to run scaffold command from package manager console (Tools => Nuget Package Manager => Package Manager Console) to generate dbContext class using Entity Framework Core. Create folder called Infrastructure and sub folder called DBContext into the project.
  1. Scaffold-DbContext “Server=Anupam-PC\SQLEXPRESS;Database=GraphQLDemo;Trusted_Connection=True;” Microsoft.EntityFrameworkCore.SqlServer -OutputDir Infrastructure\DBContext  
After successful execution of Scaffold command, entity and context will be added as like below.
 
Developing API In .NET Core With GraphQL
 
Now create a Repositories folder where we can handle CRUD operation on tech event entity.
 
ITechEventRepository.cs
  1. using GraphQL.API.Infrastructure.DBContext;  
  2. using System.Collections.Generic;  
  3. using System.Threading.Tasks;  
  4.   
  5. namespace GraphQL.API.Infrastructure.Repositories  
  6. {  
  7.     public interface ITechEventRepository  
  8.     {  
  9.         Task<TechEventInfo[]> GetTechEvents();  
  10.         Task<TechEventInfo> GetTechEventById(int id);  
  11.         Task<List<Participant>> GetParticipantInfoByEventId(int id);  
  12.     }  
  13. }  
TechEventRepository.cs
  1. using GraphQL.API.Infrastructure.DBContext;  
  2. using Microsoft.EntityFrameworkCore;  
  3. using System;  
  4. using System.Collections.Generic;  
  5. using System.Linq;  
  6. using System.Threading.Tasks;  
  7. using GraphQL.API.Domain;  
  8.   
  9.   
  10. namespace GraphQL.API.Infrastructure.Repositories  
  11. {  
  12.     /// <summary>  
  13.     /// TechEventRepository.  
  14.     /// </summary>  
  15.     public class TechEventRepository : ITechEventRepository  
  16.     {  
  17.   
  18.         /// <summary>  
  19.         /// The _context.  
  20.         /// </summary>  
  21.         private readonly TechEventDBContext _context;  
  22.   
  23.         public TechEventRepository(TechEventDBContext context)  
  24.         {  
  25.             this._context = context;  
  26.         }  
  27.   
  28.         public async Task<List<Participant>> GetParticipantInfoByEventId(int id)  
  29.         {  
  30.             return await(from ep in this._context.EventParticipants  
  31.                                  join p in this._context.Participant on ep.ParticipantId equals p.ParticipantId  
  32.                                  where ep.EventId == id  
  33.                                  select p).ToListAsync();  
  34.         }  
  35.   
  36.         public async Task<TechEventInfo> GetTechEventById(int id)  
  37.         {  
  38.             return await Task.FromResult( _context.TechEventInfo.FirstOrDefault(i => i.EventId == id));  
  39.         }  
  40.   
  41.         public async Task<TechEventInfo[]> GetTechEvents()  
  42.         {  
  43.             return _context.TechEventInfo.ToArray();  
  44.         }  
  45.     }  
  46. }  
Register DI on Startup.cs for Repository:
  1. services.AddTransient<ITechEventRepository, TechEventRepository>();  
So far, we've created our DB context and repository. It's time to start using GraphQL integration in our project, so we can get our GraphQL server running.
 
Add the below NuGet packages into the API project:
  1. <PackageReference Include="GraphQL" Version="2.4.0" />  
  2. <PackageReference Include="graphiql" Version="1.2.0" />  
  3. <PackageReference Include="GraphQL.Server.Transports.AspNetCore" Version="3.4.0" />  
First, we're going to configure the GraphQL schema step by step as mentioned below:
 
Step 1 - Create GraphQL types
 
To admit through our endpoint to our GraphQL clients.
 
Step 2 - Create a GraphQL query object
 
To allow us to define the queries that will then surface to the clients which they can then issue against our endpoint.
 
Step 3 - Create a schema object to expose the query
 
To allow tools like graphical to inspect and see what types are available. It's also necessary to enable GraphQL clients.
 
Step 4 - Register all types in Startup.cs
 
To register all of those types with our ASP.NET core IOC container.
 
Add a folder into a project called “GraphqlCore” – where we will keep GraphQL related Type, Schema, Query files, etc.
 

Create GraphQL types

 
 Create TechEventInfoType.cs which inherits from ObjectGraphType<TechEventInfo> and set all the fields into the constructor.
  • ObjectGraphType <T>: Used to expose GraphQL types in our schema, “T” here is model.
  • ListGraphType <T>: Used to define GraphQL Collection
  1. using GraphQL.API.Infrastructure.DBContext;  
  2. using GraphQL.API.Infrastructure.Repositories;  
  3. using GraphQL.Types;  
  4.   
  5. namespace GraphQL.API.GraphqlCore  
  6. {  
  7.     public class TechEventInfoType : ObjectGraphType<TechEventInfo>  
  8.     {  
  9.         public TechEventInfoType(ITechEventRepository repository)  
  10.         {  
  11.             Field(x => x.EventId).Description("Event id.");  
  12.             Field(x => x.EventName).Description("Event name.");  
  13.             Field(x => x.Speaker).Description("Speaker name.");  
  14.             Field(x => x.EventDate).Description("Event date.");  
  15.   
  16.             Field<ListGraphType<ParticipantType>>(  
  17.               "participants",  
  18.               arguments: new QueryArguments(new QueryArgument<IntGraphType> { Name = "eventId" }),  
  19.               resolve: context => repository.GetParticipantInfoByEventId(context.Source.EventId)  
  20.            );  
  21.         }  
  22.     }
  23. }  
Here, Field() is nothing but used to expose to client If you have any specific domain property and that you don’t want to expose – don’t need to add into the constructor. Also, we can set a description of each field. Those descriptions will actually populate back to the client when they use graphical and they view the schema.
 
In this TechEventInfoType, we added Field called “participants” as we want to expose participants along with events and Field will be like
 
Field<ListGraphType<ParticipantType>> (Name, Arguments, Resolve Func)
 
Name (i.e. “participants”) is basically the name of the field. Arguments (e.g. “eventId”) are nothing but used as input to resolver function. Resolve function is going to get invoked in GetParticipantInfoByEventId() to retrieve participants from repository.
 
Similarly, for Participants we need to create ParticipantType.cs
  1. using GraphQL.API.Infrastructure.DBContext;  
  2. using GraphQL.Types;  
  3.   
  4. namespace GraphQL.API.GraphqlCore  
  5. {  
  6.     public class ParticipantType : ObjectGraphType<Participant>  
  7.     {  
  8.         public ParticipantType()  
  9.         {  
  10.             Field(x => x.ParticipantId).Description("Participant id.");  
  11.             Field(x => x.ParticipantName).Description("Participant name.");  
  12.             Field(x => x.Email).Description("Participant Email address.");  
  13.             Field(x => x.Phone).Description("Participant phone number.");  
  14.         }  
  15.     }  
  16. }  

Create a GraphQL query object

 
 Now we're going to create our query which will be exposed to the client.
  1. Return a TechEventInfo based on Id.
  2. Return a list of TechEventInfo.
Here, ObjectGraphType<object> we're just going to put object and that's because we don't have an existing class that we're mapping to.
  1. using GraphQL.API.Infrastructure.Repositories;  
  2. using GraphQL.Types;  
  3.   
  4.   
  5. namespace GraphQL.API.GraphqlCore  
  6. {  
  7.     public class TechEventQuery : ObjectGraphType<object>  
  8.     {  
  9.         public TechEventQuery(ITechEventRepository repository)  
  10.         {  
  11.             Name = "TechEventQuery";  
  12.   
  13.             Field<TechEventInfoType>(  
  14.                "event",  
  15.                arguments: new QueryArguments(new QueryArgument<IntGraphType> { Name = "eventId" }),  
  16.                resolve: context => repository.GetTechEventById(context.GetArgument<int>("eventId"))  
  17.             );  
  18.   
  19.             Field<ListGraphType<TechEventInfoType>>(  
  20.              "events",  
  21.              resolve: context => repository.GetTechEvents()  
  22.           );  
  23.         }  
  24.     }  
  25. }  

Create a schema object to expose the query

 
 The schema will be used to resolve the query. We will resolve the previously created query; i.e., TechEventQuery.cs and dependency resolver is going to be necessary in order for dependency injection to work.
  
  1. using GraphQL.Types;  
  2. using System;  
  3. using System.Collections.Generic;  
  4. using System.Linq;  
  5. using System.Threading.Tasks;  
  6.   
  7. namespace GraphQL.API.GraphqlCore  
  8. {  
  9.     public class TechEventSchema : Schema  
  10.     {  
  11.         public TechEventSchema(IDependencyResolver resolver)  
  12.         {  
  13.             Query = resolver.Resolve<TechEventQuery>();  
  14.             DependencyResolver = resolver;  
  15.         }  
  16.   
  17.     }  
  18. }  
Register all types in Startup.cs
 
Now start registering the types, query and Schema that we created so far. To do this, add the below code snippet under configureservices in startup.cs.
  1. services.AddSingleton<TechEventInfoType>();  
  2. services.AddSingleton<ParticipantType>();  
  3. services.AddSingleton<TechEventQuery>();  
  4. var sp = services.BuildServiceProvider();  
  5. services.AddSingleton<ISchema>(new TechEventSchema(new FuncDependencyResolver(type => sp.GetService(type))));  
Then before the app.UseMvc(); add the following line in the configure method:
  1. app.UseGraphiQl("/graphql");  

Web API Controller

 
 You can now create a GraphQLController with a single action method/endpoint that handles every incoming query that our schema supports. The client will always be sending the POST request which will contain the query name, operation name, and the variables. You can create a new class called GraphqlQuery.cs that will serve as a model for all queries from the client.
 
GraphqlQuery.cs
  1. using Newtonsoft.Json.Linq;  
  2.   
  3. namespace GraphQL.API.GraphqlCore  
  4. {  
  5.   public class GraphqlQuery  
  6.     {  
  7.     public string OperationName { getset; }  
  8.     public string NamedQuery { getset; }  
  9.     public string Query { getset; }  
  10.     public JObject Variables { getset; }  
  11.   }  
  12. }  
GraphQLController.cs
  1. using System.Threading.Tasks;  
  2. using GraphQL.API.GraphqlCore;  
  3. using GraphQL.Types;  
  4. using Microsoft.AspNetCore.Mvc;  
  5.   
  6.   
  7. namespace GraphQL.API.Controllers  
  8. {  
  9.     [Route("[controller]")]  
  10.     [ApiController]  
  11.     public class GraphQLController : ControllerBase  
  12.     {  
  13.         private readonly IDocumentExecuter _documentExecuter;  
  14.         private readonly ISchema _schema;  
  15.         public GraphQLController(ISchema schema, IDocumentExecuter documentExecuter)  
  16.         {  
  17.             _schema = schema;  
  18.             _documentExecuter = documentExecuter;  
  19.         }  
  20.   
  21.         [HttpPost]  
  22.         public async Task<IActionResult> PostAsync([FromBody] GraphqlQuery query)  
  23.         {  
  24.             if (query == null) { throw new ArgumentNullException(nameof(query)); }  
  25.             var inputs = query.Variables.ToInputs();  
  26.             var executionOptions = new ExecutionOptions  
  27.             {  
  28.                 Schema = _schema,  
  29.                 Query = query.Query,  
  30.                 Inputs = inputs  
  31.             };  
  32.   
  33.             var result = await _documentExecuter.ExecuteAsync(executionOptions);  
  34.   
  35.             if (result.Errors?.Count > 0)  
  36.             {  
  37.                 return BadRequest(result);  
  38.             }  
  39.   
  40.             return Ok(result);  
  41.         }  
  42.     }  
  43. }  
Add below in startup.cs:
  1. services.AddSingleton<IDocumentExecuter, DocumentExecuter>();  
In REST, as a rule, we use the HTTP GET verb to fetch data. In GraphQL we can certainly still use GET requests for fetching data however that means we must store our "query" in the query string of the URL ie. api/graphql?query=.... A cleaner approach is to send the query in the JSON-encoded body of a POST request.
 
At the starting stage, we added a NuGet package called GraphiQL which will provide the UI where we can write our GraphQL queries and at the same time, we can see the result. For that, we need to browse the graphql path. We can configure the same at the launch of the browser.
 
Developing API In .NET Core With GraphQL
 
Now we are done with the demo project, let’s see the overall folder and file structure of it:
 
Developing API In .NET Core With GraphQL
 
Now are ready to run our demo application and test GraphQL query. Once it’s loaded in the browser, you will see below GraphiQL screen. On the left side, we have a query input section and at the right side, the result section. You can see query schema on Document Explorer section. On the left top section, you will play a button to execute your query.
 
 Developing API In .NET Core With GraphQL
 
NOTE: GraphiQL an in-browser IDE for exploring GraphQL. GraphiQL features include Syntax highlighting Intelligent type ahead of fields, arguments, types, and more. Real-time error highlighting and reporting. Automatic query completion.
 

Test GraphQL Query

 
To get event details with participants by event ID, you can write the below query.
 
Developing API In .NET Core With GraphQL
 
Similarly, to get a participant name for each event:
 
Developing API In .NET Core With GraphQL
 
Whatever data you need from Techevent entity, you can easily get it without modifying any code or adding additional endpoints - which resolved over fetching and under fetching problem in REST.
 
We can test the same GraphQL API using the postman tool. The below screenshot is for your reference.
 
Developing API In .NET Core With GraphQL
 
Until now. we discussed on GraphQL query. In the next article we will talk about Mutation which is nothing but modifying the data.
 

Conclusion

 
I hope this article has helped you gain some insight into GraphQL. It’s too flexible, isn’t it? It can provide developers to building and delivering data-rich APIs over Rest. Last but not the least, there is a misconception around GraphQL is that being a database technology. GraphQL is a query language for APIs - not databases.
 
Finally, all the code demonstrated here can be found in the GitHub.