Let's learn how to build a GraphQL API!
We will be using graphql-java-kickstart for both the java and kotlin implementation and apollo-server for the node example.
In the workshop you will touch on:
- Playground - An in-browser IDE for exploring GraphQL
- QueryResolver - Root queries for your application
- Resolver - Field resolver
- Integrating with an external REST API - Marked price of food items
- Documentation: Use spectacle docs at root of URL
- Integrating with an external GraphQL API - Allergens of food items
- Documentation: Use GraphiQL at root of URL
If you are stuck at any point, feel free to peek into the full node example, full java example or full kotlin example.
We are making a food ordering app! Feel free to stray for this path, but the completed examples are all the same, and they work towards this.
- Clone this repository
- Navigate to 1_starter/[node|java|kotlin]
- Build the application
- Node: yarn
- Java/Kotlin: ./gradlew
- Run the application
- Node: yarn start
- Java/Kotlin: ./gradlew bootRun
- Navigate to Playground
- Node: http://localhost:4000/
- Java/Kotlin: http://localhost:8080/api/playground
- Observe the response from the following query:
query {
developers {
name
}
}
- Use the query navigator to identify the fields that can be added to the query
- Add the fields and explore the result
- Navigate to https://node.gql.systek.dev/, this is complete and working example of the api we are going to build. We will use this as a reference point in the remainder of the workshop.
Start with one query, and build the type objects as you need them. We will start each section with the query illustrating the new functionality being added.
- For Java and Kotlin, the schema definition can be found in
main/resources/schema.graphqls
- For node, the schema is defined in the constant
typeDefs
inindex.js
query {
dishes{
id
name
ingredients {
id
name
}
}
}
- Add a Ingredient type to your schema
- id: number - unique id
- name: String
- Add a Dish type in your schema
- id: number - unique id
- name: String
- ingredients: Collection of Ingredients
- Build a query called
dishes
that returns a list of all available dishes asDish
-objects that adheres to the above query. - Create a fake datasource (or real!), put some
Dish
-objects andIngredient
-objects into it. - Create a query resolver that connects the query to the datasource
query {
dish(dishId: 101) {
id
name
ingredients {
id
name
}
}
}
- Create a new query, call it
dish
, make it receive a single required parameter that is the dish's ID. - Create a new query resolver that connects the query to a filter that extracts the dish identified by the id.
- The query should return the dish, or null if not found.
query {
orders {
id
delivery
delivered
items {
id
name
ingredients {
id
name
}
}
}
}
- Add a
Receipt
definition to your schema- id: number
- delivery: String - date/time of expected delivery
- delivered: nullable String - date/time of delivery
- items: collection of
Dish
items
- Add a orders query to your schema that takes no parameters and returns a list of
Receipt
's - Store receipts in your system.
- Upgrade the fake datasource to contain these, or steal the one from the example projects if you want.
- This query should return all receipts in the system.
mutation {
order(dishes: [{ dishId: 101, quantity: 1 }]) {
id
delivery
delivered
items {
id
name
ingredients {
id
name
}
}
}
}
- Add a
Order
definition to your schema- dishId: number
- quantity: number
- Create a mutation, call it
order
, receive an array ofOrder
items. - Create a mutation resolver for the new mutation
- Store the order in the system (you might need to extend your datasource).
- Return a
Receipt
to the user.
You now probably have all your domain objects, let us extend them with some object-resolvers, and talk to some external APIs.
Create a marketPrice
of type float on your Ingredient
-object. Look at the resources section above, look at the API and create a HTTP client that communicates with this API. Make it available through the object resolver.
Next, create a allergens
of which can be a list of strings. Create a HTTP client that communicates with this external GraphQL API.
Extra curricular activity: Find a library that is a GraphQL client for your platform, is it easier to use? Better? REST 4 lyfe?
- Create a query called
ingredients
, with no params. It should just return a list of all ingredients in the database. - Create a enum type, add it as an optional parameter to
ingredients
. When passed in you can sort the result differently. Make sure you parse the enum correctly.- NAME: sort by name
- PRICE: sort by price
There are four main domain objects, Dish
, Ingredient
, Order
and Receipt
.
A receipt has an orderId which is a unique string, a delivery which is a timestamp (represented a a ISO-8601 formatted). It also resolves a list of Ingredient
based on it's own orderId.
A order is a object that is only used as an input to mutations, this object has a dishId and a quantity, both integers.
Dish has an integer ID, a name, a price. It should also be extended to resolve a list of Ingredient
objects based on it's name.
Ingredient has an integer ID and a name. It shall also be extended to have two externally resolvable variables. "Market price" and "allergens" (which is a list). The shape of these objects you can chose your self, based on what the API provides.
If you managed to get this far, good job! Want an extra challenge? Try enabling batch fetching of orders through the use of DataLoaders. The price and ingredients services provide separate endpoints/resolvers for batched fetching of data.