Building Scalable APIs with .NET Core and GraphQL

paulotorrestech

Paulo Torres

Posted on August 26, 2024

Building Scalable APIs with .NET Core and GraphQL

In the modern era of web development, building scalable and efficient APIs is crucial for handling the demands of dynamic applications. Traditional REST APIs have served developers well, but GraphQL offers a powerful alternative that can enhance flexibility, reduce over-fetching, and improve performance. This article delves into how you can build scalable APIs using .NET Core and GraphQL, providing advanced techniques and real-world examples to demonstrate the advantages of this approach.

Understanding GraphQL

What is GraphQL?

GraphQL is a query language for your API, and it offers a more efficient, powerful, and flexible alternative to REST. Developed by Facebook, GraphQL allows clients to request exactly the data they need, avoiding the over-fetching or under-fetching common in REST APIs. It also enables clients to aggregate data from multiple sources in a single request.

Key Features of GraphQL:

  • Single Endpoint: Unlike REST, which typically uses multiple endpoints, GraphQL consolidates all requests through a single endpoint.
  • Client-Driven Queries: Clients specify exactly what data they need, resulting in more efficient data retrieval.
  • Strongly Typed Schema: GraphQL uses a strongly typed schema that defines the types of data that can be queried.

Setting Up a .NET Core Project with GraphQL

Step 1: Create a New .NET Core Web API Project

To start, create a new .NET Core Web API project:

dotnet new webapi -n GraphQLApi
cd GraphQLApi
Enter fullscreen mode Exit fullscreen mode

Step 2: Add GraphQL NuGet Packages

Add the necessary GraphQL packages to your project:

dotnet add package HotChocolate.AspNetCore
dotnet add package HotChocolate.Data
dotnet add package HotChocolate.Types
Enter fullscreen mode Exit fullscreen mode

Step 3: Define Your GraphQL Schema

In GraphQL, the schema defines the structure of the API, including the types of data and the queries available.

Example: Defining a Product Schema

Create a Product model:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Define a GraphQL type for the Product model:

public class ProductType : ObjectType<Product>
{
    protected override void Configure(IObjectTypeDescriptor<Product> descriptor)
    {
        descriptor.Field(p => p.Id).Type<NonNullType<IdType>>();
        descriptor.Field(p => p.Name).Type<NonNullType<StringType>>();
        descriptor.Field(p => p.Price).Type<NonNullType<DecimalType>>();
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Implement Queries and Mutations

In GraphQL, queries are used to fetch data, while mutations are used to modify data.

Example: Implementing a Query

Create a query class to retrieve products:

public class Query
{
    public IQueryable<Product> GetProducts([Service] ProductDbContext context) =>
        context.Products;
}
Enter fullscreen mode Exit fullscreen mode

Register the query in the Startup.cs file:

public void ConfigureServices(IServiceCollection services)
{
    services.AddPooledDbContextFactory<ProductDbContext>(options =>
        options.UseInMemoryDatabase("ProductsDb"));

    services
        .AddGraphQLServer()
        .AddQueryType<Query>()
        .AddType<ProductType>();
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Set Up the Database Context

Set up an in-memory database for demonstration purposes:

public class ProductDbContext : DbContext
{
    public ProductDbContext(DbContextOptions<ProductDbContext> options)
        : base(options) { }

    public DbSet<Product> Products { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Seed the database with some initial data:

public static class DataSeeder
{
    public static void Seed(ProductDbContext context)
    {
        context.Products.AddRange(
            new Product { Name = "Laptop", Price = 999.99M },
            new Product { Name = "Smartphone", Price = 499.99M }
        );
        context.SaveChanges();
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 6: Build and Run the API

Build and run your API:

dotnet run
Enter fullscreen mode Exit fullscreen mode

Navigate to https://localhost:5001/graphql to explore the GraphQL Playground, where you can test your queries and mutations.

Advanced Techniques for Scalability

1. Pagination with GraphQL

To manage large datasets, implement pagination in your GraphQL API. GraphQL supports several pagination strategies, including offset-based and cursor-based pagination.

Example: Cursor-Based Pagination

public class ProductConnection
{
    public Connection<Product> GetProducts(
        int first,
        string after,
        [Service] ProductDbContext context)
    {
        return context.Products
            .OrderBy(p => p.Id)
            .ToConnection(first, after);
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Caching with DataLoader

DataLoader is a utility that helps minimize the number of database queries by batching and caching requests. This is particularly useful in GraphQL when you have to resolve fields across multiple types that might require separate database queries.

Example: Using DataLoader

public class ProductByIdDataLoader : BatchDataLoader<int, Product>
{
    private readonly IDbContextFactory<ProductDbContext> _dbContextFactory;

    public ProductByIdDataLoader(
        IBatchScheduler batchScheduler,
        IDbContextFactory<ProductDbContext> dbContextFactory)
        : base(batchScheduler)
    {
        _dbContextFactory = dbContextFactory;
    }

    protected override async Task<IReadOnlyDictionary<int, Product>> LoadBatchAsync(
        IReadOnlyList<int> keys,
        CancellationToken cancellationToken)
    {
        await using var dbContext = _dbContextFactory.CreateDbContext();

        return await dbContext.Products
            .Where(p => keys.Contains(p.Id))
            .ToDictionaryAsync(p => p.Id, cancellationToken);
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Optimizing Performance with Query Complexity Analysis

GraphQL allows clients to specify exactly what data they need, but this can lead to complex queries that impact performance. Implement query complexity analysis to prevent abuse by limiting the depth and complexity of queries.

Example: Implementing Complexity Analysis

services.AddGraphQLServer()
    .AddQueryType<Query>()
    .AddType<ProductType>()
    .AddMaxComplexityRule(100)
    .AddMaxDepthRule(10);
Enter fullscreen mode Exit fullscreen mode

Conclusion

Building scalable APIs with .NET Core and GraphQL offers numerous advantages, from improved efficiency in data retrieval to the flexibility of client-driven queries. By leveraging GraphQL’s powerful features, such as single endpoint access, query complexity management, and caching, you can create APIs that are not only scalable but also highly responsive to the needs of modern applications. Whether you're building an API for internal use or public consumption, integrating GraphQL with .NET Core positions your applications for success in the evolving landscape of web development.

💖 💪 🙅 🚩
paulotorrestech
Paulo Torres

Posted on August 26, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related