DEV Community

loading...
.NET

How YOU can build a Web API using GraphQL .Net Core and Entity Framework

softchris profile image Chris Noring Updated on ・12 min read

 Building a Web Api using GraphQL .Net Core and Entity Framework

In this article we will learn:

  • Build a Web Api, we will learn how to scaffold a Web Api using the .Net Core CLI and how different classes respond to different requests
  • Integrate GraphQL, we will create the necessary helper classes to create things such as Schema, Resolvers. Additionally, we will learn to work with the visual environment GraphiQL
  • Set up Entity Framework and mapping classes to tables, here we will set up a class with entities that will create corresponding tables and columns in a database. We will also integrate EF with our API

Here's the repo for this article:

https://github.com/softchris/graphql-ef-demo

 Resources

 Build our Web Api

The first thing we will do is to scaffold a .Net Core project. We will use a template called webapi. The command is as follows:

dotnet new webapp -o aspnetcoreapp
Enter fullscreen mode Exit fullscreen mode

This will create a Web Api project in a folder aspnetcoreapp. The flag -o says what to name the directory. So you can replace aspnetcoreapp with a name of your choosing.

If you've never built a Web Api in .Net Core before I recommend having a look at the Web Api link as mentioned in Resources. I will say this though. The idea is to have a concept of routes that you match to controllers. In a normal looking Wep Api you would normally have a route api/Products that would be handled by a ProductsController class. We will look at this a bit closer when we implement the GraphQL part.

 Integrate GraphQL

We will take the following steps to integrate GraphQL:

  1. Install dependencies from NuGet
  2. Define a Schema with custom types, query types and mutations
  3. Create resolver functions that will respond to requests
  4. Add a Web Api route to respond to requests from GraphiQL, our visual environment

Install dependencies

First ensure we are inside of our project directory:

cd aspnetcoreapp
Enter fullscreen mode Exit fullscreen mode

Now install the dependencies like so:

dotnet add package GraphQL --version 2.4.0
dotnet add package graphiql --version 1.2.0
Enter fullscreen mode Exit fullscreen mode

The package GraphQL will give us the needed core library to set up a schema, and define resolvers. graphiql package is a visual environment that we will use to show how great the developer experience is with it.

 Set up schema

Create a Graphql directory like so:

mkdir Graphql
Enter fullscreen mode Exit fullscreen mode

Now create a file Schema.cs and give it the following content:

using GraphQL.Types;
using GraphQL;
using Api.Database;

namespace Api.Graphql 
{
  public class MySchema 
  {
    private ISchema _schema { get; set; }
    public ISchema GraphQLSchema 
    {  
      get 
      {
        return this._schema;
      }
    }

    public MySchema() 
    {
      this._schema = Schema.For(@"
          type Book {
            id: ID
            name: String,
            genre: String,
            published: Date,
            Author: Author
          }

          type Author {
            id: ID,
            name: String,
            books: [Book]
          }

          type Mutation {
            addAuthor(name: String): Author
          }

          type Query {
              books: [Book]
              author(id: ID): Author,
              authors: [Author]
              hello: String
          }
      ", _ =>
      {
        _.Types.Include<Query>();
        _.Types.Include<Mutation>();
      });
    }

  }
}
Enter fullscreen mode Exit fullscreen mode

Let's break down what we just did. Query and Mutation are reserved words. Query is our public API, anything we put in here can be queried for. Mutation is also part of the public API but signals that we want to change data. Our only entry is addAuthor that will allow us to create an Author. Author and Book are custom types that we just defined and have some suitable properties on them.

Querying

If you haven't read any of my other articles on GraphQL I recommend having a look at the resource section but here's a quick run-through of how it works to query things in GraphQL. Given our schema above we can query for books. It could look like this:

{ 
  books { 
    name,
    genre,
    published
  }
}
Enter fullscreen mode Exit fullscreen mode

This would give a response like so:

