- Part 1 - Getting Started
- Part 2 - App Service with dotnet
- Part 4 - CosmosDB and GraphQL
- Part 5 - Can We Make GraphQL Type Safe in Code? (this post)
- Part 6 - GraphQL Subscriptions with SignalR
- Part 7 - Server-Side Authentication
- Part 8 - Logging
- Part 9 - REST to GraphQL
- Part 10 - Synthetic GraphQL Custom Responses
- Part 11 - Avoiding DoS in queries
- Part 12 - GraphQL as a Service
- Part 13 - Using React with DAB and SWA
- Part 14 - Using Blazor with DAB and SWA
I’ve been doing a lot of work recently with GraphQL on Azure Functions and something that I find works nicely is the schema-first approach to designing the GraphQL endpoint.
The major drawback I’ve found though is that you start with a strongly typed schema but lose that type information when implementing the resolvers and working with your data model.
So let’s have a look at how we can tackle that by building an application with GraphQL on Azure Functions and backing it with a data model in CosmosDB, all written in TypeScript.
To learn how to get started with GraphQL on Azure Functions, check out the earlier posts in this series.
Creating our schema
The API we’re going to build today is a trivia API (which uses data from Open Trivia DB as the source).
We’ll start by defining a schema that’ll represent the API as a file named
schema.graphql within the graphql folder:
Our schema has defined two core types,
Answer, along with a few queries and a mutation and all these types are decorated with useful GraphQL type annotations, that would be useful to have respected in our TypeScript implementation of the resolvers.
Creating a resolver
Let’s start with the query resolvers, this will need to get back the data from CosmosDB to return the our consumer:
This matches the query portion of our schema from the structure, but how did we know how to implement the resolver functions? What arguments do we get to
getRandomQuestion? We know that
question will receive an
id parameter, but how? If we look at this in TypeScript there’s
any all over the place, and that’s means we’re not getting much value from TypeScript.
Here’s where we start having a disconnect between the code we’re writing, and the schema we’re working against.
Enter GraphQL Code Generator
Thankfully, there’s a tool out there that can help solve this for us, GraphQL Code Generator. Let’s set it up by installing the tool:
And we’ll setup a config file named
config.yml in the root of our Functions app:
This will generate a file named
generated.ts within the
graphql folder using our
schema.graphql as the input. The output will be TypeScript and we’re also going to generate the resolver signatures using the
typescript-resolvers plugins, so we best install those too:
It’s time to run the generator:
Strongly typing our resolvers
We can update our resolvers to use this new type information:
Now we can hover over something like
id and see that it’s typed as a
string, but we’re still missing a piece, what is
dataStore and how do we know what type to make it?
Creating a data store
Start by creating a new file named
data.ts. This will house our API to work with CosmosDB, and since we’re using CosmosDB we’ll need to import the node module:
Why CosmosDB? CosmosDB have just launched a serverless plan which works nicely with the idea of a serverless GraphQL host in Azure Functions. Serverless host with a serverless data store, sound like a win all around!
With the module installed we can implement our data store:
This class will receive a
CosmosClient that gives us the connection to query CosmosDB and provides the two functions that we used in the resolver. We’ve also got a data model,
QuestionModel that represents how we’re storing the data in CosmosDB.
To create a CosmosDB resource in Azure, check out their quickstart and here is a data sample that can be uploaded via the Data Explorer in the Azure Portal._
To make this available to our resolvers, we’ll add it to the GraphQL context by extending
If we run the server, we’ll be able to query the endpoint and have it pull data from CosmosDB but our resolver is still lacking a type for
dataStore, and to do that we’ll use a custom mapper.
Custom context types
So far, the types we’re generating are all based off what’s in our GraphQL schema, and that works mostly but there are gaps. One of those gaps is how we use the request context in a resolver, since this doesn’t exist as far as the schema is concerned we need to do something more for the type generator.
Let’s define the context type first by adding this to the bottom of
Now we can tell GraphQL Code Generator to use this by modifying our config:
We added a new
config node in which we specify the
contextType in the form of
<path>#<type name> and when we run the generator the type is used and now the
dataStore is typed in our resolvers!
It’s time to run our Function locally.
And let’s query it. We’ll grab a random question:
Unfortunately, this fails with the following error:
Cannot return null for non-nullable field Question.answers.
If we refer back to our
Question type in the GraphQL schema:
This error message makes sense as
answers is a non-nullable array of non-nullable strings (
[String!]!), but if that’s compared to our data model in Cosmos:
Well, there’s no
answers field, we only have
It’s time to extend our generated types a bit further using custom models. We’ll start by updating the config file:
mappers section, we’re telling the generator when you find the
Question type in the schema, it’s use
QuestionModel as the parent type.
But this still doesn’t tell GraphQL how to create the
answers field, for that we’ll need to define a resolver on the
These field resolvers will receive a parent as their first argument that is the
QuestionModel and expect to return the type as defined in the schema, making it possible to do mapping of data between types as required.
If you restart your Azure Functions and execute the query from before, a random question is returned from the API.
We’ve taken a look at how we can build on the idea of deploying GraphQL on Azure Functions and looked at how we can use the GraphQL schema, combined with our own models, to enforce type safety with TypeScript.
We didn’t implement the mutation in this post, that’s an exercise for you as the reader to tackle.
You can check out the full example, including how to connect it with a React front end, on GitHub.
This article is part of #ServerlessSeptember (https://aka.ms/ServerlessSeptember2020). You’ll find other helpful articles, detailed tutorials, and videos in this all-things-Serverless content collection. New articles from community members and cloud advocates are published every week from Monday to Thursday through September.
Find out more about how Microsoft Azure enables your Serverless functions at https://docs.microsoft.com/azure/azure-functions/