Introduction:





GraphQL is a query language for your API and a server-side runtime for executing queries by using a type system you define for your data. GraphQL can be integrated into any framework like ASP.NET, Java, NestJs, etc and it isn't tied to any specific database or storage engine and is instead backed by your existing code and data.

How GraphQL API Different From Rest API:

GraphQL exposes a single end-point or route for the entire application, regardless of its responses or actions.

HTTP-POST is the only Http verb recommended by the GraphQL.

The client applications (consumers of API) can give instructions to GraphQL API about what type of properties to be returned in the response.

Building Blocks Of GraphQL API:

The main building blocks of GraphQL API is Schemas and Types.

A 'Schema' in GrpahQL API describes the functionality available to the clients connect to API. Schema mostly consists of GraphQL Object Types, Queries, Mutations, etc.

Types likely to be called GraphQL Object Types. GraphQL Object Types represents the data source types return from API. GraphQL Object Types contains 'Fields' and 'Methods'. Fields are just properties in a class, 'Methods' are used to modify or conditionally change the field values based on client query.

Create ASP.NET Core Web API Project Template:

Let's start to learn about features in GraphQL, its implementation, and its integration into the Asp.Net Core application. Now create the Asp.Net Core Web API template.

GraphQL Nuget:

Open package manager console in VisualStudio to install GraphQL Nuget.

Install-Package GraphQL -Version 2.4.0

Create Data Source Model:

Data Source Model means which represents a Table class. So add the new folder as 'Models', to that folder add a new class as 'Player.cs'

public class Player { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public int CurrentAge { get; set; } public string Teams { get; set; } public string PlayingRole { get; set; } public string BattingStyle { get; set; } public string BowlingStyle { get; set; } }

Create A Data Repository And Register To Services:

A repository is a data access layer, where communication with the database goes. So add the new folder as 'Repositories', to that folder add two files like 'IPlayerRepository' and 'PlayerRepository'.





IPlayerRepository:

using System.Collections.Generic; using GraphQLSample.Models; namespace GraphQLSample.Repositories { public interface IPlayerRepository { List<Player> GetAll(); } }