{
  "data": {
    "books" : [{
      "name": "IT",
      "genre": "Horror",
      "published": "1994"
    }]
  } 
}
Enter fullscreen mode Exit fullscreen mode

One of the great things about GraphQL is that it allows for us to go at depth and ask for even more data, so we could be asking for it to list the author as well in our query above, like so:

{
  books {
    name, 
    genre,
    published,
    author {
      name
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

with the corresponding answer:

{
  "data": {
    "books" : [{
      "name": "IT",
      "genre": "Horror",
      "published": "1994",
      "author": {
        "name": "Stephen King"
      }
    }]
  } 
}
Enter fullscreen mode Exit fullscreen mode

 Define resolvers

Before we go so far as defining resolvers we need a couple of types. We need to create Author and Book.

Create types

First create a file Author.cs under a directory Database. Give it the following content:

// Database/Author.cs

using System.Collections.Generic;

namespace Api.Database
{
  public class Author
  {
    public int Id { get; set; }
    public string Name { get; set; }
    public List<Book> Books { get; set; }
  }
}
Enter fullscreen mode Exit fullscreen mode

Now create the Book.cs also that one under the Database directory:

// Database/Book.cs

namespace Api.Database 
{
  public class Book
  {
    public string Id { get; set; }

    public string Name { get; set; }

    public bool Published { get; set; }

    public string Genre { get; set; }

    public int AuthorId { get; set; }

    public Author Author { get; set; }
  }
}
Enter fullscreen mode Exit fullscreen mode

Create Query resolver

Now let's define the corresponding resolvers. In our Schema.cs we mentioned Query and Mutation, classes we are yet to define. Let's start by creating Query.cs and give it the following content:

// Graphql/Query.cs

using System.Collections.Generic;
using GraphQL;
using System.Linq;
using Api.Database;
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;

namespace Api.Graphql 
{
  public class Query
  {

    [GraphQLMetadata("books")]
    public IEnumerable<Book> GetBooks()
    {
      return Enumerable.Empty<Books>();
    }

    [GraphQLMetadata("authors")]
    public IEnumerable<Author> GetAuthors() 
    {
      return Enumerable.Empty<Authors>();
    }

    [GraphQLMetadata("author")]
    public Author GetAuthor(int id)
    {
      return null;
    }

    [GraphQLMetadata("hello")]
    public string GetHello()
    {
      return "World";
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

We have created a class above that handles every request to query in our schema. We've also created a method that corresponds to everything we can query for. The decorator GraphQLMetadata helps us to map what's written in the Schema to a method, a resolver. For example, we can see how author(id: ID): Author is mapped to the following code in the Query class:

[GraphQLMetadata("author")]
public Author GetAuthor(int id)
{
  return null;
}
Enter fullscreen mode Exit fullscreen mode

Create Mutation resolver

We have one more resolver to define namely Mutation. Let's create Mutation.cs with the following content:

using Api.Database;
using GraphQL;

namespace Api.Graphql 
{
  [GraphQLMetadata("Mutation")]
  public class Mutation 
  {
    [GraphQLMetadata("addAuthor")]
    public Author Add(string name)
    {
      return null;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Adding the GraphQL route

When it comes to GraphQL the whole point is to only have one route /graphql and for a negotiation to happen between frontend and backend about what content should be returned back. We will do two things:

  1. Map GraphiQL to /graphql
  2. Create a controller to respond to /graphql

Map GraphiQL

GraphiQL is visual environment and something we installed from NuGet. To be able to use it we need to open up Startup.cs and in the method Configure() we need to add the following line:

app.UseGraphiQl("/graphql");
Enter fullscreen mode Exit fullscreen mode

NOTE, make sure to add the above line before app.UseMvc();

Create a GraphQL Controller

Under the directory Controllers let's create a file GraphqlController.cs. Let's build up this file gradually. Let's start with the class definition:

// GraphqlController.cs

using System.Threading.Tasks;
using GraphQL;
using Microsoft.AspNetCore.Mvc;
using Api.Graphql;

[Route("graphql")]
[ApiController]
public class GraphqlController: ControllerBase 
{
}
Enter fullscreen mode Exit fullscreen mode

By using the decorator Route we are able to map a certain route to class instead of relying on a default convention. As you can see we give it the argument graphql to ensure it matches /graphql. We also give the class the decorator ApiController, this is something we need to do to all API Controllers so they can respond to requests.

Next, we need a method to handle requests. A good thing to know about GraphQL is that all requests in GraphQL use the verb POST, consequently, we need to set up such a method like so:

[HttpPost]
public async Task<ActionResult> Post([FromBody] GraphQLQuery query) 
{
  return null;
}

Enter fullscreen mode Exit fullscreen mode

The decorator HttpPost ensures that we can respond to POST requests. Let's have closer look at the input parameter query. It uses the decorator FromBody to parse out values from the posted Body and try to convert it to the type GraphQLQuery.

Two questions come to mind, what is GraphQLQuery and why do we need it?

GraphQLQuery is a class we need to define so let's do that by creating /Graphql/GraphQLQuery.cs:

using Newtonsoft.Json.Linq;

namespace Api.Graphql
{
  public class GraphQLQuery
  {
    public string OperationName { get; set; }
    public string NamedQuery { get; set; }
    public string Query { get; set; }
    public JObject Variables { get; set; }
  }
}
Enter fullscreen mode Exit fullscreen mode

For the second question why we need it? It needs to look this way because we are integrating it with our visual environment GraphiQL. We have reason to come back to this structure once we start using GraphiQL and we can see how the above structure is populated.

Let's add the rest of the implementation for our Controller:

// GraphqlController.cs

using System.Threading.Tasks;
using Api.Graphql;
using GraphQL;
using Microsoft.AspNetCore.Mvc;

namespace graphql_ef.Controllers 
{
  [Route("graphql")]
  [ApiController]
  public class GraphqlController: ControllerBase 
  {
    [HttpPost]
    public async Task<ActionResult> Post([FromBody] GraphQLQuery query) 
    {
      var schema = new MySchema();
      var inputs = query.Variables.ToInputs();

      var result = await new DocumentExecuter().ExecuteAsync(_ =>
      {
        _.Schema = schema.GraphQLSchema;
        _.Query = query.Query;
        _.OperationName = query.OperationName;
        _.Inputs = inputs;
      });

      if (result.Errors?.Count > 0)
      {
        return BadRequest();
      }

      return Ok(result);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Above we are reading qhat we need from our input query and pass that on to the DocumentExecuter and we end up with a result.

We have everything in place right now to try out our Api so let's do that next with Debug/Start Debugging. You should see the following:

Here we have created three queries AllBooks, Author and AllBooksWithAuthor.

We can easily run one of the queries hitting the Play button, this allows us to select a specific one:

Running the query we get the following back:

We aren't surprised though as we have only stubbed out the answer to return an empty array. Before we fix that and connect with a database let's talk a bit more about our GraphiQL environment.

One great thing I failed to mention was that we have auto-complete support when author our query or mutation so we can easily get info as we type what resources and columns are available:

We can obviously write a number of queries and select the one we want. There is more, we can look the right pane and see that our schema definition can be browsed:

Clicking the Docs link will show all the types we have starting with the top-level:

Then we can drill down as much as we want and see what we can query for, what custom types we have and more:

 Adding a database with Entity Framework

Now that we have everything working, let's define the database and replace the stubbed answers in the resolver methods with database calls.

To accomplish all this we will do the following:

  1. Define a database in code, We do this by creating a class inheriting from DbContext and ensure it has fields in it of type DbSet
  2. Define the models we mean to use in the Database, we've actually already done this step when we created Author and Book.
  3. Set up and configure the database type, we will use an in-memory database type for this but we can definitely change this later to Sql Server or MySql or whatever database type we need
  4. Seed the database, for the sake of this example we want some initial data so that when we query we get something back
  5. Replace the stubbed code in resolver methods with real calls to the database

 Define a database in code

We are using an approach called code first. This simply means we create a class with fields, where the fields become the tables in the Database. Let's create a file StoreContext.cs and give it the following content:

using Microsoft.EntityFrameworkCore;

namespace Api.Database
{

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

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
      optionsBuilder.UseInMemoryDatabase("BooksDb");
    }

    public DbSet<Book> Books { get; set; }
    public DbSet<Author> Authors { get; set; }
  }
}
Enter fullscreen mode Exit fullscreen mode

The two fields Books and Authors is of type DbSet<Book> and DbSet<Author> respectively and will become tables in our database. The method OnConfiguring() is where we set up our database BooksDb and we also specify that we want to make it in an in-memory database with the method UseInMemoryDatabase(). We can change this to something else should we want it to persist an actual database.

Seed the database

Now this is not a step we must do but it's nice to have some data when we start querying. For this we will open up Program.cs and add the following to the Main() method:


using(var db = new StoreContext()) 
{
    var authorDbEntry = db.Authors.Add(
        new Author
        {
            Name = "Stephen King",
        }
    );

    db.SaveChanges();

    db.Books.AddRange(
    new Book
    {
        Name = "IT",
        Published = true,
        AuthorId = authorDbEntry.Entity.Id,
        Genre = "Mystery"
    },
    new Book
    {
        Name = "The Langoleers",
        Published = true,
        AuthorId = authorDbEntry.Entity.Id,
        Genre = "Mystery"
    }
    );

    db.SaveChanges();
}
Enter fullscreen mode Exit fullscreen mode

The above will create an author and two books.

Replace the stubbed code

Now to the fun part. We will replace our stubbed code with actual calls to the database and Entity Framework.

There are two files we need to change, Query.cs and Mutation.cs. Let's start with Query.cs.

Query.cs

Open up the file Query.cs under the Graphql directory and replace its content with the following:

// Graphql/Query.cs

using System.Collections.Generic;
using GraphQL;
using System.Linq;
using Api.Database;
using Microsoft.EntityFrameworkCore;

namespace Api.Graphql 
{
  public class Query
  {

    [GraphQLMetadata("books")]
    public IEnumerable<Book> GetBooks()
    {
      using(var db = new StoreContext())
      {
        return db.Books
        .Include(b => b.Author)
        .ToList();
      }
    }

    [GraphQLMetadata("authors")]
    public IEnumerable<Author> GetAuthors() 
    {
      using (var db = new StoreContext())
      {
        return db.Authors
        .Include(a => a.Books)
        .ToList();
      }
    }

    [GraphQLMetadata("author")]
    public Author GetAuthor(int id)
    {
      using (var db = new StoreContext())
      {
        return db.Authors
        .Include(a => a.Books)
        .SingleOrDefault(a => a.Id == id);
      }
    }

    [GraphQLMetadata("hello")]
    public string GetHello()
    {
      return "World";
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Above we have replaced all our stubbed code with calls to the Database. Let's go through the relevant methods:

GetBooks

[GraphQLMetadata("books")]
public IEnumerable<Book> GetBooks()
{
  using(var db = new StoreContext())
  {
    return db.Books
    .Include(b => b.Author)
    .ToList();
  }
}
Enter fullscreen mode Exit fullscreen mode

Above we are selecting all the Books from the database and also ensuring we include the Author property. This is so we support a query like:

{
  books {
    name,
    author {
      name
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

GetAuthors

[GraphQLMetadata("authors")]
public IEnumerable<Author> GetAuthors() 
{
  using (var db = new StoreContext())
  {
    return db.Authors
    .Include(a => a.Books)
    .ToList();
  }
}
Enter fullscreen mode Exit fullscreen mode

Here we are selecting all the Authors from the database and we also include all the books written by that author. This is so we support a query like so:

{
  authors {
    name,
    books {
      name,
      published
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Mutation.cs

Open up Mutation.cs and replace its stubbed code with:

using Api.Database;
using GraphQL;

namespace Api.Graphql 
{
  [GraphQLMetadata("Mutation")]
  public class Mutation 
  {
    [GraphQLMetadata("addAuthor")]
    public Author Add(string name)
    {
      using(var db = new StoreContext()) 
      {
        var author = new Author(){ Name = name };
        db.Authors.Add(author);
        db.SaveChanges();
        return author;
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

As you can see above we support the mutation addAuthor(name: String): Author by creating a new author, save it to the database and then returning the entity.

Test it out in GraphiQL

We have one last thing to do namely to test this out in our visual interface. Hit Debug/Start Debugging and let's see what happens:

It seems like our query to list books work fine, it gives us the titles from the database.

Next, let's try to carry out a mutation:

Judging from the above result that seems to have gone well, awesome! :)

Summary

This was quite an ambitious article. We managed to create a GraphQl Api within a Web Api. We also managed to involve a database that we accessed with Entity Framework.

The big takeaway from this was not only how easy this was to set up but the wonderful visual environment GraphiQL. It not only helped us with auto-completion but documented our schema, helped us verify our queries and more.

Hope you found this useful albeit a somewhat long read.

As a final comment, I would like to say that webapi project type comes with built-in Dependency Injection that I was unable to use. The main reason was that this way of setting up GraphQL meant that we weren't in control of instantiating Query and Mutation. You can see in the references section how I point to an article doing what we did here today and managed to use DI. However, you have to set up your GraphQL schema in a very different way, that IMO is much more verbose.

Discussion (26)

pic
Editor guide
Collapse
jpeg729 profile image
jpeg729

When querying list of Books or Authors, the database access is inefficient in that it will load all properties of the books/authors, and the linked entities, even if they aren't needed.

I know Entityframework can do magic with Select on queryables, and I realise that exposing the structure of the data storage to external queries is potentially problematic, so there isn't an obvious optimisation strategy.

What are your current thoughts on this problem?

Collapse
softchris profile image
Chris Noring Author

hi. There are many approaches to optimizing GraphQL. Adding pagination and limiting the depth at which you can query is a good start. Also adding a layer between the resolvers and the database is preferred. Some data almost never change and can be put in a cache and some other needs to be reread each time. As for loading linked entities, EF will only load what you ask for with include operator, so you need to be very specific what you actually want. Unless you have "lazy loading" enabled, which I don't recommend. I mean a lot of these things depends on how many records will be in each table, how will it be used etc. I wouldn't use this article's code in production unless at minimum paging is in place. I would also be very conservative how many levels I would allow a client to query for. This needs to be a living conversation with backend team and any consumers of this API.

Collapse
jondesam profile image
Jonghyun

Hi Chris, Thank you for great article:) I have followed it and just found your comment "I wouldn't use this article's code in production unless at minimum paging is in place."

Can you please let me know optimized structures for in production and guide me with source code examples such as Github repositories?

Thanks! :)

Collapse
nk54 profile image
NK54

Hello, nice article / tutorial ! I've discovered few things here.
I still face a problem : I don't have any documentation : it says "no schema available" any idea ? Maybe you can give a link to a repo where I can try your code to see if I made a typo somewhere.
You wrote "public string Id { get; set; }" in the Book class. But inserting new item throw an error because EntityFrameworkCore doesn't seem to handle string Id. I fixed that by switching to int. You may also want to add a note to seed the database in the main method before the call to CreateHostBuilder(args).Build().Run(); otherwise the in memory db will be empty.

It feels like GraphQL is awesome to build a product until all UI are done and at some point, maybe switching to pure EF queries (to avoid N+1) might be a better option if performance matters.

Have a good day and thanks again !

Collapse
softchris profile image
Chris Noring Author

hi there. Here's a repo github.com/softchris/graphql-ef-demo Let me know if that solves things for you /Chris

Collapse
nk54 profile image
NK54

Hey ! Thanks :)
It doesn't compile with .NET Core 3 / 3.1
Book id of type string throw an error.
The repo doesn't have right dependencies set : entityframeworkcore and entityframeworkcore.inmemory.

I've fixed those issues and I still don't see any documentation : no schema available. At least, I didn't make a typo and I can move on without getting a headache trying to find what's wrong :D

Have a good day !

Thread Thread
softchris profile image
Chris Noring Author

let me look at that and get back to you. Thanks for pointing out the above :)

Collapse
dolkensp profile image
Peter Dolkens

Hey Chris,

Is this the response to my comments on your previous GraphQL article (dev.to/dolkensp/comment/e380)?

Haven't had a chance to go over it properly yet, but am keen to see what you came up with if it is!

Collapse
softchris profile image
Chris Noring Author

I'm merely showing how to use EF in this article. It needs a separate article to talk about optimization

Collapse
hjrb profile image
hrj

Great article. I'll give a try. I don't like code first... It is always: schema first. But my personal taste.
In a not so far future we will rediscover the power of SQL :-) On a more serious note: there are actually .net / Jscript libraries available that allow a user to create and expression tree and send it.

Collapse
tdesaules profile image
Thibault DESAULES

Hi !

Just a question... I'm stuck on a stupid query :
I want to add something like that :

type Book {
id: ID
name: String,
genre: String,
published: Date,
Author: Author,
ref: Book
}

adding data like :
new Book
{
ID = 2,
Name = "IT",
Published = true,
AuthorId = authorDbEntry.Entity.Id,
Genre = "Mystery",
RefID = 1
},

that I should query like:
{
books {
name,
author {
name
},
ref {
name
}
}
}

Collapse
softchris profile image
Chris Noring Author

yea you would need an entry in the Query part supporting books, You would also need a specific resolver class for author and ref as they are complex objects that you mean to resolve from numbers into pure objects. Have a look here aka.ms/graphql-workshop-dotnet. Imagine books is reviews from my example

Collapse
tdesaules profile image
Thibault DESAULES

Thank you look exactly what I need with the "PersonResolver" i still have an id error when it try to resolved :/ need to figured it out

Collapse
apung profile image
Abdul Gaffur A Dama

Bookmarked, thanks anyway 🙏

Collapse
tsenseval profile image
tsenseval

Tx Chris, could we do subscription in dotnetcore ?

Collapse
softchris profile image
Collapse
alan5142 profile image
Alan Ramírez Herrera • Edited

Did you know hot chocolate?, in my opinion it has better support and feels simpler

Collapse
softchris profile image
Chris Noring Author

that looks very interesting. Thanks for the link Alan :)

Collapse
dvanherten profile image
Dave van Herten

Didn't even realize you could use the .net graphql library in this way! Thanks for the share.

Collapse
kamalashrafgill profile image
Kamal Ashraf Gill

Great article. Can we add some authentication to this like JWT?

Collapse
softchris profile image
Chris Noring Author

hi Kamal.. GRaphQL doesn't have a strong opinion of how to handle authorization. You can definitely add it as part of your Web Api though. Or where you looking how to do JWT generally in .Net Core? Happy to write such an article if that's the question :)

Collapse
kamalashrafgill profile image
Kamal Ashraf Gill

Hi Chris, thanks for the reply. Please write an article about JWT in .Net Core. It would be beneficial.

Thread Thread
softchris profile image
Collapse
aaiing profile image
AAI INGENIERIA

thanks for article, do you have a code example for login user and return only token and user data without password?

Collapse
bolapatil profile image
patil

Why we need graphql in dot net if we have OData???

Collapse
softchris profile image