GraphQL is a query language that builds flexible and efficient APIs, providing a fluid and dynamic experience for developers and users. Learn how to integrate GraphQL resources with an ASP.NET Core API.
GraphQL is highly efficient as it allows you to search data accurately without overloading the network with unnecessary data. Additionally, the GraphQL schema provides a clear description of how data can be queried, which facilitates application development and collaboration between frontend and backend teams.
To understand how to implement GraphQL in practice, we will create a minimal API in ASP.NET Core using some NuGet packages with the necessary resources for manipulating data through GraphQL.
GraphQL is an open-source API query language developed by Facebook. GraphQL uses its syntax to request specific data and return that data from multiple sources, allowing customers to request exactly what they need and nothing more.
The biggest advantage of GraphQL is its flexibility. Imagine you are in a restaurant and want to order food but want a personalized dish, choosing which ingredients you want and which you don’t. GraphQL is like a personalized menu, where you can choose exactly the dishes and ingredients you want, rather than accepting a restaurant’s standard dish.
In a traditional API, you get whatever data the server decides to send you, which may include information you don’t currently need. With GraphQL, you can request only the specific fields you need, saving bandwidth and improving your application’s performance.
GraphQL is a query language and runtime for fetching and manipulating data in APIs. Using GraphQL is recommended in situations with needs like:
Query flexibility: With GraphQL, clients can request exactly the data they need, thus avoiding the data overload that often occurs with traditional RESTful APIs.
Reduction of multiple API calls: In many cases, a single GraphQL call can provide all the data needed for a given visualization or functionality, thus eliminating the need for multiple API calls.
Complex data modeling: If the system has a complex data model, with relationships between different types of objects, GraphQL can be a great option. It allows customers to navigate these relationships efficiently.
Evolving the API without breaking client applications: With GraphQL, it is possible to add new fields and types to your API without breaking existing clients. Customers only need to request the new data if and when they need it.
Applications with different data requirements: If you have different customers—for example, mobile and web applications that require different sets of data—GraphQL allows you to provide each customer with the exact data they need, without creating versions separate from the API.
However, it is important to consider that using GraphQL also brings additional complexity to the system, especially compared to simple RESTful APIs, there are also situations where it may not be the most appropriate choice:
Simple and predictable APIs: In simple APIs with well-defined endpoints and clear data requirements, a traditional RESTful approach may be more suitable, as GraphQL introduces a layer of complexity that may not be justified in simple cases.
Low complexity of queries and relationships: If the data and queries are simple, without many complex relationships between objects, it may be easier and more efficient to use a RESTful approach, where you can predict exactly what will be returned from each endpoint.
Limited development resources or unfamiliar staff: Implementing and maintaining a GraphQL API may require additional knowledge and development resources. If the team is unfamiliar with GraphQL or if resources are limited, it may be better to opt for a simpler approach.
Caching and performance optimizations: Although GraphQL offers flexibility in obtaining data, it can sometimes be challenging to implement efficient caching strategies, especially compared to RESTful APIs where endpoints often have more predictable and cacheable responses.
In summary, while GraphQL is a powerful tool, it is important to consider the specific needs of the project, the complexity of the data and queries, the team’s experience and available resources before deciding whether GraphQL is the right choice.
Below is an image comparing the main differences between a GraphQL query and a REST API request when accessing data.
GraphQL is a technology that can be implemented across multiple platforms, including ASP.NET Core, which has a wide range of features for handling web APIs. ASP.NET Core uses specific libraries and tools to add support for GraphQL.
Some popular libraries allow you to integrate GraphQL into ASP.NET Core applications, such as GraphQL.NET and Hot Chocolate.
GraphQL.NET is a GraphQL library for .NET that allows you to easily add a GraphQL server to your ASP.NET Core applications. It provides full support for creating GraphQL schemas, handling queries and performing data resolution.
Hot Chocolate is a GraphQL framework for .NET that easily integrates with ASP.NET Core applications. It offers advanced features such as schema-first and code-first support, complex query handling, Entity Framework Core integration and real-time subscription support.
These libraries provide tools and abstractions that make it easy to implement GraphQL servers in ASP.NET Core applications, allowing developers to focus on the application’s business logic.
To explore the capabilities of GraphQL in ASP.NET Core, we will build a web API to save and query data from a blog, where we will have blog posts and for each blog post there will be a list of comments.
Imagine that our blog is very successful, receiving hundreds of comments, and we need to analyze each comment to create statistics. There is nothing better than GraphQL to do this analysis because through it we can easily filter exactly the data we want.
To create the sample application, you will need to have installed the latest version of .NET. This example will use .NET 8. An IDE is also needed, and here Visual Studio Code is used.
You can access the project source code here: Blogosphere source code.
To create the application you can use the following commands
dotnet new webapi -n BlogoSphere
cd BlogoSphere
and download the NuGet packages:
dotnet add package Microsoft.EntityFrameworkCore.Sqlite --version 8.0.1
dotnet add package Microsoft.EntityFrameworkCore.Tools --version 8.0.1
dotnet add package Microsoft.EntityFrameworkCore.Design --version 8.0.1
dotnet add package HotChocolate.AspNetCore --version 14.0.0-p.29
dotnet add package HotChocolate.Data.EntityFramework --version 14.0.0-p.29
Open the application with your IDE, and then create a new folder called “Models.” Inside it, create the classes below, which are the application’s entities.
using System.ComponentModel.DataAnnotations;
namespace Blogosphere.Models;
public class Post
{
[Key]
public Guid Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public DateTime CreatedAt { get; set; }
public string Author { get; set; }
public ICollection<Comment> Comments { get; set; }
public Post()
{
Id = Guid.NewGuid();
CreatedAt = DateTime.UtcNow;
}
}
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Blogosphere.Models;
public class Comment
{
[Key]
public Guid Id { get; set; }
public string Content { get; set; }
public DateTime CreatedAt { get; set; }
public string Author { get; set; }
[ForeignKey("PostId")]
public Guid PostId { get; set; }
public Post Post { get; set; }
public Comment()
{
Id = Guid.NewGuid();
CreatedAt = DateTime.UtcNow;
}
}
The next step is to configure the database. In this post, we will use SQLite, which is a relational database and is normally stored in the same directory as the application. In addition, we will use Entity Framework Core to handle the automatic generation of the database, tables and data manipulation.
Create a new folder in the root of the project called “Data” and inside it create the class below:
namespace Blogosphere.Data;
using Blogosphere.Models;
using Microsoft.EntityFrameworkCore;
public class BlogDbContext : DbContext
{
public DbSet<Post> Posts { get; set; }
public DbSet<Comment> Comments { get; set; }
public BlogDbContext(DbContextOptions<BlogDbContext> options) : base(options)
{
}
}
In this class we are declaring to EF Core which tables should be created when database migrations are generated when running the application.
Now let’s configure the GraphQL settings, declaring which data collections we want to expose to clients.
So, inside the “Data” folder, create the class below:
using Blogosphere.Models;
namespace Blogosphere.Data;
public class PostQueryProvider
{
[UseProjection]
[UseFiltering]
[UseSorting]
public IQueryable<Post> GetPosts([Service] BlogDbContext context) => context.Posts;
[UseProjection]
[UseFiltering]
[UseSorting]
public IQueryable<Comment> GetComments([Service] BlogDbContext context) => context.Comments;
}
Note that in the class above we are declaring methods to return data from the Posts and Comments entities. In it we use the following annotations from the Hot Chocolate package:
[UseProjection]
: Indicates that projection capability (field selection) is enabled for the field to which the attribute is linked.
[UseFiltering]
: Indicates that filtering capability is enabled for the field to which the attribute is linked.
[UseSorting]
: Indicates that the sorting capability is enabled for the field to which the attribute is linked.
These attributes configure which query capabilities are exposed through the GraphQL schema for the GetPosts field. They define which projection, filtering and ordering operations can be applied to the data returned by this field.
The GetPosts method returns an IQueryable<Post>
, the query’s data source. In the case of Hot Chocolate, the library uses this type to generate dynamic queries, allowing GraphQL API clients to request only the data they need.
To verify the GraphQL functions, we need sample data. To speed this up, let’s create a class that uses the NuGet Faker package to create data using chunks of text in the Lorem ipsum style.
Inside the Data folder, create a new class called “DataSeeder” and put the code below in it:
using Blogosphere.Models;
using Faker;
namespace Blogosphere.Data
{
public static class DataSeeder
{
public static void SeedData(BlogDbContext dbContext)
{
if (!dbContext.Posts.Any())
{
for (int i = 1; i <= 10; i++)
{
var post = new Post
{
Title = Lorem.Sentence(),
Content = Lorem.Paragraphs(3).FirstOrDefault(),
Author = Name.FullName(),
CreatedAt = DateTime.Now,
};
dbContext.Posts.Add(post);
for (int j = 1; j <= 10; j++)
{
var comment = new Comment
{
Content = Lorem.Sentence(),
Author = Name.FullName(),
CreatedAt = DateTime.Now,
Post = post
};
dbContext.Comments.Add(comment);
}
}
dbContext.SaveChanges();
}
}
}
}
Note that the SeedData method receives a parameter of type BlogDbContext
, which is the database context. It checks if the Posts table is empty. If it is empty, the method creates 10 posts and, for each post, it creates 10 random comments. Posts are populated with randomly generated titles, content, authors and creation dates using the Faker library.
Comments are also filled with random content, authors and creation dates. After filling the database, the method saves the changes using dbContext.SaveChanges()
. This way, when starting the application, if there is no data, this method will create sample data.
To create the connection with the database, we will add a small configuration to the appsettings.json
file. Open the appsettings.json
file and add the code snippet below to it:
"ConnectionStrings": {
"DefaultConnection": "Data Source=blog.db"
},
The last step is to add the database and GraphQL settings to the Program class. Replace the existing code in the Program.cs file with the code below:
using Blogosphere.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<BlogDbContext>(options =>
options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddGraphQLServer()
.AddQueryType<PostQueryProvider>()
.AddProjections()
.AddFiltering()
.AddSorting();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
var dbContext = services.GetRequiredService<BlogDbContext>();
dbContext?.Database.EnsureCreated();
DataSeeder.SeedData(dbContext);
}
app.MapGraphQL("/graphql");
app.Run();
Let’s now check the most important parts of this code in the context of the post.
builder.Services.AddDbContext<BlogDbContext>(options =>
options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection")));
This part configures the database using SQLite as a provider, based on the connection string called “DefaultConnection” provided in the configuration file.
builder.Services.AddGraphQLServer()
.AddQueryType<PostQueryProvider>()
.AddProjections()
.AddFiltering()
.AddSorting();
This part of the code configures the GraphQL server in the application. Below is each of the methods explained separately.
.AddGraphQLServer()
: This method adds the GraphQL server to the application services. It allows the application to provide a GraphQL API.
.AddQueryType<PostQueryProvider>()
: This method adds the query type to the GraphQL schema. The PostQueryProvider class provides queries related to posts and comments. This means you can query information about posts using the GraphQL API.
.AddProjections()
: This method enables projections on the GraphQL server. Projections are used to determine which fields or properties of an object are returned in a GraphQL query. For example, if you want to return just a few fields from an object in a GraphQL query, you can use projections to do so.
.AddFiltering()
: This method enables filtering on the GraphQL server. This means customers can submit GraphQL queries with filter parameters to retrieve data based on specific criteria such as dates, numeric values, etc.
.AddSorting()
: This method enables sorting on the GraphQL server. This allows clients to specify the order of results returned in a GraphQL query.
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
var dbContext = services.GetRequiredService<BlogDbContext>();
dbContext?.Database.EnsureCreated();
DataSeeder.SeedData(dbContext);
}
This creates a scope for the application services, gets the database context, and ensures that the database is created (if it doesn’t exist) and populated with data using the DataSeeder class.
app.MapGraphQL("/graphql");
This maps GraphQL requests to the "/graphql"
path. So when the application is run, the GraphQL interface will be available at the "/graphql"
endpoint.
Now that all implementations are ready, we can run the application and access the GraphQL interface. Run the command dotnet run
in the terminal to run the application. It may take a while to start the application due to database generation and data insertion, but it is not a considerable amount of time.
Then access the address in the browser: http://localhost:[PORT]/graphql/
, and the GraphQL interface will be displayed. Note that there is a field for typing where it says “Operations.” Paste the code below into this field:
{
query: comments {
content
}
}
Then click on the “Run” button and the query data will be displayed in the tab on the right side called “Response,” as shown in the image below:
It is possible to filter data using the GraphQL interface, as can be seen in the image below. In addition, there are other filtering and sorting options. You can check out all the options available in GraphQL documentation.
Note that in the query shown in the image, this code was used:
{
query: comments(where: {author: { contains: "Jeanie Schowalter PhD" } }) {
content
id
author
}
}
which filtered the name “Jeanie Schowalter PhD” and returned the data entered in the query, content, id and author.
Throughout the post, we saw the details of GraphQL, its main advantages and how it has great flexibility compared to traditional REST APIs.
Furthermore, we created an ASP.NET Core project and integrated it with GraphQL through the Hot Chocolate NuGet libraries, then we ran the project and saw how to fetch data through the GraphQL interface.
It is important to highlight that despite the advantages brought by GraphQL, it is always advisable to analyze whether its use is necessary. After all, in scenarios where there is not a large volume of data and relationships between them, perhaps GraphQL is not a good choice.
GraphQL is not superior to traditional REST APIs nor does it replace them—it is just another option when working with large-scale web applications.