Introduction

GraphQL is a query language and runtime for APIs that allows clients to request exactly the data they need.

Traditional REST APIs commonly expose multiple endpoints for different resources. For example, an application might use separate endpoints for employees, departments, and projects. A GraphQL API can expose a single endpoint where the client describes the data it wants to retrieve.

This can be particularly useful for applications with complex user interfaces, such as React, Angular, Blazor, and mobile applications, where different screens may require different combinations of data.

In this article, we will build a simple GraphQL API using ASP.NET Core and Hot Chocolate. We will create a query, expose employee data, execute GraphQL queries, understand the generated schema, and examine the returned JSON response.

What Is GraphQL?

GraphQL allows a client to describe the structure of the response it needs.

For example, a client may request:

{
  employees {
    id
    name
    designation
  }
}

The server returns the requested fields:

{
  "data": {
    "employees": [
      {
        "id": 1,
        "name": "John",
        "designation": "Developer"
      },
      {
        "id": 2,
        "name": "Sarah",
        "designation": "Tester"
      }
    ]
  }
}

If the client does not need designation, it can leave that field out of the query.

{
  employees {
    id
    name
  }
}

The response then contains only those requested fields.

This client-driven query model is one of the major differences between GraphQL and many REST API designs.

GraphQL vs REST

A REST API might expose endpoints such as:

GET /api/employees
GET /api/employees/1
GET /api/departments
GET /api/projects

GraphQL normally exposes a single endpoint:

POST /graphql

The client sends a query to that endpoint.

Feature

REST

GraphQL

Endpoint model

Multiple endpoints

Typically one endpoint

Data selection

Defined by endpoint response

Client selects fields

Schema

API-specific

Strongly typed GraphQL schema

Over-fetching

Can occur

Can often be reduced

Under-fetching

Can require multiple requests

Can often be reduced

Query flexibility

Depends on endpoint design

High

Built-in API explorer

Depends on tooling

Hot Chocolate provides tooling

GraphQL does not automatically make every API faster. Performance still depends on database queries, resolver design, caching, authorization, network latency, and the amount of data requested.

What Is Hot Chocolate?

Hot Chocolate is a .NET GraphQL server framework that can be used to build GraphQL APIs with ASP.NET Core.

It provides functionality for:

  • GraphQL queries.

  • Mutations.

  • Subscriptions.

  • Schema generation.

  • Dependency injection.

  • Resolver methods.

  • Filtering and sorting capabilities.

  • GraphQL development tools.

In this tutorial, we will use Hot Chocolate to create a simple GraphQL API.

Prerequisites

Before starting, make sure you have:

  • A supported .NET SDK.

  • Visual Studio or Visual Studio Code.

  • Basic knowledge of C#.

  • Basic knowledge of ASP.NET Core.

  • Basic understanding of JSON and APIs.

Step 1: Create an ASP.NET Core Project

Create a new ASP.NET Core Web API project.

Using the .NET CLI:

dotnet new webapi -n GraphQLDemo
cd GraphQLDemo

Run the application once to make sure the project works:

dotnet run

The application will start on the local URL displayed in the terminal.

Step 2: Install Hot Chocolate

Install the Hot Chocolate ASP.NET Core package:

dotnet add package HotChocolate.AspNetCore

The package provides the ASP.NET Core integration required to expose the GraphQL endpoint.

For a real project, use a Hot Chocolate version compatible with your target .NET version.

Step 3: Create the Employee Model

Create a folder named Models.

Inside it, create Employee.cs:

namespace GraphQLDemo.Models
{
    public class Employee
    {
        public int Id { get; set; }

        public string Name { get; set; } = string.Empty;

        public string Designation { get; set; } = string.Empty;

        public string Department { get; set; } = string.Empty;
    }
}

This class represents the employee data that will be exposed through GraphQL.

Step 4: Create the Query Class

Create a Query.cs file:

using GraphQLDemo.Models;

namespace GraphQLDemo
{
    public class Query
    {
        public string GetMessage()
        {
            return "Hello GraphQL!";
        }

        public IEnumerable<Employee> GetEmployees()
        {
            return new List<Employee>
            {
                new Employee
                {
                    Id = 1,
                    Name = "John",
                    Designation = "Developer",
                    Department = "IT"
                },
                new Employee
                {
                    Id = 2,
                    Name = "Sarah",
                    Designation = "Tester",
                    Department = "QA"
                },
                new Employee
                {
                    Id = 3,
                    Name = "David",
                    Designation = "Manager",
                    Department = "HR"
                }
            };
        }
    }
}

The Query class contains methods that GraphQL exposes as query fields.

For example:

public string GetMessage()
{
    return "Hello GraphQL!";
}

can be queried through:

{
  message
}

The GetEmployees() method exposes employee data.

Step 5: Configure GraphQL in Program.cs

Open Program.cs and register Hot Chocolate:

using GraphQLDemo;

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

app.MapGraphQL();

app.Run();

The important configuration is:

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

This registers the GraphQL server and tells Hot Chocolate that the Query class contains the root query fields.

The following line exposes the GraphQL endpoint:

app.MapGraphQL();

