In this post we will explore some of the fundamental concepts of GraphQL in a hands-on way. The goal of this post is to allow the beginner to gain a familiarity with the basic features of GraphQL.
Setting up
The best way to learn is to do. So this tutorial will be light on theory and heavy on practical exercises.
So we'll need a real graphql server to play with. Let's use Vendure, an open-source GraphQL e-commerce framework. I chose Vendure because it is really easy to set up - all you need is to make sure you have Node.js installed. Plus I built it so I know the API quite well 😉
To install a local instance of Vendure, run:
npx @vendure/create my-shop
and follow the prompts. Select SQLite and use the defaults for all other prompts. Installation should take about 5 minutes and then you'll have a full, production-grade GraphQL server pre-populated with sample data ready to play with!
Once installation is complete, open up http://localhost:3000/shop-api in your browser.
Note: if you are unable to install Vendure locally for any reason (e.g. you are on a mobile device), you can still follow along with this article by using the public demo server at demo.vendure.io/shop-api.
You should see the GraphQL Playground - an interactive interface for working directly with the GraphQL server. This is what we'll be using for all the exercises on this article.
Your first query
GraphQL allows us to fetch data by sending queries. Queries are similar to GET requests in a REST API. Let's write our first query:
query {
product(id: 1) {
id
name
}
}
Copy this into the left pane of the playground and then press the play button. You should see the response in the right pane:
{
"data": {
"product": {
"id": "1",
"name": "Laptop"
}
}
}
Congratulations! You executed your first graphql operation!
What did we do here? Let's analyze each part of this.
-
query
: First of all we are declaring that we are making a query. This is in contrast to a mutation (which we will cover below). Technically you can omit thatquery
keyword, but we'll keep using it just to be more explicit. -
product
: Next we are specifying the query we wish to use: product. Each GraphQL server defines a set of possible queries. Since Vendure is an e-commerce server, it exposes queries related to products, orders, customers, etc. -
id: 1
: We then pass an argument to our query, specifying the ID of the product we want to return. You can think of this very much like a function call. Just like function calls, GraphQL queries can take zero, one or more arguments. -
{ id, name }
: We finally specify the fields we want to return. Fields are like properties on a JavaScript object. In this case, we want to know just the ID and name of the product.
Selecting fields
One of the big benefits of GraphQL over REST APIs is that you can specify the exact data you need, and the server will return those and only those fields. This means you can avoid sending too much data over the wire, leading to faster data transfer and lower bandwidth usage.
Let's try adding some more fields to our query. Add the slug
and description
fields to the selection, and press the play button. You should see that these two new fields are added to the result:
{
"data": {
"product": {
"id": "1",
"name": "Laptop",
"slug": "laptop",
"description": "Now equipped with seventh-generation Intel Core processors, ..."
}
}
}
Now let's try an experiment: what happens if we add a field to our selection that does not actually exist on our product. Try adding the field age
, and you'll get the following result:
{
"error": {
"errors": [
{
"message": "Cannot query field \"age\" on type \"Product\". Did you mean \"name\"?",
"locations": [
{
"line": 7,
"column": 5
}
]
}
]
}
}
Schema & Types
This highlights another important property of GraphQL: it is statically typed. That is to say, the exact fields available, and the types of data they can hold (strings, numbers, dates etc.) are explicitly defined in a schema. The schema, in fact, defines all available queries and mutations, and the exact type of objects they can return.
This is an extremely powerful property of GraphQL and allows things like auto-complete and automatic validation of data you send to the server.
You have already seen the auto-complete as you typed the field names above. You can also see all available fields on a given object by pressing ctrl + space
:
At the bottom of the auto-complete popup, in orange text, you'll see the type of that field. GraphQL types are analogous to primitives in JavaScript, String
, Boolean
, Int
, Float
& ID
. In GraphQL these are known as scalar types.
When a field represents a more complex data type, GraphQL supports object types. In fact, we've been working with an object type all along - the Product
object! That's right, queries themselves have a type. In this case, the product
query has the type Product
.
📖 Reference: graphql.org Scalar Types, graphql.org Object Types
Nested Objects
Another of the major benefits of GraphQL is that a single query allows you to fetch an object plus nested relations. What would require several individual http requests using a REST API can be achieved in a single GraphQL query.
To illustrate that, let's update our query to select all the variants of our Laptop product:
query {
product(id: 1) {
id
name
slug
description
variants {
name
sku
price
}
}
}
Now when we run this, we'll get the requested fields on each of the laptop variants:
{
"data": {
"product": {
"id": "1",
"name": "Laptop",
"slug": "laptop",
"description": "Now equipped with seventh-generation Intel Core processors, ...",
"variants": [
{
"name": "Laptop 13 inch 8GB",
"sku": "L2201308",
"price": 129900
},
{
"name": "Laptop 15 inch 8GB",
"sku": "L2201508",
"price": 139900
},
{
"name": "Laptop 13 inch 16GB",
"sku": "L2201316",
"price": 219900
},
{
"name": "Laptop 15 inch 16GB",
"sku": "L2201516",
"price": 229900
}
]
}
}
}
In a REST API, we'd typically need to make 5 separate requests to get all that data - one for the product, and one for each of the 4 variants!
Hands-on: Queries
Now that we know the fundamental concepts of GraphQL queries, spend some time exploring the Vendure API! Use the ctrl + space
hotkey to bring up the available queries and their fields.
Mutations
Mutations are the counterpart to queries, and as the name suggests, they are used to mutate (change) data. Mutations are similar to POST or PUT requests in a REST API.
Let's execute a mutation. Paste this into the left hand panel of the playground:
mutation {
addItemToOrder(productVariantId:1, quantity: 1) {
...on Order {
id
state
totalQuantity
totalWithTax
}
...on ErrorResult {
errorCode
message
}
}
}
Press the play button and you'll see something like this:
{
"data": {
"addItemToOrder": {
"id": "1",
"state": "AddingItems",
"totalQuantity": 1,
"totalWithTax": 155880
}
}
}
Let's once again break down each part of the mutation syntax:
-
mutation
: declares that we are dealing with a mutation as opposed to a query. Unlike with queries, this keyword is not optional. -
addItemToOrder
: the name of the mutation as declared by our schema. -
productVariantId: 1
: like queries, mutations can (and usually do) take arguments. -
... on Order
: this is known as an inline fragment. Since things might go wrong when adding an item to an order (e.g. insufficient stock), the mutation will return either anOrder
object, or anErrorResult
object. This syntax is like saying "if it returns an Order, then give me the ID, state, totalQuantity & totalWithTax fields. If it returns an ErrorResult, then give me the errorCode and message fields." You can try this out by enteringquantity: -1
and observing what happens.
As you can see, mutations share all the same parts as a query. In fact, the only real difference is the mutation
keyword at the start. Just like with queries, they have arguments and they return a type. In this case the type is a combination of several object types (known in GraphQL as a union type, a more advanced topic out of the scope of this tutorial).
📖 Reference: graphql.org Mutations, graphql.org Union Types
Hands-on: Mutations
Now you can explore for yourself how mutations work using the GraphQL playground. With the initial mutation { }
keyword in place, pressing ctrl + space
will now bring up auto-complete on all available mutations.
Here's a challenge for you: see if you are able to successfully execute the adjustOrderLine
mutation in order to change the quantity of laptops in your order.
Wrapping Up
In this post we've covered how to set up a full-featured GraphQL server using Vendure, and then learned the basics of queries and mutations. These two concepts form the basis of everything you'll be doing with GraphQL.
But of course this is more that you can learn - fragments, interfaces, named operations, variables, aliases and more. If you like the style of this tutorial and would like to see a follow-up that takes the next step into these more advanced topics, please leave a comment below!
And if you enjoyed working with Vendure - check out the GitHub repo and leave a star! 🙏
Top comments (2)
Great post! How do you recommend to handle translations, e.g. english and spanish product name and slug? Thank you!
Hi, thank you!
In Vendure, we support multiple translations (this is set up in the admin ui, "global settings") and then we can choose which language version to return by appending eg
?languageCode=es
to the query url.