Overview
In this lesson, we will:
- Explore the journey of how a client requests data and how a GraphQL server retrieves it
- Learn about the components that make up a GraphQL server
- Learn about the basics of the GraphQL schema syntax: SDL, or schema definition language
Journey of a GraphQL operation
Our app needs to fetch data for a particular page.
To get that data, it sends a GraphQL operation to our GraphQL server. The app shapes the operation as a string that defines the selection set of fields it needs. Then, it sends that operation to the server in an HTTP POST
or GET
request.
In server-land
When our server receives the HTTP request, it first extracts the string with the GraphQL operation. It parses and transforms it into something it can better manipulate: a tree-structured document called an AST (Abstract Syntax Tree). With this AST, the server validates the operation against the types and fields in our schema.
If anything is off (e.g. a requested field is not defined in the schema or the operation is malformed), the server throws an error and sends it right back to the app.
In this case, the operation looks good, and the server can "execute" it. Meaning, the server can continue its process and actually fetch the data. The server walks down the AST.
For each field in the operation, the server invokes that field's resolver function. A resolver function's mission is to "resolve" its field by populating it with the correct data from the correct source, such as a database or a REST API. These data sources don't necessarily need to live within the GraphQL server, they can be externally hosted.
In this way, GraphQL is a powerful bridge to REST (and other data sources!) that ties all of your app's data together. The GraphQL API acts as the layer on top of them, providing a single interface through which multiple data sources can be queried simultaneously.
As all of the operation's fields are resolved, the data is assembled into a nicely ordered JSON object with the exact same shape as the query.
The server assigns the object to the HTTP response body's data
key, and it's time for the return trip, back to our app.
Back to client-land
Our client receives the response with exactly the data it needs and passes that data to the right components to render them.
And that's the journey of a GraphQL operation!
Now that we have a birds-eye view of the full journey from client, to server, and back, we'll take some time to zoom into two pieces of the story: the anatomy of a GraphQL server and the anatomy of a GraphQL operation.
Anatomy of a GraphQL server
The GraphQL server is where all the magic happens. Let's dive deeper into the components that make it up.
The schema
The GraphQL schema is a collection of types and fields that make up the comprehensive picture of everything we can do with the data in a GraphQL server. No actual data lives here; just the basic skeleton of the shapes that the live data will conform to. (Think of a blueprint!)
Schema definition language (SDL)
The GraphQL schema has its own language called schema definition language, or SDL.
We'll briefly cover the syntax for SDL, but it's not the purpose of the course! In the next lesson, we'll see exactly how Hot Chocolate, the GraphQL server framework we'll be using, lets us implement GraphQL in C#, without worrying about the specifics of SDL syntax. However, it's still helpful to be familiar with SDL, since it will be the common language when discussing the schema between API and client app teams.
Let's look at an example of a schema.
type Fruit {name: String!quantity: IntaverageWeight: FloathasEdibleSeeds: Booleannutrients: [String]}
The Fruit
type is an example of an object type. We define an object type using the type
keyword, then the name of the object, followed by curly braces. Inside the curly braces, we define the fields associated with that type.
A field starts with the name of the field, then a colon (:
) and then the type for that field. In the example above, all the fields in the Fruit
type are scalar types.
The available scalar types in GraphQL are String
, Int
, Float
, Boolean
and ID
. These are similar to scalar types in any programming language. The square brackets ([]
) indicate a List
type.
We can visualize the schema as a graph, a data model of nodes and edges. Each object type or scalar type is a node and each relationship (the object's field) is an edge between two nodes.
Here's how C# types map to GraphQL types:
C# type | GraphQL type |
---|---|
string | String |
int | Int |
double | Float |
bool | Boolean |
string, int, long | ID |
List<string> | [String] |
In terms of nullability, GraphQL denotes non-nullable (or required) fields with an exclamation mark (!
) after the type.
In contrast, in C#, all types are non-nullable by default. Nullable types are denoted with a ?
.
Fields can also return object types. For example, in the schema below, we have a Vendor
type that contains a fruitCatalog
field, which returns a list of Fruit
types. We can also add a new field in the Fruit
type called soldBy
that returns a Vendor
type.
type Fruit {name: String!quantity: IntaverageWeight: FloathasEdibleSeeds: Booleannutrients: [String]soldBy: Vendor}type Vendor {name: String!fruitCatalog: [Fruit]}
This is where we really start to see the graph shine in GraphQL: when we model and traverse the relationships between our types.
To make use of this graph, we need an entry point into the graph.
Entry points
A GraphQL operation (the thing that the client sends to the GraphQL server) can either be a query, a mutation, or a subscription. A query reads data, a mutation changes data and a subscription listens for live, streaming data.
All three operations map to a corresponding type in the schema: Query
, Mutation
and Subscription
. Let's take the query as an example.
type Query {mostPopularFruit: Fruit}
We can think of the fields within the Query
type as the list of things we can ask for from our GraphQL API. Similarly, the Mutation
type is the list of things we can do with our GraphQL API.
The resolver functions
For every type and field in the GraphQL schema, we need to define a resolver function. This is a function that can retrieve the data for a specific field. These functions have access to various data sources: databases, REST APIs, even text files or JSON!
Resolvers for fields in our schema follow the hierarchy of the schema. This means that resolver functions for the name
and quantity
fields on a Fruit
type are methods that belong to a Fruit
class. This keeps our resolver functions just as organized as our schema.
Anatomy of a GraphQL operation
The journey starts with a GraphQL operation sent from the client. We described a GraphQL operation as "a string that defines the selection set of fields [the client] needs". When we write this operation, we use the schema as our reference guide for what types and fields we have access to, and how they relate to each other.
We know that a GraphQL operation can be a query, a mutation or a subscription. Let's check out what an example of a query might look like:
query GetMostPopularFruit {mostPopularFruit {namehasEdibleSeedssoldBy {name}}}
We can already see how the schema was used as a reference for this query.
First, we start with the type of GraphQL operation, in this case query
, followed by an operation name of our choosing, GetMostPopularFruit
, then curly braces. Inside, we can start to add fields from the Fruit
type: name
, hasEdibleSeeds
, soldBy
. And since soldBy
is a field that returns a Vendor
type, we add fields from that type as well, like name
.
Visually, we can also follow this as traversing the graph.
In this course, we'll be using a tool called Apollo Sandbox Explorer that will help us build queries easily, without needing to know the specifics of the syntax we should be following.
Practice
Drag items from this box to the blanks above
changes
question
screenshots
connection
subscription
query
Key takeaways
- There are three types of GraphQL operations: queries, mutations and subscriptions. A query reads data, a mutation changes data and a subscription listens for live, streaming data.
- The GraphQL schema is a collection of types and fields that make up the comprehensive picture of everything we can do with the data in a GraphQL server. It is written in schema definition language (SDL).
- A resolver function retrieves the data for a specific field in our schema. These functions have access to various data sources: databases, REST APIs, even text files or JSON. These data sources don't need to live within the GraphQL server.
Up next
This course is all about building a GraphQL server, so let's get into it! Grab a warm drink and get ready, it's time to meet Hot Chocolate.
Share your questions and comments about this lesson
This course is currently in
You'll need a GitHub account to post below. Don't have one? Post in our Odyssey forum instead.