This exercise is part of the React GraphQL Academy learning material. The goal of the exercise is to help you get started transitioning from REST to GraphQL.
- Understand the main functionalities and responsibilities of a GraphQL server
- Learn how to migrate an existing REST API to GraphQL and start “thinking in graphs”
- Start identifying potential problems when running real-world GraphQL APIs
Given the following GraphQL API / https://api.reactgraphql.academy/graphql:
- Query a list with all the training and retrieve the title and language for each
- Query a single discount by id (try id equals
dis:422) and get its name - Query how many languages are in the system?
- How many types do we have in the system?
We are going to create our own GraphQL API on top of this REST API
git clone https://github.com/reactgraphqlacademy/graphql-api-training.gitcd graphql-api-traininggit checkout fundamentals-v2yarn installornpm installyarn startornpm start
- Don't forget to checkout the
fundamentals-v2branch, install the dependencies, and let me walk you through the code meanwhile. - We use nodemon in the
startscript, so every time you save the server will restart automatically. - The
src/index.jsis the getting started tutorial from Apollo. - Let's replace the schema:
type Query {
books: [Book]
}with
type Query {
avocados: [Book]
}What do we need to change so the field avocados returns the array of books when we run the query? I'll give you 2 minutes to fix it :)
[Book]
2- You can use the scalar type ID for ids.
3- In GraphQL types are nullable by default. If you want to make a type non-nullable use ! (exclamation mark). Example:
type Book {
id: ID!
}To complete the tasks you'll use the mock data and helper functions that are at the bottom of the file src/index.js.
-
1. Create a
Trainingtype in your schema. Define the following fieldstitle,id,objectives,curriculum. Have a look at the mock training data insrc/index.jsto identify the types of each field.- 1.1. Add a
trainingsfield to theQuerytype. You can replace thebooksfield from Query type withtrainingssince we won't use books. Thetrainingsfield in theQuerytype should return an array of training. . - 1.2. Add a
trainingsresolver to the Query's resolvers. You can replace thebooksfield from Query type withtrainingssince we won't use books. You can return the trainingMockData array (which is in the scope and defined at the bottom of the file index.js) in the resolver function. - 1.3 You should be able to manually test the
trainingsquery in Playground at http://localhost:4000/
- 1.1. Add a
-
2. Create a
Discounttype in your schema. Define only the fieldscode,id, anddiscountPercentage. Have a look at the discount mock data insrc/index.jsto identify the types of each field.- 2.1. Add a
discountsfield to theQuerytype. Thediscountsfield should return an array of discounts. - 2.2. Add a
discountsresolver to the Query's resolvers. You can return the mock discounts array (which is in the scope and defined at the bottom of the file index.js) in the resolver function. - 2.3 You should be able to manually test the
discountsquery in Playground at http://localhost:4000/
- 2.1. Add a
-
3. Replace the mock data with real data using the following endpoints:
Hint. You can use the fetchTrainings and fetchDiscounts defined at the bottom of this file src/index.js
- You'll need to replace mock data in 2 different places:
- Query discounts
- Query trainings
Note on mocking. In the next session, we'll use the automocking feature of Apollo Server. The only thing you need to do is mocks:true in your Apollo Server configuration. More info here.
const server = new ApolloServer({
typeDefs,
mocks: true // ⬅️⬅️⬅️⬅️
});Congratulations, you've completed part 2! You've learned how to create object types in GraphQL and add fields to them using scalar types (String,Int, ID) or your own object types (Training, Discount). You've also learned how to create a relationship between two object types.
In GraphQL you can also create your custom scalars types, like Date.
Bonus task, add a field called startDate to the Training object type using a DateTime scalar type. GraphQL doesn't provide a DateTime type. Instead of creating a custom DateTime scalar type you are going to use https://github.com/excitement-engineer/graphql-iso-date. Note, the package is already installed in package.json.
Resolvers are functions that have 4 arguments (parent, args, context, info). In this exercise, we are only going to use the first 2 arguments: parent and args.
The first argument, often called parent, points to the parent object. For instance, we could override the default resolver of the title field in the Training and return an upper case version of the title.
const resolvers = {
Query: {
//...
},
Training: {
title: (parent) {
return parent.title.toUpperCase()
}
}
};We could also create a new field that returns the upper case version of the title without changing the title field. Example:
type Training {
title: String!
upperCaseTitle: String!
# the rest remains the same
}const resolvers = {
Query: {
//...
},
Training: {
upperCaseTitle: (parent) {
return parent.title.toUpperCase()
}
}
};🏋️♀️Bonus exercise, return all the URLs of the discounts field in upper case.
The second argument of the resolver (we are calling it args) points to the arguments passed to the field. In the following example args contains id:
const schema = gql`
type Query {
author(id: ID!): Author
}
`;
const resolvers = {
Query: {
author(parent, args) {
console.log(args); // { id: 3 } based on the query below
}
}
};query authorName {
author(id: 3) {
name
}
}To complete the tasks you'll use the helper functions that are at the bottom of the file src/index.js
- 4. Implement a new field in the
Querytype that returns a single training given an id. You need to fetch the training from this endpointhttps://restapi.reactgraphql.academy/v1/trainings/+id. Hint, you need to pass arguments to the field, and then use the second argument in the resolver. There is a helper function at the bottom ofsrc/index.js.
Once implemented you should be able to run the following query:
query getTraining {
training(id: "tra:22") {
title
}
}- 5. Create the following relationship between the Training type and the Discount type in your schema:
type Training {
discounts: [Discount]
# the rest of the fields remain the same
}- You need to add a
Trainingkey in the resolvers object and an object with adiscountskey inTraining. Similar to the Author type and books field in the Apollo documentation - You need to use the first argument of the resolver: the 'parent'. In this case, the parent of the
discountsfield is theTraining.parent.discountsgives you the array of URLs that you can use to fetch each discount from the REST API. - You can use the helper function
fetchDiscountByUrldefined at the bottom of this filesrc/index.js. - Heads up! We want our Training type to have a field called
discountsthat returns an array ofDiscounttypes not an array ofString - Once implemented, you should be able to run the following query:
query getTraining {
training(id: "tra:22") {
title
discounts {
code
}
}
}- Create the types and resolvers so the following query works:
query getDangerousDiscount {
discount(id: "dis:421") {
code
training {
title
discounts {
code
# why this query could be dangerous?
}
}
}
}Once implemented, do you see any problems/ vulnerability issues on that query?
🤸🏾Do you want some extra workout? Create an enumeration for the languages. Add field language to the Training object type that uses the language enum.
You are going to build a GraphQL API on top of an existing REST API. Steps:
1- Choose a public API. You have a list of public APIs here. Suggestion, choose an API that doesn't require authentication and has decent documentation.
2- Create a GraphQL server to validate and execute the GraphQL queries. You can get started using the getting started tutorial from Apollo Server.
3- Create the GraphQL schema using the Schema Definition Language (SDL) . You'll define types and relationships between those types.
4- Add the resolvers to your schema. We are following a SDL-first approach to build our schema. It's the most popular approach in the GraphQL JavaScript community, but be aware that it's not the only one. You can read more about it and other alternatives in this article.
- http://graphql.org/learn/
- http://graphql.org/learn/thinking-in-graphs/
- https://dev-blog.apollodata.com/graphql-vs-rest-5d425123e34b
- https://dev-blog.apollodata.com/graphql-explained-5844742f195e
- https://facebook.github.io/relay/docs/thinking-in-graphql.html
- https://dev-blog.apollodata.com/the-anatomy-of-a-graphql-query-6dffa9e9e747
- https://github.com/apollographql/apollo-server
- https://www.youtube.com/watch?v=PHabPhgRUuU
- https://facebook.github.io/relay/graphql/connections.htm
- https://dev-blog.apollodata.com/introducing-launchpad-the-graphql-server-demo-platform-cc4e7481fcba
- https://dev-blog.apollodata.com/
- http://dev.apollodata.com
- https://astexplorer.net/
This material is available for private, non-commercial use under the GPL version 3.