The master of whisperers is starting to lose track of all the comings and goings in Westeros. He has decides to build an GraphQL API to keep track, and has demanded that you help him.
Good luck!
GraphQL is an open-source data query language for API's, and a runtime for fulfilling those queries with existing data. It represent a new way to think about API's compared to traditional methods like REST. This workshop will give you hands-on experience using GraphQL to master common API operations.
We will cover basic topics like fetching data from a GraphQL server using queries and mutations, writing schemas to describe what data can be queried and getting to know the schema type system. At the end of the workshop you will be well equipped to start implementing a new, or to query an existing, GraphQL API.
First, clone this project.
The workshop comes in two versions. The exercises can be solved in Node.js or .NET. Choose your favourite, and follow the links below to install the project.
Your GraphQL server should now be running on http://localhost:4000. Open the browser, to check that you are running GraphiQL, which is one of the key benefits of GraphQL. It is an interactive documentation of the GraphQL API, where you can see the Schema, read the autogenerated docs and try out queries with autocomplete. Use it for all it is worth in the excersises.
We also provide a React client that is set up to consume data exposed from our GraphQL server. All client related tasks are marked with "Frontend" and are not required to finish the workshop. Follow the link below to read installation instructions for the client
Your React.js app should now show some html on http://localhost:3000. Inspecting network requests in your browser Developer Console is a neat trick to do debugging. GraphQL returns great error responses if something is wrong with the network request,
The GraphQL query language is basically about selecting fields on objects, which means that the client is now in control. The GraphQL server will return only the fields asked for.
A query is built hierarchical, with a top-level query
query {
characters {
id
}
}
The client decides which character fields it needs. It can ask for id, name and image like this:
query {
characters {
id
name
image
}
}
Nested queries can be used to find information about a characters siblings:
query {
characters {
name
siblings {
name
}
}
}
The schema defines your GraphQL API's type system, and what is allowed to be executed in the GraphQL server. Calls from the client are validated and executed against the schema.
Every schema needs a root query type, defining the top level queries.
type Query {
characters: [Character]
}
The characters
field is defined as a list of type Character
.
type Character {
id: ID!
name: String
image: String
allegiances: [String]
siblings: [Character]
}
The Character
type defines two fields of type ID
and String
. Both are one of the built in scalar types defined by GraphQL. A scalar type resolves to a single scalar object, which can't have sub-selections in a query. The scalar types are:
- Int: A signed 32‐bit integer.
- Float: A signed double-precision floating-point value.
- String: A UTF‐8 character sequence.
- Boolean:
true
orfalse
. - ID: represents a unique identifier and is serialized in the same way as a String
For scalar types, you can just add a field to a type in your schema - and GraphQL will resolve it based on matching name in the data set.
The !
behind ID
simply means that the field is non-nullable.
a) Add the field alligiances
to the Character
type. Use GraphiQL to find alligiances of all characters.
Resolvers are responsible for mapping the operations to actual functions. For the Character
type, there is already defined one resolver - the one that resolved your query for siblings earlier.
With Javascript it would look something like this:
const Character = {
siblings: (root, args) => {
return characters.filter(character =>
root.siblingIds.includes(character.id)
);
}
};
All resolvers receives the root
argument, which is the parent beeing resolved. To find all the siblings, the resolver filters all characters using the siblingIds
list.
In C# the resolver responsible for mapping siblings is placed in CharacterType.cs
and looks like this:
Field<ListGraphType<CharacterType>>(
"siblings",
resolve: context => data.GetSiblings(context.Source)
);
Up until now we have queried all characters, but we want to be able to get one specific. The following query should return Bran Stark:
query {
character(name: "Bran Stark") {
name
}
}
To support this query you need to extend the Query
type in your schema:
type Query {
characters: [Character]
character(name: String): Character
}
Now we will make use of the args
argument. Adding a character
resolver like below, will allow you to query a specific character by name.
const Query = {
characters: (root, args, context) => {
return characters;
},
character: (root, args, context) => {
return characters.find(char => char.name === args.name);
}
};
With C# you would have to do something like this in the GotQuery
-file
Field<CharacterType>(
"character",
arguments: new QueryArguments(
new QueryArgument<StringGraphType> { Name = "name", Description = "name of the character" }
),
resolve: context =>
{
var name = context.GetArgument<string>("name");
return data.GetCharacter(name);
});
type House {
id: ID!
name: String
words: String
region: [String!]
allegiances: [House!]
members: [Character!]
}
e) [Frontend] Get required data for a specific character in Character.js
by implementing react-apollo
's Query
-component. You might need to update your server side GraphQL schema to get all required fields. Useful documentation can be found here: https://www.apollographql.com/docs/react/essentials/queries/
Mutations are the second main operation in GraphQL. It deals with creating, deleting and updating data. As with the Query
type in your schema, you will need to add a Mutation
type. Lets imagine you are Jaime Lannister, secrets are important to you - sometimes desperate actions are needed:
type Mutation {
pushFromWindow(name: String!): Character
}
b) [Frontend] Implement the pushFromWindow
mutation in the Push
-component in Character.js
. Use react-apollos
's Mutation
-component. Useful documentation can be found here: https://www.apollographql.com/docs/react/essentials/mutations/
It is not just in Westeros the action is happening. Across the Narrow Sea, an important wedding is taking place. The ruggedly handsome Khal Drogo is marrying the beautiful Daenerys Targaryen.
type Mutation {
pushFromWindow(name: String!): Character
marry(spouseName1: String!, spouseName2: String!): [Character]
}
Although the claim may be poor, Joffrey Baratheon manages to be crowned King of the Seven Kingdoms. We have to make sure our API keeps track.
d) Give King Joffrey the titles: the First of His Name, King of the Andals and the First Men, Lord of the Seven Kingdoms, and Protector of the Realm
e) [Frontend] Make it possible to add titles to a character by implementing a mutation in the AddTitle
-component in Character.js
Add seat to all the the houses. To do this you need to define a Castle
type with the necessary fields (check the data set).