GraphQL Schema, Object Types, and Function Examples
In this lesson, we introduce and discuss some of GraphQL's main concepts such as the GraphQL schema, object types and resolver functions.
Get the project source code below, and follow along with the lesson material.
Download Project Source CodeTo set up the project on your local machine, please follow the directions provided in the README.md
file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.
GraphQL Concepts
📝 This lesson's quiz can be found - here.
🗒️ Solutions for this lesson's quiz can be found - here.
📖 This lesson's lecture slides can be found - here.
Now that we've seen the advantages of using GraphQL in a real-world example, let's dive into some core concepts. After interacting with Github's GraphQL API in the previous lesson, we know that the GraphQL query language allows us to select properties on certain object types.
For example, assume we could query the title
and price
fields on a listing
object type that we can create.
{
listing {
title
price
}
}
This will return data that looks something like the following:
{
"data": {
"listing": {
"title": "Chic condo...",
"price": 50
}
}
}
How do we know that the listing
object has a title
or a price
property? Does the listing
object have more properties we can select? This is where the GraphQL Schema comes in.
Every GraphQL API has a schema which completely describes all the possible data we can request. The schema is the blueprint of a GraphQL API. When a request comes in, it is validated against this schema and processed accordingly.
How do fields in the schema get processed? That's due to the second piece of a GraphQL API - the resolvers. GraphQL resolvers are functions that turn a GraphQL operation or request into data.
We'll first talk about the GraphQL schema before we dive into discussing GraphQL resolvers.
Object types
As quoted from the GraphQL documentation, the most basic component of a GraphQL schema are the object types which represent the kind of object we can query and what properties that object has. For the example above, we could have had an object type like the following:
type Listing {
id: ID!
title: String!
address: String!
price: Int!
}
The above states that:
- We're creating an object type called
Listing
. - The
Listing
object has a property calledid
which is a GraphQLID
. - The
Listing
object has a property calledtitle
which is a GraphQLString
. - The
Listing
object has a property calledaddress
which is a GraphQLString
. - The
Listing
object has a property calledprice
which is a GraphQLInt
.
The definitions at the end of these fields refer to the type that is to be returned from the field. (e.g. title
is expected to return a string
).
The syntax we're looking at here is known as the Graphql Schema Language with which we're going to talk about some more as we start to introduce GraphQL into our Node application.
A GraphQL schema allows us to develop relationships between different object types. For example, we can say the Listing
object type can have a tenant
field in which tenant
is to be an object type of its own (e.g the User
object type).
type Listing {
"..."
tenant: User!
}
The User
object type can have a similar but inverse relationship of its own where it can contain a listing
field of the Listing
object type.
type User {
"..."
listing: Listing!
}
These are one-to-one relationships where a listing can return a particular user and a user can return a certain listing. We could also have one-to-many relationships where we can say, for example, the User
object type has a listings
field that returns a list of listings.
type User {
"..."
listings: [Listing!]!
}
In the GraphQL Schema Language, we can define a GraphQL list by using the square brackets syntax. In this case, we're stating the
listings
field is to return a list ofListing
objects -[Listing!]!
.The
!
syntax is to tell our schema that the fields are marked as non-null (i.e. the field is required to be of a certain type and nevernull
).We discuss more about lists and the non-null marker at the end of this lesson and in a lot more detail in the upcoming lessons.
This lesson preview is part of the The newline Guide to Building Your First GraphQL Server with Node and TypeScript course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.
Get unlimited access to The newline Guide to Building Your First GraphQL Server with Node and TypeScript, plus 70+ \newline books, guides and courses with the \newline Pro subscription.
[00:00 - 00:18] Now that we first hand saw the advantages of using GraphQL in a real-world example, let's dive into some core concepts. After playing around with the GitHub's GraphQL API in the previous lesson, we know that the GraphQL query language allows us to select properties on object types.
[00:19 - 00:38] For example, querying something like this, a listing object type, that has a title and a price field would allow us to gain data like this, where title and price return some information. But how do we know that the listing object type has a title or price property?
[00:39 - 00:44] Does it have more properties we can select? This is where the GraphQL schema comes in.
[00:45 - 00:56] Every GraphQL API has a schema which completely describes all the possible data we can request. Another good way to look at it is that the schema is the blueprint of a GraphQL API.
[00:57 - 01:05] When a request comes in, it's validated against that schema and processed accordingly. How does fields in the schema get processed?
[01:06 - 01:15] That's due to the second piece of a GraphQL API, the resolvers. Resolvers are functions that turn a GraphQL operation or request into data.
[01:16 - 01:35] We'll first talk about a GraphQL schema before we dive into discussing the topics within GraphQL resolvers. The most basic components of a GraphQL schema are the object types, which represent some kind of object we can query and what properties that object has.
[01:36 - 01:44] In the previous example, we could have an object like so. This type definition says this is an object type called listing.
[01:45 - 01:58] This listing object has a property called ID, which is a GraphQL ID. It also has a property called title, which is a string address, which is also a string and price, which is an integer.
[01:59 - 02:11] The definitions at the end of these fields refer to the type that is to be returned from these fields. The title fields expected to return a string, the address fields expected to return a string and so on.
[02:12 - 02:22] The syntax we're looking at here is known as the GraphQL schema language. We're going to talk about this some more as we start to introduce it into our node application.
[02:23 - 02:39] A GraphQL schema allows us to develop relationships between different object types. For example, we can say the listing object type can have a tenant field in which tenant is to be an object type of its own, for example, a user object type.
[02:40 - 02:57] Similarly, the user object type can have a similar but inverse relationship of its own, where it can contain a listing field that is to be the listing object type. These are sort of one-to-one relationships where listing can return a particular user and the user can return a particular listing.
[02:58 - 03:13] We could also have one-to-many relationships where we can example say the user object type has a listings field that return a list of listings. In the GraphQL schema language, we can define a GraphQL list by using the square brackets syntax.
[03:14 - 03:27] Here, which states we're returning a list of the listing object type. There are two special object types within a GraphQL schema called the query and mutation object types.
[03:28 - 03:39] Every GraphQL schema must have a query type and may or may not have a mutation type. Query and mutation represent the entry points of every GraphQL query.
[03:40 - 03:52] The purpose of a query type is to define all the possible entry points for fetching data. The purpose of the mutation type is to define all the possible entry points for manipulating data.
[03:53 - 04:11] Here's an example of a query object type that allows us to query a field known as listings. Similarly, we can have a mutation object type that has a field labeled "Delete Listing" which takes ideas and arguments and according to its name would most likely delete a listing.
[04:12 - 04:29] The listings query and delete listing mutation both return variations of the listing type that we could have defined earlier. A GraphQL object type has one or more properties, but at some point, these properties must resolve to some concrete data.
[04:30 - 04:37] That's where these scalar types come in. They represent basic data types that do not have any more nested properties.
[04:38 - 05:08] GraphQL comes with a set of default scalar types out of the box. Boolean, true or false, int, assigned 32-bit integer, float, assigned double precision floating point number, in other words a decimal number, string, a UTF-8 character sequence, ID, which is similar to string, but it's used to represent a unique identifier in GraphQL , which is basically an identifier which is not intended to be human readable.
[05:09 - 05:22] Notice how some of these types are very similar to the basic types in Type Script? Inimoration types are a special kind of scalar type that are restricted to a defined set of allowed values.
[05:23 - 05:41] For example, let's say we wanted to add a listing type property for our listing object type, and we wanted to say the listing type can only be either a house or an apartment. This is where we can use an enumeration type to restrict the property to be one of these values.
[05:42 - 05:57] Objects, scalars, and enums are the only types we can define in GraphQL, but we can apply additional modifiers to affect our behavior. For example, let's say the listing object type is to have a bookings field.
[05:58 - 06:18] You've already seen this before, but in this particular case we're specifying the return to be a GraphQL list, which is denoted by the square brackets here in the Graph QL schema language. So we're stating that the bookings field is to return a list of values in which the values are to conform to the booking object type.
[06:19 - 06:33] Now one thing that we've glazed over that we haven't actually mentioned yet, notice that we haven't introduced any exclamation marks here. The exclamation mark refers to telling GraphQL that these fields have to conform to these types.
[06:34 - 06:48] In other words, these fields have to be required and can't be null values. In this case, we're saying that the bookings field could be a null value and the returned items within the array or list could also be a null value.
[06:49 - 07:15] When we look back at the other example we had before, by using the exclamation mark twice, we're saying we always expect a list with zero or more items, but in the particular list, we always expect every item to be defined as the listing type. Just like functions in any other programming language, we can pass arguments to fields.
[07:16 - 07:32] This is because fields are conceptually functions that return values. What if we needed to pass in multiple values to a create listing GraphQL field that will be able to resolve the intended results, which is have a listing be created.
[07:33 - 07:49] Just like we can pass in multiple arguments in a JavaScript function, we can pass in multiple arguments to a GraphQL field. In this case, we're saying this particular field expects the ID, title, address , and price arguments, and these are the respective types.
[07:50 - 08:14] Now, good convention will often find us creating input object types in situations like this, often valuable in the case of mutations, where we might want to pass in an entire object to be created. In the GraphQL schema language, input types look exactly the same as regular object types, but with the keyword input instead of type.
[08:15 - 08:32] In this case, we're saying the create listing field now takes a non-nullable input argument of type create listing inputs. We've come to understand that a GraphQL schema lays out the blueprint of how a GraphQL API is to be shaped.
[08:33 - 08:43] But how do these fields return values or make the changes we expect them to? We've mentioned this earlier, but this is where GraphQL resolvers come in.
[08:44 - 08:58] Resolvers are essentially functions or methods that resolve the value of a GraphQL field or GraphQL type. Every field in a GraphQL API is referenced with a resolver function responsible in returning the value of that field.
[08:59 - 09:18] With the original listings query we had in mind, a resolver can simply be a function that returns an array of listings that might have been defined elsewhere. The delete listing mutation resolver could have some additional functionality with regards to making the deletion before returning the expected value.
[09:19 - 09:38] And just like how these root level fields in the root mutation object types have the resolvers, the fields within the custom object types we can create have their own resolvers as well. For example, in this case we can say the ID is to resolve to an ID with an object titled to resolve to title within the object.
[09:39 - 09:59] These are known as trivial resolvers, so it's returning simply values within the past an object that is equal to the name of the field that we're actually declaring. So with that being said, you might be wondering what these objects or arguments or context arguments within the resolver function is.
[10:00 - 10:10] A GraphQL resolver would always receive four positional arguments. The first one is the object returned from the resolver on the parent field.
[10:11 - 10:22] For the root query and mutation object types, this argument is often not used in undefined. The second argument is the arguments provided to the field.
[10:23 - 10:51] The third argument is known as context, which is a value provided to every res olver and usually holds important context information, for example, the states of the currently logged in user. And finally, the fourth info argument is usually only used in advanced cases, but it contains information about the execution state of the query, such as the field name, the schema, the root value, etc.
[10:52 - 11:08] This introductory lesson only touches some of the main core concepts of GraphQL to give us a head start as we proceed. There are numerous other topics, both advanced and non-advanced like pagination , caching, union types, interfaces and so on.
[11:09 - 11:19] However, this will be more than enough to get us started and well on our way for the next couple of lessons. Keep in mind, we're going to reintroduce these topics as we begin to use them in the course.