GraphQL: The Most Misunderstood API Technology of the 21st Century

GraphQL might be the most misunderstood technology of the 21st century. Despite the hype and complexity that often surrounds it, the core concepts are surprisingly straightforward. Let’s cut through the noise and explore what GraphQL actually is, why it matters, and how you can start using it today with simple C# examples that you can run directly in LINQPad.

What GraphQL Really Is

At its heart, GraphQL is about API design and organization. It provides a way to structure how clients request data from servers, and how servers respond with exactly what was asked for. The technology itself is built on a simple foundation: queries read data, and mutations modify data.

The beauty of GraphQL lies in its flexibility. Instead of making multiple API calls to different endpoints, you can request all the data you need in a single query. This unified interface can pull data from many different sources behind the scenes, presenting it as if it all came from one place.

The Restaurant Analogy

Think of GraphQL like visiting a restaurant. You sit down, review the menu, and place your order: nachos for the appetizer, a burger and fries for the main course, and a rich chocolate dessert to finish. Throughout the evening, your food arrives at the perfect time, creating a wonderful dining experience.

What you don’t see is that the nachos came from a food truck out back, the kitchen prepared the burger and fries, and the dessert was pre-sliced from a local bakery. But none of that matters to you as the customer. You got exactly what you ordered, and it was delicious.

GraphQL works the same way. You place your order for data through queries, and the GraphQL server decides how to fulfill that request. Each piece of data might come from a different source - a database, a microservice, a third-party API - but you get everything you need in one response.

Core GraphQL Concepts

GraphQL has two main operations: queries and mutations. Queries are for reading data, while mutations are for modifying data. That’s it. The complexity comes from the implementation details, not the concepts themselves.

The GraphQL API designer builds the API much like a chef builds a dinner menu. They define what data is available, how it can be accessed, and what operations are allowed. This design process is crucial because it determines how flexible and powerful your API will be.

Introspection is GraphQL’s way of letting you read the API menu. It’s a built-in feature that allows clients to discover what queries and mutations are available, what data types are supported, and how to structure requests. This self-documenting nature is one of GraphQL’s biggest advantages.

Download LINQPad Today

Get started with the most powerful .NET Rapid Progress Tool (RPT) available.

Get LINQPad Premium

Powerful .NET acceleration. Code at the speed of thought.

Testing GraphQL with LINQPad

LINQPad makes it incredibly easy to experiment with GraphQL APIs. You can quickly test queries, explore the schema through introspection, and see exactly how the data flows. This hands-on approach is perfect for learning GraphQL concepts without getting bogged down in complex setup.

Let’s look at a practical example. Here’s a simple GraphQL API that manages a list of players:

async Task Main()
{
	var sc = new ServiceCollection();
	sc.AddScoped<PlayersQuery>();
	sc.AddScoped<PlayersMutation>();
	sc.AddScoped<PlayerRepository>();

	var schema = new PlayersSchema(sc.BuildServiceProvider());

	// get the initial list of players from our graphql api
	var json = await schema.ExecuteAsync(_ =>
	{
		_.Query = "{ players { id name jerseyNumber team { name } } }";
	});
	json.Dump("query 1");

	// perform a mutation, which we've coded to add one new player to our list
	var dict = new Dictionary<string, object>
	{
		{ "player", new Dictionary<string, object> { {"jerseyNumber", 12}, {"name", "Messi"}, } }
	};
	var json2 = await schema.ExecuteAsync(_ =>
	{
		_.Query = "mutation ($player:PlayerInput!) { playerCreate(player: $player) { id jerseyNumber name } }";
		_.Variables = new Inputs(dict);
	});
	json2.Dump("mutation 1");

	// get the list of players to see our new player has been added
	var json3 = await schema.ExecuteAsync(_ =>
	{
		_.Query = "{ players { id name jerseyNumber team { name } } }";
	});
	json3.Dump("query 2");
}

When you run this code in LINQPad, you’ll see three different outputs. The first query returns the initial list of players, the mutation adds a new player, and the second query shows the updated list. This demonstrates the core GraphQL operations in action.

LINQPad showing code to run GraphQL standalone as a console

The Schema Definition

The schema is where you define what your GraphQL API can do. Here’s how we define our players schema:

public class PlayersSchema : Schema
{
	public PlayersSchema(IServiceProvider services)
	{
		Query = services.GetService<PlayersQuery>();
		Mutation = services.GetService<PlayersMutation>();
	}
}

This simple setup tells GraphQL that we have query operations (for reading data) and mutation operations (for modifying data). The actual implementation is handled by the PlayersQuery and PlayersMutation classes.

Query Operations

Queries in GraphQL are all about reading data. They’re flexible and powerful, allowing you to request exactly the data you need. Here’s how we define our players query:

public class PlayersQuery : ObjectGraphType<List<Player>>
{
	public PlayersQuery(PlayerRepository repo)
	{
		Field<ListGraphType<PlayerGraphType>>("players").Resolve(ctx => repo.Players);
	}
}

This creates a players field that returns a list of players. The beauty of GraphQL is that clients can request any combination of fields. They might ask for just the names, or they might want names, jersey numbers, and team information.

Mutation Operations

Mutations are used to modify data. They follow the same pattern as queries but are designed for operations that change the state of your system:

