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

Chris Noring - Aug 29 '19 - - Dev Community

 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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .