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:
Resources
Building your first Graphql app in .Net Core
Teaches you how to create your first app in .Net Core using GraphQL. This talks about fundamental topics such as Query, Mutation, Resolvers and more.Building a Serverless Api with GraphQL in .Net Core
This covers how to build a Serverless API with an underlying GraphQL API.Article I based this article on
This is an excellent article in many ways. It does use a different and more verbose approach to setting up GraphQL. It's up to you which approach you want to useDeploy your .Net Core app to the Cloud
This teaches you how to deploy from VS CodeFree Azure account
To deploy Serverless Azure Functions you will need a free Azure account)
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
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:
- Install dependencies from NuGet
- Define a Schema with custom types, query types and mutations
- Create resolver functions that will respond to requests
- 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
Now install the dependencies like so:
dotnet add package GraphQL --version 2.4.0
dotnet add package graphiql --version 1.2.0
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
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>();
});
}
}
}
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
}
}
This would give a response like so:
{
"data": {
"books" : [{
"name": "IT",
"genre": "Horror",
"published": "1994"
}]
}
}
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
}
}
}
with the corresponding answer:
{
"data": {
"books" : [{
"name": "IT",
"genre": "Horror",
"published": "1994",
"author": {
"name": "Stephen King"
}
}]
}
}
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; }
}
}
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; }
}
}
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";
}
}
}
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;
}
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;
}
}
}
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:
- Map GraphiQL to
/graphql
- 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");
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
{
}
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;
}
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; }
}
}
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);
}
}
}
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:
-
Define a database in code, We do this by creating a class inheriting from
DbContext
and ensure it has fields in it of typeDbSet
-
Define the models we mean to use in the Database, we've actually already done this step when we created
Author
andBook
. - 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
- Seed the database, for the sake of this example we want some initial data so that when we query we get something back
- 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; }
}
}
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();
}
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";
}
}
}
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();
}
}
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
}
}
}
GetAuthors
[GraphQLMetadata("authors")]
public IEnumerable<Author> GetAuthors()
{
using (var db = new StoreContext())
{
return db.Authors
.Include(a => a.Books)
.ToList();
}
}
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
}
}
}
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;
}
}
}
}
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.