Use a schema to define what your
GraphQL API can provide.
Today you will look at a simple example of implementing GraphQL with Express. This will give us a chance to look at GraphQL from the server-side.
- Define a GraphQL Schema
- Use the GraphQL Schema Language
- Define a GraphQL Resolver
- Use GraphQL Queries
- Use GraphiQL
Discuss 🤼♀️
GraphQL and SQL are both Query languages. How do they differ?
Name three advantages of GraphQL 😎 over REST 😴
Use: https://swapi-graphql.eskerda.vercel.app to answer the following questions...
- Who is person 10?
- name?
- eyecolor?
- height?
- What movies did they appear in?
- totalCount?
- titles?
- What about vehicles?
- totalCount?
- names?
A GraphQL Schema 🛠 contains a set of Types that describe everything you can query from a service.
Schemas 🛠 are written in the GraphQL 😎 Schema language which is similar to the Query language.
SWAPI GraphQL might define a person 💁 like this:
type Person {
name: String!
eyeColor: String!
...
}
name
is a fieldString
is its type!
means the field is non-nullable (it will always return a value.)
type Person {
name: String!
height: Int!
eyecolor: String!
films: [Film!]!
}
You can use types like:
Int
Float
[Type]
(collection of type)
GraphQL includes these default types:
Int
: IntegerFloat
: DecimalString
: StringBoolean
: true or falseID
: Special type that represents a unique value[Type]
: Array of a type
The elements in a collection are typed and must all be the same type.
type MyType {
favNumbers: [Int!] # null, [], [1,4,7] No nulls allowed [1,null,7]
favFoods: [String!]! # [], ["a", "b"] Not null, or ["a", null]
favWebsites: [URL]! # [], ["http://", null], not null
favFavs: [Favs] # null, [], [Fav1, null Fav2] (where Favs is a type)
}
What about a Recipe 🍛 type:
type Recipe {
name: String! # Name is a string that can't be null
description: String # Description is a string and
# might be null
}
(the !
means a field must have a value)
A recipe 🍝 must have a list of ingredients.
type Recipe {
name: String!
description: String
ingredients: [String!]! # Must have a list of Strings
# and none of those strings can be
# null
}
The Recipe type needs more information:
type Recipe {
name: String!
description: String
ingredients: [String!]!
isSpicy: ? # what type?
isVegetarian: ? # what type?
}
What are the types for isSpicy
and isVegetarian
?
The GraphQL Schema language supports enumerations. ☎️
An enumeration ☎️ is a list of set values.
1️⃣ 2️⃣ 3️⃣
🍏 🍊 🍉
🙁 😄 😊
👽 👾 🤖
The Recipe type needs some more information:
enum MealType {
breakfast
lunch
dinner
}
type Recipe {
...
mealType: MealType! # Only "breakfast", "lunch" or "dinner"
}
(Validates and restricts values to one from the list)
Write an enum that defines the diet type:
- omnivore 🍱
- paleo 🍖
- vegetarian 🧁
- vegan 🥗
- insectivore 🐝
enum DietType {
ominvore
paleo
vegitarian
vegan
insectivore
}
type Recipe {
...
dietType: DietType!
}
An interface 🔌 is a description (like a contract) that describes types that conform to it.
Imagine characters 👯♂️ in the Star Wars films 🎬 could be humans 👷 or droids 🤖.
interface Character { # All characters have...
name: String!
films: [film!]!
}
type Human implements Character {
name: String! # Character
eyeColor: String! # Character
films: [film!]!
}
type Droid implements Character {
name: String! # Character
films: [film!]! # Character
primaryFunction: String!
}
(Anything that implements the interface must include the fields: name
and films
)
An interface 🔌 is also a type. For example:
type Film {
title: String!
cast: [Character!]! # Can be Humans or Droids
}
(Cast contains Humans or Droids, or any type with fields name and films)
Let's get started with GraphQL 😎 and Express 🚂.
The goal of this section is to create an Express server that implements GraphQL.
- Create a new folder
- Initialize a new npm project:
npm init -y
- Install dependencies:
npm install --save express express-graphql graphql
- Install nodemon:
npm i nodemon -g
- Create a new file:
server.js
Important! Be sure to include a .gitignore
. You need to prevent uploading your node_modules
folder to GradeScope!
How to add a .gitignore
: https://www.toptal.com/developers/gitignore/api/node
Edit package.json
"scripts": {
"start": "nodemon server.js"
}
If you don't have nodemon use: "start": "node server.js"
You can now run your project with:
npm start
Add the following to server.js
.
// Import dependancies
const express = require('express')
const { graphqlHTTP } = require('express-graphql')
const { buildSchema } = require('graphql')
Import dependancies
Build a schema. Add the following to server.js
.
// Create a schema
const schema = buildSchema(`
type About {
message: String!
}
type Query {
getAbout: About
}`)
The schema is written in the GraphQL schema language, buildSchema()
takes the schema as a string and returns a schema object
Define a resolver:
// Define a resolver
const root = {
getAbout: () => {
return { message: 'Hello World' }
}
}
A resolver is a function that's responsible for returning the results of a query. You might say a resolver resolves a query.
Add this to server.js
:
// Create an express app
const app = express()
Standard Express.
Define a route. Use graphqlHTTP
to handle requests to this route.
// Define a route for GraphQL
app.use('/graphql', graphqlHTTP({
schema,
rootValue: root,
graphiql: true
}))
In the use
function above, we supplied the schema, the root resolver, and activated the GraphiQL browser for our app.
The endpoint will be: /graphql
Finally, start your app:
// Start this app
const port = 4000
app.listen(port, () => {
console.log(`Running on port: ${port}`)
})
(Standard Express app)
npm start
run your app- http://localhost:4000/graphql
This should open GraphiQL in your browser.
GraphiQL allows us to test our GraphQL Queries. It's the same tool you used in the last class.
{
getAbout {
message
}
}
Compare this to the schema and the resolver:
- query type:
getAbout
- Returns: an
About
type
- Returns: an
About
has a field ofmessage
of type string
Let's follow this backward. Starting with this query:
{
getAbout {
message
}
}
Sending this query...
GraphQL handles with a resolver:
const root = {
getAbout: () => {
return { message: 'Hello World' }
}
}
It returns an object with a message
property that is type String
.
getAbout has to return something that looks the About type.
The Resolver checked this against the schema.
type About {
message: String!
}
type Query {
getAbout: About
}
The getAbout
query returns an About
which always has a message
of type String
.
A resolver is responsible for resolving a query. Resolvers can be hierarchical and complicated. You might spend more time here on some projects.
This is the root resolver.
It maps queries to the schema.
const root = {
getAbout: () => {
return { message: 'Hello World' }
}
}
(getAbout
maps to the query type with the same name)
type Query {
getAbout: About
}
Imagine you're making an API for yourself. Imagine a query is asking you a question. The response is the answer you might provide.
Define a new type in your schema
If someone asks what to eat? You would reply with a meal type.
type Meal {
description: String!
}
Add a data type
Add a query type to handle meal queries. It will return a Meal.
type Query {
getAbout: About
getmeal: Meal
}
Add a query type
Add a resolver function. This function returns something that must match the Meal type (has description field of type string)
const root = {
getAbout: () => {
return { message: 'Hello World' }
},
getmeal: () => {
return { description: 'Noodles' }
}
}
Sometimes it takes some information to get some information. Often you'll need to provide parameters to the data that you need.
Queries can take parameters. You saw this in SWAPI. You can add arguments to your queries.
Imagine there is a different meal depending on the time: breakfast, lunch, or dinner.
The Meal type will stay the same since it will still be a field description that is a string.
Modify the Query type to accept an argument.
type Query {
getAbout: About
getmeal(time: String!): Meal
}
(getMeal
now takes an argument: time
, of type String
which is required)
Modify the resolver to work with this argument.
const root = {
getAbout: () => {
return { message: 'Hello World' }
},
getmeal: ({ time }) => {
const allMeals = { breakfast: 'toast', lunch: 'noodles', dinner: 'pizza' }
const meal = allMeals[time]
return { description: meal }
}
}
(The resolver receives an args object with all of the parameters defined in the query type)
Test your query:
{
getmeal(time: "lunch") {
description
}
}
Should return:
{
"data": {
"getmeal": {
"description": "noodles"
}
}
}
Since there are only three possible values you can use an enum!
enum MealTime {
breakfast
lunch
dinner
}
type Query {
getAbout: About
getmeal(time: MealTime!): Meal
}
Note! Using an enum prevents spelling errors or things assumptions like a bunch...
Often you'll want to work with collections. You'll often return posts, or users, or foods.
Imagine you want to define a list of pets. You might start with a Pet
type.
type Pet {
name: String!
species: String!
}
Imagine you have an array of pets. A query type might look like this:
type Query {
...
getPet(id: Int!): Pet # Add a query to get a single pet
allPets: [Pet!]! # Returns an array of type Pet
}
Now set up a resolver for each of the new queries.
const root = {
...
getPet: ({ id }) => {
return petList[id]
},
allPets: () => {
return petList
},
...
}
getPet(id)
takes the id
and returns the pet at that index, allPets
returns an array of all pets
Better define the petList
!
// Mock datatbase in this case:
const petList = [
{ name: 'Fluffy', species: 'Dog' },
{ name: 'Sassy', species: 'Cat' },
{ name: 'Goldberg', species: 'Frog' }
]
This could be defined by a database!
Now write a query. Notice you can choose fields to fetch.
{ # Get the names of all pets
allPets {
name
}
}
{ # Get pet 2 species
getPet(id: 2) {
species
}
}
Your goal is to make a list of things, not unlike SWAPI. This could be a list of pets, songs, recipes, movies, anything.
Make a GraphQL server that serves the things in your list.
Challenge 1 🐶
Create an Array of objects. Each object should have at least three properties.
Examples:
- Pet: name, species, age
- Song: title, genre, length
- Movie: title, genre, rating
In code this might look something like:
const petList = [
{ name: 'Fluffy', species: 'Dog', age: 2 },
{ name: 'Sassy', species: 'Cat', age: 4 },
{ name: 'Goldberg', species: 'Frog', age: 1.3 }
]
Challenge 2 🐶
Make a Type in your schema for your objects:
type Pet {
name: String!
species: Species! # use an enum!
}
Use enum for something!
Advanced: Use an interface!
Challenge 3 🐈
Make a Query type that will return all of the things:
type Query {
allPets: [Pet!]! # returns a collection of Pet
}
Challenge 4 🐈
Write a resolver for your query:
const root = {
allPets: () => {
return petList
},
...
}
This returns the entire list.
Challenge 5 🐡
Test your work in Graphiql:
{
allPets {
name
}
}
Should display a list of names.
Challenge 6 🐸
Add a query that returns a thing at an index:
type Query {
allPets: [Pet!]!
getPet(index: Int!): Pet
}
Add the new query to your Query types in your schema.
Challenge 7 🐿
Add a new resolver. The parameters from the query will be received in the resolver function:
const root = {
...
getPet: ({ index }) => { // index is a param from the query
return petList[index]
}
}
Challenge 8 🐹
Test your work, write a query in Graphiql.
{
getPet(index: 0) {
name
}
}
Challenge 9 👽
Write a query that gets the last item and the first item from the collection.
Schema:
firstPet: Pet
Resolver:
firstPet: () => ???
Challenge 10 ⏰
We need a type that represents time.
- hour
- minute
- second
Write a resolver that gets the time and returns an object with the properties: hour, minute, second.
{
getTime {
hour
second
minute
}
}
Challenge 11 🎲
Imagine we need the server to return a random number. Your job is to write a query type and resolver that makes the GraphQL query below function:
{
getRandom(range: 100)
}
Which should return:
{
"data": {
"getRandom": 77
}
}
Challenge 12 🤔
Create a type that represents a die roll. It should take the number of dice and the number of sides on each die. It should return the total of all dice, sides, and an array of the rolls.
Below is an example query, and the response that should come back
Example Query
{
getRoll(sides:6, rolls: 3) {
total,
sides,
rolls
}
}
Example Response
{
total: 10, // total of all rolls (see below)
sides: 6, // each roll should be 1 to 6 based on the original sides parameter
rolls: [5, 2, 3] // 3 rolls based on the original rools parameter (5+2+3=10)
}
Challenge 13 📋
Add a query that returns the count of elements in your collection. You'll need to add a query and a resolver.
The trick of this problem is how to form this query.
Challenge 14 ✅
Add a query that returns some of your collection in a range. Imagine the query below for pets:
{
petsInRange(start: 0, count: 2) {
name
}
}
The results of the query should return two pets starting with the pet at index 0.
Challenge 15 🔎
Get things by one of their features. For example, if the Type was Pet we could get pets by their species:
{
getPetBySpecies(species: "Cat") {
name
}
}
Challenge 16 ➡️
Choose a field. This query should return all of these values that exist in the things in your list. This would work best for a field with a fixed or limited set of values, like a field that uses an enum as its type:
Here is a sample query:
{
allSpecies {
name
}
}
Returns: "Cat", "Dog", "Frog"
- Complete the challenges here. Submit them on GradeScope.
- Watch https://www.howtographql.com videos up to the GraphQL Node Tutorial:
- Clients
- Servers
- More GraphQL Concepts
- Tooling and Ecosystem
- Security
- Common Questions
- Submit your work to GradeScope.
- Define a GraphQL Schema
- Define a GraphQL Resolver
- Use GraphQL Queries
- Use GraphiQL
- | Does not meet expectations | Meets Expectations | Exceeds Expectations |
---|---|---|---|
GraphQL Schemas | Can't describe or explain GraphQL schemas | Can describe GraphQL schemas | Could teach the basic concepts of GraphQL schemas to someone else |
Writing Schemas | Can't write a GraphQL schema | Can write a GraphQL schema | Feel confident you could write a GraphQL schema for a variety of situations beyond the homework examples |
GraphQL Queries | Can't write a GraphQL Query | Could write a graphQL query | Feel confident you could write GraphQL queries beyond the solutions to the homework problems |
Resolvers | Can't explain resolvers, couldn't find them in your code | Could explain how the resolver works in the sample code from the lesson | Could expand on the resolvers from this lesson adding more use cases |