By default, the endpoint is available at:

/graphql

Step 6: Run the Application

Run the project:

dotnet run

Open the GraphQL endpoint in your browser:

https://localhost:<port>/graphql

Depending on the Hot Chocolate version and project configuration, the GraphQL development UI can provide an interactive environment for executing queries.

Hot Chocolate has historically used Banana Cake Pop as its GraphQL IDE/development tool.

Step 7: Execute the First GraphQL Query

In the GraphQL development interface, enter:

{
  message
}

Execute the query.

The response will be:

{
  "data": {
    "message": "Hello GraphQL!"
  }
}

How This Query Works

The GraphQL query:

{
  message
}

corresponds to the C# method:

public string GetMessage()
{
    return "Hello GraphQL!";
}

GraphQL maps the method to a field in the generated schema.

The client does not need to know how the server internally generates the message. It only needs to know that the message field exists in the schema.

Step 8: Query Employee Data

Now query the employee data:

{
  employees {
    id
    name
    designation
    department
  }
}

The server returns:

{
  "data": {
    "employees": [
      {
        "id": 1,
        "name": "John",
        "designation": "Developer",
        "department": "IT"
      },
      {
        "id": 2,
        "name": "Sarah",
        "designation": "Tester",
        "department": "QA"
      },
      {
        "id": 3,
        "name": "David",
        "designation": "Manager",
        "department": "HR"
      }
    ]
  }
}

The employees field comes from:

public IEnumerable<Employee> GetEmployees()

Hot Chocolate uses the return type to build the corresponding GraphQL schema.

Step 9: Request Only Required Fields

Suppose the application only needs employee names.

Instead of requesting every field, the client can send:

{
  employees {
    name
  }
}

The response becomes:

{
  "data": {
    "employees": [
      {
        "name": "John"
      },
      {
        "name": "Sarah"
      },
      {
        "name": "David"
      }
    ]
  }
}

The client did not request id, designation, or department, so those fields are not included in the response.

This is one of the main reasons GraphQL can be useful for clients with different data requirements.

Step 10: Query Multiple Fields

A GraphQL query can request multiple root fields in one operation.

For example:

{
  message
  employees {
    id
    name
  }
}

The response can contain both:

{
  "data": {
    "message": "Hello GraphQL!",
    "employees": [
      {
        "id": 1,
        "name": "John"
      },
      {
        "id": 2,
        "name": "Sarah"
      },
      {
        "id": 3,
        "name": "David"
      }
    ]
  }
}

The client can therefore request multiple related pieces of data through the GraphQL API.

Understanding the GraphQL Schema

GraphQL APIs are strongly typed.

Our Employee class:

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public string Designation { get; set; } = string.Empty;
    public string Department { get; set; } = string.Empty;
}

is represented conceptually in the GraphQL schema as:

type Employee {
  id: Int!
  name: String!
  designation: String!
  department: String!
}

The exact schema representation depends on the framework's type inference and configuration.

The root query can expose:

type Query {
  message: String!
  employees: [Employee!]!
}

This schema tells clients which fields and types are available.

Adding a Query Parameter

GraphQL queries can also accept arguments.

For example, modify the query class:

public Employee? GetEmployeeById(int id)
{
    return new List<Employee>
    {
        new Employee
        {
            Id = 1,
            Name = "John",
            Designation = "Developer",
            Department = "IT"
        },
        new Employee
        {
            Id = 2,
            Name = "Sarah",
            Designation = "Tester",
            Department = "QA"
        }
    }
    .FirstOrDefault(e => e.Id == id);
}

The client can query a specific employee:

{
  employeeById(id: 1) {
    id
    name
    designation
  }
}

A possible response is:

{
  "data": {
    "employeeById": {
      "id": 1,
      "name": "John",
      "designation": "Developer"
    }
  }
}

This demonstrates how GraphQL arguments can be used to retrieve specific data.

Connecting GraphQL to a Database

The previous examples use an in-memory collection so that the GraphQL concepts remain simple.

In a real application, the resolver would normally retrieve data from a database or another service.

For example:

GraphQL Client
      |
      v
/graphql
      |
      v
Query Resolver
      |
      v
Application Service
      |
      v
Entity Framework Core
      |
      v
SQL Server

A service could retrieve employee records from Entity Framework Core:

public class EmployeeService
{
    private readonly ApplicationDbContext _context;

    public EmployeeService(ApplicationDbContext context)
    {
        _context = context;
    }

    public async Task<List<Employee>> GetEmployeesAsync()
    {
        return await _context.Employees.ToListAsync();
    }
}

The GraphQL resolver can then call the service.

This separation is preferable to putting database access logic directly into a large resolver.

GraphQL Queries and REST Endpoints

Consider an employee dashboard that needs:

  • Employee ID.

  • Employee name.

  • Designation.

  • Department.

With REST, the client consumes a predefined endpoint response.

With GraphQL, the client can explicitly request:

{
  employees {
    id
    name
    designation
    department
  }
}

If another screen only needs names:

{
  employees {
    name
  }
}

Both requests can use the same GraphQL endpoint.

Advantages of GraphQL

