How you can build a Serverless API using GraphQL .Net Core, C# and VS Code
Follow me on Twitter, happy to take your suggestions on topics or improvements /Chris
This article takes you through how to build a full CRUD API using GraphQL. We end up hosting it in a Serverless function and also show how you can do external HTTP calls to build your API. Whether the data lives here or somewhere else doesn't matter. GraphQL is the front that your users will talk to.
TLDR; This article might be a bit long but it does teach a lot on GraphQL, queries, mutations. It also teaches you how to create a serverless function all in the context of .Net Core, C# and VS Code
In this article we will:
- Learn how to build a full CRUD GraphQL including queries and mutations
- Create a Serverless function and Host our GraphQL API in a function
- Show how you can make an external HTTP call and make that part of our GraphQL API
Resources
Getting started with GraphQL in .Net This is the first article I've written on the topic of GraphQL with .Net. I recommend you have a look to get a feeling for the basics of GraphQL.
Starting with Azure Functions Covers creating a Serverless function and deploying to the Cloud using VS Code
Starting with Serverless in .Net .Net C# Developer reference to Azure Functions
Creating a Serverless API in C# and .Net Article I wrote covering how to create a CRUD API with Serverless, .Net, C#, and VS Code
Create your first Serverless function LEARN module for creating an Azure Function
Free Azure account To deploy Serverless Azure Functions you will need a free Azure account)
Create a Serverless function app
The first thing we will do is to create a Serverless function. But what is Serverless and why might it be useful in the context of GraphQL? That's really two questions but let's try to answer them in order.
Why Serverless?
Serverless is not about no servers but more about the server is NOT in your basement, anymore. In short, the servers have moved to the Cloud. That's not all, however. Serverless have come to mean other things namely that everything is set up for you. That means you don't need to think about what OS your code runs on or what Web Server runs it, everything is managed. There is more.
There is always more, isn't it? What now a fancy sticker?
It's about cost. Serverless usually means it's cheap.
Why is it cheap?
Well, Serverless code is meant to be run seldom and you are only billed for the time it executes
Ok, how do YOU make money?
Well, the function isn't always there. Whatever resources are needed are allocated when someone/something queries your function.
Doesn't that make it a bit slow like there could be some initial wait when it's scrambling to be created and then respond the query?
You are right. This is something called a cold start. This can be avoided however by either regularly polling our function or using a premium offering that shortens the cold start.
Ok so it's I can either have 100% availability or cheap, pick one π
I guess so.
Why Serverless with GraphQL?
Ok, we are a bit smarter about the whats and whys of Serverless so why add GraphQL to the mix?
Well, GraphQL has the ability to stitch together data from different APIs so it could act as an API that aggregates data from different sources. So in a sense, it works well in the context of Serverless cause it's not like you need to have any data stored in the function if it's someone else's API.
Ok sounds good, I guess there are no other good reasons the rest is just about the hype that's GraphQL and Serverless right? π
...
Prerequisits
To create a Serverless function we first need a function app to put it in. To makes this as easy as possible we will be using VS Code and an Azure Function extension. So our prerequisites are:
- Node.js
- Visual Studio Code
- Azure Functions extension
We can download Node.js from the following page:
https://nodejs.org/en/
You can find Visual Studio code here:
https://code.visualstudio.com/download
As for the extension we need. Search for an extension called Azure Functions
. It should look like the below:
Scaffold
Ok, now we should be all setup and ready to create our first Serverless function. As mentioned before, the function needs to live within something called a function app. So we need to create the function app first.
Hit CMD+SHIFT+P
or choose View/Command Palette
to bring up the command palette:
Now we need to type a command that will help us create a Serverless function app. It's called Azure Functions: Create New Project
Then it's going to ask you for what directory to create the app. The directory you are standing in is the default chosen one, select that.
Next thing it asks about is the language, select C#
.
Next up it's asking you about the projects first function and how it should be triggered. Select HttpTrigger
.
Now you need to provide a function name. Call it Graphql.
Next question is a namespace. You could really choose anything here but let's call it Function
just for the sake of it.
Lastly, it asks about Access Right
. There are different options here Anonymous
, Function
, Admin
. We select Anonymous
as we want to make our function publically available. The other options mean we would need to provide some kind of key when calling the function.
VS Code should now scaffold all the needed files. It should also ask you to restore
all dependencies, to download the libraries. You could also run
dotnet restore
if you should miss clicking on this dialog.
Your project structure should now look like this:
Test it out
Let's ensure that we can run our newly scaffolded function. First, you should have been prompted if you want to add the necessary to be able to debug. You should answer YES
here. This will generate a directory .vscode
looking like the below:
The tasks.json
contains tasks needed to build, clean and run your project. These will assist you with the step we are about to do next Debugging
.
To Debug we choose Debug/Start Debugging
from the menu. It should compile the code and once done it should look like this:
It's telling us to go to http://localhost:7071/api/Graphql
Let's spin up a browser:
Seems to work alright. π
It's hitting our function Graphql
. Speaking of which, let's have a quick look at the sample code we've been given when we scaffolded the Serverless app:
// Graphql.cs
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace Function
{
public static class Graphql
{
[FunctionName("Graphql")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string name = req.Query["name"];
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
return name != null
? (ActionResult)new OkObjectResult($"Hello, {name}")
: new BadRequestObjectResult("Please pass a name on the query string or in the request body");
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Let's not spend to much time understanding everything right now but suffice to say it does its job and is able to work with query parameters:
string name = req.Query["name"];
and the Body, in case you POST a request:
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
2
3
Next, let's talk GraphQL.
##Β Adding GraphQL to Serverless
Ok, we established that we wanted to add GraphQL to our Serverless function. First, let's build a GraphQL API. Any GraphQL will contain the same moving parts:
- A Schema, this will define what we can query for and what custom data types we have
- A Resolver, a collection of functions that is able to respond to a request and ends up delivering a response
Adding a GraphQL schema
This will be authored in something called GQL or GraphQL Query Language. Our schema will look like the following:
type Jedi {
id: ID
name: String,
side: String
}
input JediInput {
name: String
side: String
id: ID
}
type Mutation {
addJedi(input: JediInput): Jedi
updateJedi(input: JediInput ): Jedi
removeJedi(id: ID): String
}
type Query {
jedis: [Jedi]
jedi(id: ID): Jedi
hello: String
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Now that's a lot. Let's explain what we are looking at.
Query
Anything of type Query
or Mutation
is something we can ask for in our query to our API. The semantic meaning of Query
is that you want to fetch data. In this case, we want to fetch a list of jedis using a query syntax like this:
{
jedis { name side }
}
2
3
This is equivalent to writing the following in SQL:
SELECT name, side
FROM jedis;
2
The other thing we do is supporting a query with a parameter, namely jedi(id: ID): Jedi
. We call it like so:
{
jedi(id: 1) { name }
}
2
3
This is equivalent to writing the following in SQL:
SELECT name
FROM jedi
WHERE id=1;
2
3
Custom types
Everything queryable was defined under type Query
. Everything else except for type Mutation
are custom types that we define. For example:
type Jedi {
id: ID
name: String,
side: String
}
2
3
4
5
Mutation
This semantically means we will try to change the data. Looking at the operations we support:
addJedi(input: JediInput): Jedi
updateJedi(input: JediInput ): Jedi
removeJedi(id: ID): String
2
3
We can see that we support adding, updating and removal.
To call a mutation we need to type like so:
mutation test {
addJedi(input: {
name: "JarJar",
side: "Dark"
}) { name }
}
2
3
4
5
6
Input, complex input for our Mutation
Note the input property input
in addJedi()
. If we look at the schema we can see that it's of type JediInput
which is defined like this:
input JediInput {
name: String
side: String
id: ID
}
2
3
4
5
The reason we are using the keyword input
instead of type
is that this is a special case. Special how, you wonder? Well, there are two types of input parameters to a mutation:
- Scalars, this is a String, ID, Boolean, etc, also called primitives
- Inputs, this is nothing more than a complex data type with many properties on it, example
JediInput
so it's definitely possible to define a mutation looking like this:
type Mutation {
addTodo(todo: String!): String
}
2
3
So the million-dollar question is why can't I just use a custom type like Jedi
as an input parameter type to my mutation?
The honest answer is I don't know.
Just remember this: If you need an input parameter that is more complex than a scalar, then you need to define it like so:
input MyInputType {
// my columns
}
2
3
Add NuGet package
Ok next step is to set up this schema properly in code. For that we will create a file Server.cs
and also install the GraphQL package like so:
dotnet add package GraphQL
Create a Schema
Now add the following code to Server.cs
, like so:
using GraphQL;
using GraphQL.Types;
using Newtonsoft.Json;
using System.Threading.Tasks;
namespace Function {
public class Server
{
private ISchema schema { get; set; }
public Server()
{
this.schema = Schema.For(@"
type Jedi {
id: ID
name: String,
side: String
}
input JediInput {
name: String
side: String
id: ID
}
type Mutation {
addJedi(input: JediInput): Jedi
updateJedi(input: JediInput ): Jedi
removeJedi(id: ID): String
}
type Query {
jedis: [Jedi]
jedi(id: ID): Jedi
}
", _ =>
{
_.Types.Include<Query>();
_.Types.Include<Mutation>();
});
}
public async Task<string> QueryAsync(string query)
{
var result = await new DocumentExecuter().ExecuteAsync(_ =>
{
_.Schema = schema;
_.Query = query;
});
if(result.Errors != null) {
return result.Errors[0].Message;
} else {
return JsonConvert.SerializeObject(result.Data);
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
In the constructor above we set up the Schema by calling Schema.For()
with a string representing our schema expressed as GQL. The second argument of that won't compile though, namely this part:
_ =>
{
_.Types.Include<Query>();
_.Types.Include<Mutation>();
}
2
3
4
5
Adding resolvers
The reason is it won't compile is that Query
and Mutation
don't exist yet. They are simply resolver classes answering to query and mutation requests. Let's make it compile by creating first Db.cs
and a file Query.cs
Adding in-memory database
// Db.cs
using System.Collections.Generic;
using System.Linq;
namespace Function
{
public class StarWarsDB
{
private static List<Jedi> jedis = new List<Jedi>() {
new Jedi(){ Id = 1, Name ="Luke", Side="Light"},
new Jedi(){ Id = 2, Name ="Yoda", Side="Light"},
new Jedi(){ Id = 3, Name ="Darth Vader", Side="Dark"}
};
public static IEnumerable<Jedi> GetJedis()
{
return jedis;
}
public static Jedi AddJedi(Jedi jedi)
{
jedi.Id = jedis.Count + 1;
jedis.Add(jedi);
return jedi;
}
public static Jedi UpdateJedi(Jedi jedi)
{
var toUpdate = jedis.SingleOrDefault(j => j.Id == jedi.Id);
toUpdate.Name = jedi.Name;
toUpdate.Side = jedi.Side;
return toUpdate;
}
public static string RemoveJedi(int id)
{
var toRemove = jedis.SingleOrDefault(j => j.Id == id);
jedis.Remove(toRemove);
return "success";
}
}
public class Jedi
{
public int Id { get; set; }
public string Name { get; set; }
public string Side { get; set; }
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
Db.cs
is nothing more than a simple in-memory database.
Adding a resolver class to handle all Queries
Next, let's create Query.cs
:
// Query.cs
using System.Collections.Generic;
using GraphQL;
using System.Linq;
namespace Function
{
public class Query
{
[GraphQLMetadata("jedis")]
public IEnumerable<Jedi> GetJedis()
{
return StarWarsDB.GetJedis();
}
[GraphQLMetadata("jedi")]
public Jedi GetJedi(int id)
{
return StarWarsDB.GetJedis().SingleOrDefault(j => j.Id == id);
}
[GraphQLMetadata("hello")]
public string GetHello()
{
return "World";
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
What's interesting about the above is how we map something in our GraphQL schema to a resolver function. For that we use the decorator GraphQLMetadata
like so:
[GraphQLMetadata("jedis")]
public IEnumerable<Jedi> GetJedis()
{
return StarWarsDB.GetJedis();
}
2
3
4
5
The above tells us that if the user queries for jedis
then the function GetJedis()
will respond.
Dealing with parameters is almost as easy. It's the same decorator but we just add input parameter like so:
[GraphQLMetadata("jedi")]
public Jedi GetJedi(int id)
{
return StarWarsDB.GetJedis().SingleOrDefault(j => j.Id == id);
}
2
3
4
5
Adding a resolver class to handle all Mutation requests
We are almost there but we need the class Mutation.cs
and let's add the following content to it:
// Mutation.cs
using GraphQL;
namespace Function {
public class Mutation
{
[GraphQLMetadata("addJedi")]
public Jedi AddJedi(Jedi input)
{
return StarWarsDB.AddJedi(input);
}
[GraphQLMetadata("updateJedi")]
public Jedi UpdateJedi(Jedi input)
{
return StarWarsDB.AddJedi(input);
}
[GraphQLMetadata("removeJedi")]
public string AddJedi(int id)
{
return StarWarsDB.RemoveJedi(id);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
As you can see it looks very similar to Query.cs
, even with parameter handling.
Updating our Serverless function
There's just one piece left of the puzzle, namely our serverless function. It needs to be altered so we can support the user to invoke our function like so:
url?query={ jedis } { name }
Change the code in Graphql.cs
to the following:
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace Function
{
public static class GraphQL
{
[FunctionName("GraphQL")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
var server = new Server();
string query = req.Query["query"];
// string query = "mutation test { addJedi(input: { name: \"JarJar\", side: \"Dark\" }) { name } }";
var json = await server.QueryAsync(query);
return new OkObjectResult(json);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Test it all out
To test it we just start debugging by choosing Debug/Start Debugging
from the menu and change the URL in the browser to:
http://localhost:7071/api/GraphQL?query={ jedis { name } }
Let's see what the browser says:
Yep, we did it. Serverless function with a GraphQL API.
Bonus - calling other endpoints
Now. One of the great things about GraphQL is that it allows us to call other APIs and thereby GraphQL acts as an aggregation layer. We can easily achieve that by the following steps:
- Do the HTTP request, this should do the request to our external API
- Add resolver method for our external call
- Update our schema with the new type
- Test it out
HTTP Request
We can easily do HTTP request in .Net with HttpClient
so let's create a class Fetch.cs
like so:
// Fetch.cs
using System.Net.Http;
using System.Threading.Tasks;
namespace Function {
public class Fetch
{
private static string BaseUrl = "https://swapi.co/api";
public static async Task<string> ByUrl(string url)
{
using (var client = new HttpClient())
{
var json = await client.GetStringAsync(string.Format("{0}/{1}", BaseUrl, url));
return json;
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Add resolver method
Now open up Query.cs
and add the following method to the Query
class:
[GraphQLMetadata("planets")]
public async Task<List<Planet>> GetPlanet()
{
var planets = await Fetch.ByUrl("planets/");
var result = JsonConvert.DeserializeObject<Result<List<Planet>>>(planets);
return result.results;
}
2
3
4
5
6
7
Additionally, we should be installing a new NuGet package:
dotnet add package Newtonsoft.Json
This is needed so we can convert the JSON response we get into a Poco.
We should also add types Planet
and Result
to Query.cs
, but outside the class definition:
public class Result<T>
{
public int count { get; set; }
public T results { get; set; }
}
public class Planet
{
public string name { get; set; }
}
2
3
4
5
6
7
8
9
10
Update our schema with the new type
We have the schema left to update before we can try it out. So let's open up Schema.cs
and make sure it now looks like this:
this.schema = Schema.For(@"
type Planet {
name: String
}
type Jedi {
id: ID
name: String,
side: String
}
input JediInput {
name: String
side: String
id: ID
}
type Mutation {
addJedi(input: JediInput): Jedi
updateJedi(input: JediInput ): Jedi
removeJedi(id: ID): String
}
type Query {
jedis: [Jedi]
jedi(id: ID): Jedi
hello: String
planets: [Planet]
}
", _ =>
{
_.Types.Include<Query>();
_.Types.Include<Mutation>();
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Above we've added the type Planet
:
type Planet {
name: String
}
2
3
We've also added planets
to Query
:
planets: [Planet]
Testing it out
That should be it, let's test it. Debug/Start Debugging
:
http://localhost:7071/api/GraphQL?query={ planets { name } }
There we have it. π
We've managed to do an external call to an API and make that part of our GraphQL API. As you can see we can intermix data from different sources easily.
Summary
We've managed to create a GraphQL that supports the full CRUD. That is we support both querying for data but also creating, update and delete.
Additionally, we've taken our first steps with Serverless functions.
Finally, we added our GraphQL API to be served by our Serverless function.
As an added bonus we also showed how we could take to external APIs and make them part of our API. That's really powerful stuff.
I hope you enjoyed this article. In out next part we will take a look at how to add GraphQL to a Web API project in .Net Core.