public class PlayersMutation : ObjectGraphType
{
	public PlayersMutation(PlayerRepository repo)
	{
		Field<PlayerGraphType>("playerCreate")
		  .Argument<NonNullGraphType<PlayerInputType>>("player")
		  .Resolve(context =>
		  {
			  var human = context.GetArgument<Player>("player");
			  return repo.PlayerAdd(human);
		  });
	}
}

This mutation allows clients to create new players by providing player data as an argument. The mutation returns the newly created player, including the generated ID.

Data Types and Resolvers

GraphQL uses a type system to define the structure of your data. Each type has fields, and each field has a resolver that determines how to get the data:

public class PlayerGraphType : ObjectGraphType<Player>
{
	public PlayerGraphType()
	{
		Field(x => x.Id).Description("player id");
		Field(x => x.JerseyNumber).Description("player jersey number");
		Field(x => x.Name).Description("player name");

		AddField(new FieldType
		{
			Name = "team",
			Description = "player team name",
			Type = typeof(TeamGraphType),
			Resolver = new FuncFieldResolver<Team>(context =>
			{
				return context.Source switch
				{
					Player p when p.Name == "Michael Jordan" => new Team { Name = "North Carolina" },
					Player p when p.Name == "Tony Romo" => new Team { Name = "Eastern Illinois" },
					Player p when p.Name == "Messi" => new Team { Name = "Argentina" },
					_ => null
				};
			})
		});
	}
}

This type definition shows how GraphQL can combine data from different sources. The basic player information comes from the Player object, but the team information is resolved dynamically based on the player’s name.

LINQPad showing code for the entire GraphQL server query and mutation

Building a Web API

GraphQL isn’t just for testing in LINQPad. You can easily build a full web API using ASP.NET Core:

async Task Main()
{
	var builder = WebApplication.CreateBuilder();
	builder.Services
		.AddScoped<PlayersSchema>()
		.AddScoped<PlayersQuery>()
		.AddScoped<PlayersMutation>()
		.AddScoped<PlayerRepository>()
		.AddGraphQL(b => b
			.AddSelfActivatingSchema<PlayersSchema>()
			.AddSystemTextJson()
		);

	var app = builder.Build();
	app.UseDeveloperExceptionPage();

	app.UseGraphQL("/graphql");
	app.UseGraphQLGraphiQL("/",
		new GraphiQLOptions
		{
			GraphQLEndPoint = "/graphql",
		});
	await app.RunAsync();
}

This setup creates a web server with a GraphQL endpoint at /graphql and a GraphiQL interface at the root. GraphiQL is a tool for exploring and testing GraphQL APIs - think of it as a specialized version of Postman for GraphQL.

LINQPad showing code to run GraphQL as a server in ASP.NET Core

The Reality of GraphQL

Remember that nothing is free in this world. GraphQL requires a lot of code to implement properly. You need to define types, write resolvers, handle errors, and manage performance. The flexibility that makes GraphQL powerful also makes it complex to implement correctly.

However, the concepts themselves are simple. Queries read data, mutations modify data, and everything is just JSON under the hood. GraphQL is most commonly found in web APIs, but there’s nothing stopping you from using it in other services or console applications.

When to Use GraphQL

GraphQL shines in scenarios where you need flexible data fetching. If your clients often need different combinations of data, or if you’re building APIs that serve multiple different client applications, GraphQL can provide significant benefits.

It’s particularly useful when you have complex data relationships or when you want to reduce the number of API calls your clients need to make. The ability to request exactly the data you need in a single query can dramatically improve performance and reduce complexity.

The GraphQL Ecosystem

We’ve only scratched the surface of the GraphQL ecosystem. There are tools for schema management, performance monitoring, caching, and more. The community has built an impressive collection of libraries and tools that make working with GraphQL easier and more powerful.

The key is to start simple. Focus on the core concepts of queries and mutations, understand how the type system works, and build from there. GraphQL can be as simple or as complex as you need it to be.

Conclusion

GraphQL is often misunderstood because of the hype and complexity that surrounds it. But at its core, it’s a simple and elegant way to design APIs. The concepts are straightforward: queries read data, mutations modify data, and everything is just JSON.

The power of GraphQL comes from its flexibility and the way it allows clients to request exactly the data they need. This can lead to better performance, simpler client code, and more maintainable APIs.

Start with simple examples like the ones in this article. Use LINQPad to experiment and learn. Build small APIs and gradually add complexity as you need it. The GraphQL concepts are simple enough if you can cut through the hype and focus on the fundamentals.

Download LINQPad Today

Get started with the most powerful .NET Rapid Progress Tool (RPT) available.

Get LINQPad Premium

Powerful .NET acceleration. Code at the speed of thought.

Testing with LINQPad

The examples in this article are designed to run directly in LINQPad, making it easy to experiment with GraphQL concepts. You can modify the queries, add new fields, and see how the data changes in real-time. This hands-on approach is perfect for learning GraphQL without the overhead of setting up a full development environment.

Try running the code examples and experiment with different queries. Change the player data, add new fields, or modify the resolvers. LINQPad’s immediate feedback makes it ideal for exploring GraphQL concepts and understanding how the pieces fit together.

Full Source Code

Full source code is available to download at https://github.com/ryanrodemoyer/LINQPadGraphQL

GraphQL API Design C# .NET LINQPad Web APIs Data Fetching REST Alternatives