Client-Driven Data Selection

Clients request the fields they require.

Strongly Typed Schema

The schema defines available types, fields, arguments, and relationships.

Flexible Queries

A client can construct queries for different screens without requiring a separate endpoint for every combination of fields.

Reduced Over-Fetching

Clients can omit fields they do not need.

Reduced Under-Fetching

Related information can often be requested through one GraphQL operation instead of making several independent API calls.

Good Fit for Complex Clients

GraphQL can be useful when multiple clients have different data requirements.

Examples include:

  • Mobile applications.

  • React applications.

  • Angular applications.

  • Blazor applications.

  • Rich dashboards.

GraphQL Considerations

GraphQL also introduces challenges that should be considered before choosing it.

Query Complexity

Clients can construct deeply nested queries. Complex queries can place significant load on the server or database.

Query-depth and complexity controls may be necessary for production APIs.

N+1 Query Problem

Poorly designed resolvers can cause multiple database queries when resolving nested objects.

DataLoader and appropriate query design can help address this problem.

Caching

Traditional REST caching patterns do not always translate directly to GraphQL because many requests use the same endpoint with different query documents.

Caching therefore needs to be designed around the GraphQL architecture.

Authorization

Authorization should be applied carefully at the field, resolver, or application-service level according to the application's security requirements.

A user should not receive sensitive fields simply because the field exists in the GraphQL schema.

GraphQL vs REST: When Should You Use It?

GraphQL can be a good choice when:

  • Clients require different combinations of data.

  • Applications have complex nested data requirements.

  • Multiple client types consume the same backend.

  • Reducing unnecessary response fields is important.

  • A strongly typed schema is valuable.

REST can remain a better fit when:

  • The API has simple resource-oriented operations.

  • Standard HTTP semantics are important.

  • HTTP caching is central to the architecture.

  • The application does not require flexible queries.

  • The team already has a mature REST architecture.

GraphQL and REST are not mutually exclusive. An organization can use both approaches for different services or use cases.

Common Beginner Mistakes

Treating GraphQL as Automatically Faster

GraphQL reduces certain data-transfer inefficiencies, but it does not automatically improve backend performance.

Poor resolver or database design can still produce slow APIs.

Returning Entire Database Objects Without Planning

A GraphQL schema should represent the application's API contract rather than simply exposing every database field.

Ignoring Query Complexity

Allowing unrestricted nested queries can create expensive operations.

Putting All Logic in Resolvers

Resolvers should not become large classes containing business logic, database operations, validation, and authorization.

Application services can help maintain a cleaner architecture.

Forgetting Security

GraphQL's flexibility makes authorization especially important. Every exposed field and operation should have an appropriate security model.

Complete Program.cs Example

For reference, the basic application configuration is:

using GraphQLDemo;

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

app.MapGraphQL();

app.Run();

And the query class is:

using GraphQLDemo.Models;

namespace GraphQLDemo
{
    public class Query
    {
        public string GetMessage()
        {
            return "Hello GraphQL!";
        }

        public IEnumerable<Employee> GetEmployees()
        {
            return new List<Employee>
            {
                new Employee
                {
                    Id = 1,
                    Name = "John",
                    Designation = "Developer",
                    Department = "IT"
                },
                new Employee
                {
                    Id = 2,
                    Name = "Sarah",
                    Designation = "Tester",
                    Department = "QA"
                },
                new Employee
                {
                    Id = 3,
                    Name = "David",
                    Designation = "Manager",
                    Department = "HR"
                }
            };
        }
    }
}

The basic project flow is:

Create ASP.NET Core Project
          |
          v
Install Hot Chocolate
          |
          v
Create Query Class
          |
          v
Register GraphQL Server
          |
          v
Map /graphql Endpoint
          |
          v
Open GraphQL UI
          |
          v
Execute Query
          |
          v
Receive JSON Response

Output Summary

The basic message query:

{
  message
}

produces:

{
  "data": {
    "message": "Hello GraphQL!"
  }
}

The employee query:

{
  employees {
    id
    name
    designation
  }
}

produces data containing only the requested fields.

For the final C# Corner submission, screenshots should be captured from the author's actual application, including:

  • Project creation.

  • Hot Chocolate package installation.

  • Program.cs configuration.

  • /graphql interface.

  • message query and response.

  • Employee query and response.

  • Query requesting a subset of fields.

  • Parameterized employee query.

  • Database-backed output if Entity Framework Core is implemented.

Conclusion

GraphQL provides a flexible approach to API development by allowing clients to describe the data they need through queries.

In this tutorial, we created a GraphQL API using ASP.NET Core and Hot Chocolate. We configured the GraphQL server, created query resolvers, exposed the /graphql endpoint, executed GraphQL queries, retrieved employee data, used query arguments, and examined the generated schema and JSON responses.

GraphQL is particularly useful when different clients require different combinations of data. However, a production implementation also needs careful attention to database performance, query complexity, authorization, caching, validation, and resolver design.

For .NET developers, Hot Chocolate provides a practical way to introduce GraphQL into an ASP.NET Core application while continuing to use familiar C# and .NET development practices.