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.
Top comments (26)
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?
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.
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! :)
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 !
hi there. Here's a repo github.com/softchris/graphql-ef-demo Let me know if that solves things for you /Chris
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 !
let me look at that and get back to you. Thanks for pointing out the above :)
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!
I'm merely showing how to use EF in this article. It needs a separate article to talk about optimization
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.
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
}
}
}
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 exampleThank 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
Bookmarked, thanks anyway 🙏
Tx Chris, could we do subscription in dotnetcore ?
hi yes have a look here, graphql-dotnet.github.io/docs/gett...
Did you know hot chocolate?, in my opinion it has better support and feels simpler
that looks very interesting. Thanks for the link Alan :)
Didn't even realize you could use the .net graphql library in this way! Thanks for the share.
Great article. Can we add some authentication to this like JWT?
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 :)
Hi Chris, thanks for the reply. Please write an article about JWT in .Net Core. It would be beneficial.
will do :)