Implementing GraphQL in .NET for a Todo API


1. Introduction

Modern applications—especially SPAs and mobile apps—require APIs that are flexible, efficient, and scalable. Traditional REST APIs often return fixed data structures, leading to problems like over-fetching (getting more data than needed) or under-fetching (requiring multiple API calls).

GraphQL solves these problems by allowing clients to request exactly the data they need through a single endpoint, guided by a strongly typed schema.

In this article, we will build a Todo API using GraphQL in .NET, step by step, using Hot Chocolate, one of the most mature GraphQL libraries for .NET.

This guide is intentionally detailed and suitable for:

2. What is GraphQL?

GraphQL is a query language for APIs and a runtime for executing those queries using your existing data.

Key Characteristics

  • Single Endpoint: Typically /graphql
  • Strongly Typed Schema: Contract between client and server
  • Client-driven Queries: Client decides the shape of the response
  • Introspection: APIs are self-documenting

REST vs GraphQL (Quick Comparison)

Feature REST GraphQL
Endpoints Multiple Single
Over-fetching Common Eliminated
Under-fetching Common Eliminated
Versioning Required Usually not needed
Schema Implicit Explicit & Typed

3. Why Use GraphQL for a Todo API?

Although a Todo API is simple, it demonstrates real-world GraphQL benefits:

  • Query only id and title when needed
  • Fetch a single Todo or all Todos with the same endpoint
  • Combine multiple operations in one request
  • Clean evolution of API without breaking clients

4. Project Setup

4.1 Create the Project

dotnet new webapi -n TodoGraphQLApi
cd TodoGraphQLApi

Remove sample files like WeatherForecast.cs to keep the solution clean.

4.2 Install Required NuGet Packages

We will use Hot Chocolate for GraphQL support:

dotnet add package HotChocolate.AspNetCore
dotnet add package HotChocolate.Data
dotnet add package HotChocolate.Types

Why Hot Chocolate?

  • Native .NET experience
  • Schema-first and code-first support
  • Excellent performance
  • Actively maintained

5. Designing the Domain Model

5.1 Todo Entity

public class TodoItem
{
    public int Id { get; set; }
    public string Title { get; set; } = string.Empty;
    public bool IsCompleted { get; set; }
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}

This model will later map directly to GraphQL types.

6. Data Access Layer (Repository Pattern)

To keep GraphQL resolvers clean and testable, we use a repository layer.

6.1 Repository Interface

public interface ITodoRepository
{
    IEnumerable<TodoItem> GetAll();
    TodoItem? GetById(int id);
    TodoItem Add(TodoItem todo);
    TodoItem? Update(int id, bool isCompleted);
    bool Delete(int id);
}

6.2 In-Memory Repository Implementation

public class TodoRepository : ITodoRepository
{
    private readonly List<TodoItem> _todos = new();
    private int _idCounter = 1;

    public IEnumerable<TodoItem> GetAll() => _todos;

    public TodoItem? GetById(int id)
        => _todos.FirstOrDefault(t => t.Id == id);

    public TodoItem Add(TodoItem todo)
    {
        todo.Id = _idCounter++;
        _todos.Add(todo);
        return todo;
    }

    public TodoItem? Update(int id, bool isCompleted)
    {
        var todo = GetById(id);
        if (todo == null) return null;
        todo.IsCompleted = isCompleted;
        return todo;
    }

    public bool Delete(int id)
    {
        var todo = GetById(id);
        if (todo == null) return false;
        _todos.Remove(todo);
        return true;
    }
}

In real projects, this can be replaced with Entity Framework + SQL Server/PostgreSQL without changing GraphQL logic.

7. GraphQL Schema Concepts

GraphQL APIs are built around:

  • Query – Read operations
  • Mutation – Write operations
  • Type System – Object, Input, Enum, Scalar

Hot Chocolate automatically infers schema from C# classes.

8. Implementing GraphQL Queries

Queries fetch data and must be side-effect free.

public class Query
{
    public IEnumerable<TodoItem> GetTodos([Service] ITodoRepository repo)
        => repo.GetAll();

    public TodoItem? GetTodoById(int id, [Service] ITodoRepository repo)
        => repo.GetById(id);
}

Example Query

query {
  todos {
    id
    title
    isCompleted
  }
}

9. Implementing GraphQL Mutations

Mutations modify server-side data.

public class Mutation
{
    public TodoItem AddTodo(string title, [Service] ITodoRepository repo)
    {
        return repo.Add(new TodoItem
        {
            Title = title,
            IsCompleted = false
        });
    }

    public TodoItem? CompleteTodo(int id, [Service] ITodoRepository repo)
    {
        return repo.Update(id, true);
    }

    public bool DeleteTodo(int id, [Service] ITodoRepository repo)
    {
        return repo.Delete(id);
    }
}

Example Mutation

mutation {
  addTodo(title: "Build GraphQL API") {
    id
    title
    isCompleted
  }
}

10. Dependency Injection & Configuration

10.1 Register Services

builder.Services.AddSingleton<ITodoRepository, TodoRepository>();

builder.Services
    .AddGraphQLServer()
    .AddQueryType<Query>()
    .AddMutationType<Mutation>();

10.2 Enable GraphQL Endpoint

app.MapGraphQL("/graphql");

Once the app is running, Hot Chocolate provides a built-in GraphQL IDE.

11. Testing the API

Navigate to:

https://localhost:{port}/graphql

You can:

  • Explore schema via documentation panel
  • Run queries and mutations
  • Validate requests automatically

12. Error Handling in GraphQL

GraphQL always returns 200 OK, but errors are included in the response:

{
  "data": null,
  "errors": [
    {
      "message": "Todo not found"
    }
  ]
}

This makes client-side handling predictable.

13. Security Considerations

In real-world applications:

  • Use Authorization directives
  • Limit query depth & complexity
  • Validate input using input types
  • Enable persisted queries

Hot Chocolate supports all of these features.

14. When to Choose GraphQL

GraphQL is ideal when:

  • Multiple clients consume the same API
  • Frontend requirements change frequently
  • You want strong contracts without versioning

Avoid GraphQL when:

  • APIs are extremely simple
  • Heavy HTTP caching is mandatory

15. Conclusion

Implementing GraphQL in .NET using Hot Chocolate provides a clean, scalable, and modern API architecture. Even a simple Todo API demonstrates how GraphQL replaces multiple REST endpoints with a single, powerful schema.

From here, you can extend this API by adding:

  • Entity Framework Core
  • Pagination & filtering
  • Authentication & authorization
  • GraphQL subscriptions for real-time updates
  • GraphQL is a future-proof choice for modern .NET applications.

Happy Coding!

0 Comments Report