using System.Collections.Generic; using GraphQLSample.Models; namespace GraphQLSample.Repositories { public class PlayerRepository: IPlayerRepository { // later code will be updated // to get data from database public List<Player> GetAll() { return new List<Player> { new Player { Id=1, FirstName= "Rohit", LastName = "Sharma", CurrentAge = 32, PlayingRole = "Opener", BattingStyle = "Right-hand Bat", BowlingStyle="spin", Teams="India, Mumbai, Mumbai Indians" } // display purpose items hidden , while testing add more items }; } } }

services.AddScoped<IPlayerRepository, PlayerRepository>();

ObjectGraphType:

'GraphQL.Types.ObjectGraphType' class represents Types in GraphQL which we discussed as one of the core concepts in GraphQL.

class represents Types in GraphQL which we discussed as one of the core concepts in GraphQL. GraphQL is not bound to any specific language or framework, so it can not understand CSharp POCO( Plain Old CLR Object ) classes directly.

So to represent GraphQL Type, CSharp Classes need to inherit 'GraphQL.Types.ObjectGraphType'.

After inheriting ObjectGraphType in the constructor we need to register our own class properties as 'Field' types which is understandable by GraphQL.

using GraphQL.Types; using GraphQLSample.Models; namespace GraphQLSample.GraphQLTypes { public class PlayerType:ObjectGraphType<Player> { public PlayerType() { Field(_ => _.Id); Field(_ => _.FirstName); // description is an self //explanatory extenstion method which is options Field(_ => _.LastName).Description("last name of a player"); Field(_ => _.CurrentAge); Field(_ => _.Teams); Field(_ => _.PlayingRole); Field(_ => _.BattingStyle); Field(_ => _.BowlingStyle); } } }

Here we have a model 'Player' which CSharp POCO classes, which can't be understood by GraphQL directly.

To make 'Player' to GraphQL Type, we have created a new class 'PlayerType' which inherits 'GraphQL.Types.ObjectGrpahType<T>'. Type took by ObjectGraphType is 'Player' to represent it as GraphQLType.

All the properties of 'Player' class returning as 'Field' types which can be understood by GraphQL.

Schema:

Schema is one of the main building blocks of GraphQL API.

Schema mainly consists of Query, Mutation, Types, etc. Query in Schema is an ObjectGraphType, but it is like Root or Parent ObjectGraphType.

Schema Query held all other ObjectGraphType of entire application, more or less it acts as a Data Source to clients consuming GraphQL API.

Based on the client's request this Query in Schema serves data.

using GraphQL.Types; using GraphQLSample.Repositories; namespace GraphQLSample.GraphQLTypes { public class RootQuery:ObjectGraphType { public RootQuery(IPlayerRepository _playerRepository) { Field<ListGraphType<PlayerType>>("players", resolve: context => { return _playerRepository.GetAll(); }); } } }

Here all players returning under field name 'players' and Field register with 'PlayerType'(ObjectGraphType).

Similarly, all POCO classes which represent a table will have their individual ObjectGraphType(like PlayerType for Player class) and these all ObjectGraphType will be registered as Field type in RootQuery in our application.

services.AddScoped<RootQuery>();

using GraphQL; using GraphQL.Types; namespace GraphQLSample.GraphQLTypes { public class RootSchema:Schema, ISchema { public RootSchema(IDependencyResolver resolver):base(resolver) { Query = resolver.Resolve<RootQuery>(); } } }

services.AddScoped<IDependencyResolver>(_ => new FuncDependencyResolver(_.GetRequiredService)); services.AddScoped<ISchema, RootSchema>();

Create GraphQL API EndPoint:

Now create a new Web API Controller as 'GraphQLController' and the controller contains only one action method that supports Http-Post verb. In the model, folder add new file 'GraphQLQueryDto.cs' files that represent the post data payload entity.





GraphQLQueryDto.cs:

namespace GraphQLSample.Models { public class GraphQLQueryDto { public string Query { get; set; } } }

[Route("graphql")] public class GraphQLController : Controller { private readonly ISchema _schema; private readonly IDocumentExecuter _executer; public GraphQLController(ISchema schema, IDocumentExecuter executer) { _schema = schema; _executer = executer; } [HttpPost] public async Task<IActionResult> Post([FromBody] GraphQLQueryDto query) { var result = await _executer.ExecuteAsync(_ => { _.Schema = _schema; _.Query = query.Query; }).ConfigureAwait(false); if(result.Errors?.Count > 0) { return Problem(detail: result.Errors.Select(_ => _.Message).FirstOrDefault(), statusCode:500); } return Ok(result.Data); } }

Here [Route("graphql")] is route decorator, it says controller configured with Web API attribute routing.

'GraphQL.ISchema' is a Schema of GraphQL API, the instance of schema was created by constructor injection.

'GraphQL.IDocumentExecuter' has async GraphQL query executor. On successful execution of IDocumentExecuter returns data. On Error, execution returns an error message to the clients consuming GraphQL API.

services.AddSingleton<IDocumentExecuter, DocumentExecuter>();

Fields:

GraphQL is about asking for specific fields on objects on the server. Let's test API by requesting Fields.





Request Query:

query { players { id firstName } }

// field registered in RootQuery.cs Field<ListGraphType<PlayerType>>("players", resolve: context => { // logic to server 'players' }); // field registered in PlayerType.cs Field(_ => _.Id); Field(_ => _.FirstName);

Resolve No Service Registered For ObjectGraphQLType:

Now register PlayerType in the ConfigureService method in Startup.cs:

services.AddSingleton<PlayerType>();

Query Arguments To Filter Data :

In GraphQL we can pass an argument to filter the GraphQL API. Since in GraphQL data is coming based on Fields requested, it is possible to pass arguments to the fields to filter the data.





RootQuery.cs:

public class RootQuery:ObjectGraphType { public RootQuery(IPlayerRepository _playerRepository) { Field<ListGraphType<PlayerType>>("players", resolve: context => { return _playerRepository.GetAll(); }); Field<ListGraphType<PlayerType>>("filteredPlayers", arguments: new QueryArguments { new QueryArgument<StringGraphType> { Name = "firstName"} }, resolve: context => { string firstName = context.GetArgument<string>("firstName"); return _playerRepository.GetAll().Where(_ => _.FirstName.ToLower() == firstName.ToLower()).ToList(); }); } }

Here we added a new Field with name 'filteredPlayers'.

'ListGraphType' is collection representation in GraphQL.

Filed takes an 'arguments' as input parameter and its type is 'QueryArguments', here we capturing our argument like 'firstName' which is passed from the client to GraphQL API. Arguments that are captured here will be available from the context of Field.

'StringGraphType' is equivalent to 'string' type in dotnet.

From 'resolve' which is the input parameter of Field, we are getting the value of our argument like 'firstName' from field context. Based on the argument value filter the data and send to the client.

query { filteredPlayers(firstName: "rohit") { id firstName } }

Aliases:

We can observe that field name and API result object names are matching. By looking at the previous result object whose name is "filteredPlayers" is like more self-explanatory which might not look good in the JSON result object for clients consuming the GraphQL API. "Players" looks ideal name for the result JSON Object, but we can not use the "Players" name for argument query because of the normal query without argument using it. So this problem can be overcome by using Aliases names in the query fields. The server will return data by using the aliases' names.





Aliases Query:

query { players: filteredPlayers(firstName: "rohit") { id firstName } }



Fragments: To test the aliases name, test the API with arguments query:

A comparison between two records in GraphQL API is very easy. Let's do a comparison using the below query





The query for comparison (without Fragments):

query { leftPlayer: filteredPlayers(firstName: "rohit") { id firstName lastName } rigthPlayer: filteredPlayers(firstName: "virat") { id firstName lastName } }

query { leftComparison: filteredPlayers(firstName: "rohit") { ...props } rightComparison: filteredPlayers(firstName: "virat") { ...props } } fragment props on PlayerType { id firstName lastName }

Here 'fragment' keyword represents GraphQL Fragment.

'props' defines the name of the fragment.

'pops on PlayerType' represents a fragment working on which type of ObjectGrphQLType(example from our sample PlayerType is an ObjectGraphQLType).

A fragment was enclosed with set fields grouped.

Inside query we can observe instead of mentioning fields, we have used fragment name prop which dynamically replaces fields at the time of query execution on the server.

Accessing Data From Database:

We are going to create a DbContext, code-first approach with an existing database. Let's first install NuGet packages related to entity-framework as below.

Install-Package Microsoft.EntityFrameworkCore -Version 3.0.1

Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 3.0.1

Create a new folder name as 'Data', inside add the new file as SportsContext.cs as below:

using GraphQLSample.Models; using Microsoft.EntityFrameworkCore; namespace GraphQLSample.Data { public class SportsContext : DbContext { public SportsContext(DbContextOptions<SportsContext> options) : base(options) { } public DbSet<Player> Player { get; set; } } }

services.AddDbContext<SportsContext>(options => options.UseSqlServer(Configuration.GetConnectionString("SportsContext")));

using System.Collections.Generic; using System.Linq; using GraphQLSample.Data; using GraphQLSample.Models; namespace GraphQLSample.Repositories { public class PlayerRepository: IPlayerRepository { private readonly SportsContext _sportsContext; public PlayerRepository(SportsContext context) { _sportsContext = context; } public List<Player> GetAll() { return _sportsContext.Player.ToList(); } } }

query { players { id firstName lastName } }

Mutations:

GraphQL Mutations represent changing the state of data at the server-side, which means to create or update or delete data. To perform mutation you need to have the root mutation object of type ObjectGraphType. Like RootQuery which holds all ObjectGraphType Fields to fetch the data, we need to create RootMutation which holds all ObjectGraphType Fields to create or update or delete the data.





Let's write logic to add a new item in 'PlayerRepository.cs' file as below:

public Player AddPalyer(Player player) { _sportsContext.Player.Add(player); _sportsContext.SaveChanges(); return player; }

Player AddPalyer(Player player);

using GraphQL.Types; using GraphQLSample.Models; namespace GraphQLSample.GraphQLTypes { public class InputPlayerType:InputObjectGraphType<Player> { public InputPlayerType() { Name = "InputPlayerType"; Field(_ => _.Id); Field(_ => _.FirstName); // description is an self //explanatory extenstion method which is options Field(_ => _.LastName).Description("last name of a player"); Field(_ => _.CurrentAge); Field(_ => _.Teams); Field(_ => _.PlayingRole); Field(_ => _.BattingStyle); Field(_ => _.BowlingStyle); } } }

services.AddSingleton<InputPlayerType>();

using GraphQL.Types; using GraphQLSample.Models; using GraphQLSample.Repositories; namespace GraphQLSample.GraphQLTypes { public class RootMutation : ObjectGraphType { public RootMutation(IPlayerRepository playerRepository) { Field<PlayerType>( "addPlayer", arguments: new QueryArguments { new QueryArgument<InputPlayerType>(){ Name = "player" } }, resolve: context => { var player = context.GetArgument<Player>("player"); return playerRepository.AddPalyer(player); } ); } } }

public RootSchema(IDependencyResolver resolver):base(resolver) { Query = resolver.Resolve<RootQuery>(); Mutation = resolver.Resolve<RootMutation>(); }

services.AddScoped<RootMutation>();

public class GraphQLQueryDto { public string Query { get; set; } public string Variables { get; set; } }

var result = await _executer.ExecuteAsync(_ => { _.Schema = _schema; _.Query = query.Query; _.Inputs = query.Variables?.ToInputs(); }).ConfigureAwait(false);

mutation($player: InputPlayerType) { addPlayer(player: $player) { id firstName } }

mutation keyword to represent GraphQL Mutations.

'$player' is a variable whose values will pass from GraphQL 'Variables'.

'InputPlayerType' is assigned a type of '$player', it should match with the name of the 'InputObjectGraphType' we declared in the above steps.

'addPlayer' represents Field name in 'RootMutation' as we mentioned above steps. 'player' argument of 'addplayer' Field.

{ "player":{ "id":0 "firstName": "Shikar", "lastName": "Dhawan", "currentAge": 30, "teams": "India", "playingRole": "Opening Batsman", "battingStyle": "Left-hander", "bowlingStyle": "N/A" } }

Summary:

We have discussed the core feature of the GraphQL API. We learned how GraphQL API is different from REST API. We did a hands-on sample of integrating GraphQL API into the Asp.Net Core application.

Source Code:

Follow Me: