GraphQL basics and practical examples with Vue

Vue.js meets GraphQL

GraphQL is a query language for your API, and a server side runtime for executing queries. A single endpoint can return data about multiple resources, which makes it a great fit for Vue.js single page applications.

This article will go over how to build a GraphQL API from scratch, as well as define and implement queries and mutations with custom types. I will use Node.js for the GraphQL server, and make requests and display the results using a Vue.js single page application.

The source code for this article is here.

Introduction

A GraphQL service is created by defining types and fields, then providing functions for each field on each type. The canonical example from the GraphQL documentation is:

type Query { // define the query
me: User // define the fields
}
type User { // define the type
id: ID
name: String
}
function Query_me(request) { // define the function
return request.auth.user
}

The above is how we implement a query, custom type, and endpoint using GraphQL. The matching client side query looks like this:

{
me {
name
}
}

which returns:

{
“me”: {
“name”: “username”
}
}

The official GraphQL documentation is excellent, but I found it lacking in practical examples of how to query the data using a standard HTTP client, and integrate it into my Vue.js app, so I’ll do the following in this article. We will use the new vue-cli to scaffold a Vue project to go with it.

Getting Started

Install the vue-cli by running:

npm install -g @vue/cli@latest

Create a new project by running:

vue create graphql-example

and go with the default by choosing ❯ default (babel, eslint). A ton of nod e modules will be installed. We also need to create a folder for the API server, so run the following after cd into the project (cd graphql-example)

mkdir server
npm install express express-graphql graphql --save

We added graphql, as well as express and express-graphql, which is a thin layer that implements some best practices and guidelines for serving queries over HTTP.

Basic Query

Let’s setup up a simple query to make sure everything is working, and see what a GraphQL server looks like. Inside of server/index.js, require some modules:

const express = require('express')
const { graphql, buildSchema } = require('graphql')
const graphqlHTTP = require('express-graphql')
const cors = require('cors')
  • express and express-graphql will let us response to HTTP requests
  • buildSchema is used to define the types (more soon)
  • cors will let us make requests from our Vue app, which will run on port 8080, to the server running on port 4000

The next thing to do is define the schema — what types of queries and types the server will use. Our first schema is basically the “hello world” of GraphQL:

const schema = buildSchema(`
type Query {
language: String
}
`)

We define a Query type called language. It returns a String. GraphQL is statically typed — fields have types, and if something doesn’t match up, and error is thrown.

Unlike REST APIs, Graph APIs have just one endpoint, which responds to all requests. This is called a resolver. I’ll call mine rootValue, and include the implementation for the language query:

const rootValue = {
language: () => 'GraphQL'
}

language just returns a String. If we returned a different type, for example 1 or {}, an error would be thrown, since when we declared language in the schema, we specified a String would be returned.

The last step is to create the express app, and mount the resolver, rootValue , and schema.

const app = express()
app.use(cors())
app.use('/graphql', graphqlHTTP({
rootValue, schema, graphiql: true
}))
app.listen(4000, () => console.log('Listening on 4000'))

Let’s now implement the client side Vue app, that will make the request.

Making a request

Head over to src/App.vue, and delete the boilerplate. It should now looks like this:

<template>
<div id="app">
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'app'
}
</script>

We also import axios, which we will use to make the HTTP requests.

By default, graphqlHTTP listens for POST requests. According to the recommendations in serving over HTTP, we should include the query and variables in the body of the request. This will lead us to the following request:

axios.post('http://localhost:4000/graphql', {
query: '{ language }'
})

The query should be inside curly braces. Adding a button to trigger the request, and a variable to save the result, we end up with:

<template>
<div id="app">
<h3>Example 1</h3>
<div>
Data: {{ example1 }}
</div>
<button @click="getLanguage">Get Language</button>
<hr>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'app',
  data () {
return {
example1: ''
}
},
  methods: {
async getLanguage () {
try {
const res = await axios.post(
'http://localhost:4000/graphql', {
query: '{ language }'
})
this.example1 = res.data.data.language
} catch (e) {
console.log('err', e)
}
}
}
}
</script>

Let’s run this. In one terminal, start the GraphQL server with node server . In another, run the Vue app using npm run serve. Visit http://localhost:8080. If everything went well, you will see:

An initial demo that returns a String

Clicking “Get Language” should return and render the result.

Ok, great. So far we:

  • defined a schema
  • created the resolver, rootValue
  • make a request using axios, which included the query

What else can we do with GraphQL?

Custom Types with Models

GraphQL lets us define custom types, and objects to represent them in the target language — in this case, JavaScript, but there are GraphQL clients for most server side languages. I will define a Champion type in the schema, and matching ES6 class to store any properties and methods.

Firstly, update the schema:

const schema = buildSchema(`
type Query {
language: String
}
  type Champion {
name: String
attackDamage: Float
}
`)

Nothing too exciting other than a new type, Float. Next we can define an ES6 class to represent this type, and store any instance methods or additional data. I’ll define this in a new file, server/champion.js.

class Champion {
constructor(name, attackDamage) {
this.name = name
this.attackDamage = attackDamage
}
}
module.exports = Champion

Nothing special, just a ES6 class. Note we have name and attackDamage — the same fields defined in the schema for Champion.

Now, let’s create another query that uses the Champion type. The updated schema is as follows:

const schema = buildSchema(`
type Query {
language: String
getChampions: [Champion]
}
  type Champion {
name: String
attackDamage: Float
}
`)

getChampions returns an array of Champion. Great! To finish this example off, some mock data and another endpoint:

const champions = [
new Champion('Ashe', 100),
new Champion('Vayne', 200)
]
const rootValue = {
language: () => 'GraphQL',
  getChampions: () => champions
}

Restart the server by pressing ctrl+c in the terminal running the server, and run node server again. Let’s verify if this is working, by sending a query from the client.

Querying for specific fields

Querying getChampions is a little more interesting than language. This time the result will contain the user defined Champion type — and whatever fields we ask for. GraphQL requires us to be explicit in which fields we want. For example the following query:

{ 
getChampions
}

will not work. At least one field should be specified. The updated query:

{
getChampions {
name
}
}

Returns:

{
"data": {
"getChampions": [
{
"name": "Ashe"
},
{
"name": "Vayne"
}
]
}
}

Notice only the name is returned! If we included attackDamage, we would get that too. The query:

{
getChampions {
name
attackDamage
}
}

and response:

{
"data": {
"getChampions": [
{
"name": "Ashe"
"attackDamage": 100
},
{
"name": "Vayne"
"attackDamage": 200
}
]
}
}

Implementing this in the Vue app is equally straightforward:

<template>
<div id="app">
<!-- ... -->
    <h3>Example 2</h3>
<div>
Data:
<div v-for="champion in champions">
{{ champion }}
</div>
</div>
<button @click="getChampions">Get Champions</button>
</div>
</template>
export default {
name: 'app',
  data () {
return {
/* ... */,
champions: []
}
},
  methods: {
/* ... */
async getChampions () {
const res = await axios.post(
'http://localhost:4000/graphql', {
query: `{
getChampions {
name
}
}`
})
this.champions = res.data.data
}
}
}

Make sure you restart the server with node server, if you didn’t already. No need to restart the Vue app, since webpack’s hot reload will automatically update when you save any changes.

Clicking “Get Champions” yields:

Returning an array of a custom type, Champion

Passing Arguments

getChampions return all champions. GraphQL also supports passing arguments, to return a subset of data. This requires:

  • an additional variables object in the POST body
  • telling the client side query the type of arguments you will parse to the query from variables.

Let’s implement a getChampionByName query. As usual, start with the query definition:

const schema = buildSchema(`
type Query {
language: String
getChampions: [Champion]
getChampionByName(name: String!): Champion
}
  type Champion {
name: String
attackDamage: Float
}
`)

Notice we declare the argument name, and the type String!. The ! means the argument is required.

Next, the implementation:

const rootValue = {
language: () => 'GraphQL',
  getChampions: () => champions,
  getChampionByName: ({ name }) => {
return champions.find(x => x.name === name)
}
}

Nothing too exciting — we just use find to get the corresponding champion. An improvement would be to add some error handling, and compare name disregarding case.

Now, the client side implementation. This is where things get a bit more interesting. When passing arguments, we should name the query, and declare the arguments with the corresponding type:

async getChampionByName () {
const res = await axios.post('http://localhost:4000/graphql', {
query: `
query GetChampionByName($championName: String!) {
getChampionByName(name: $championName) {
name
attackDamage
}
}`,
variables: {
championName: 'Ashe'
}
})
this.champion = res.data.data.getChampionByName
}

Line by line:

  1. query GetChampionByName is the name we are giving to the query. This can be anything, but should be descriptive of what the query is doing. In this case, since we are only calling getChampionByName, I used a convention when the name is the same as the query on the server side, but capitalized the first letter. In real application, a single API call might include many different operations. Naming the query can make the code more easily understood.
  2. ($championName: String!) means that variables should contain a championName, and it is not optional.
  3. getChampionByName(name: $championName) is the query to execute on the server side. The first argument, name, should use the championName value in the variables object.
  4. We are requesting name and attackDamage in the response.

Some extra markup will let us display the result in the Vue app (don’t forget to restart the GraphQL server):

<template>
<div>
  <!-- ... -->
  <h3>Example 4</h3>
Name: <input v-model="name">
<div>
Data:
{{ champion }}
</div>
<button @click="getChampionByName">Get Champion</button>
  </div>
</template>
<script>
import axios from 'axios'
export default {
data () {
return {
/* ... */
champion: {}
}
},
methods: {
    /* ... */
    async getChampionByName () {
const res = await axios.post(
'http://localhost:4000/graphql', {
query: `
query GetChampionByName($championName: String!) {
getChampionByName(name: $championName) {
name
attackDamage
}
}`,
variables: {
championName: 'Ashe'
}
})
this.champion = res.data.data.getChampionByName
}
}
}
A query with arguments

Updating Records

So far, we have just been fetching data. You will often want to update data, too, which is why GraphQL also provides mutations. The syntax and implementation isn’t too far from what we covered so far. Let’s start with by defining the mutation:

const schema = buildSchema(`
type Query {
language: String
getChampions: [Champion]
getChampionByName(name: String!): Champion
}
  type Mutation {
updateAttackDamage(name: String!, attackDamage: Float): Champion
}
  type Champion {
name: String
attackDamage: Float
}
`)

Mutations goes in a Mutation type. The rest of the syntax should be familiar by this point. We are returning the updated record, a Champion type. The implementation is equally straightforward:

const rootValue = {
language: () => 'GraphQL',
  getChampions: () => champions,
  getChampionByName: ({ name }) => {
return champions.find(x => x.name === name)
},
  updateAttackDamage: ({ name, attackDamage = 150 }) => {
const champion = champions.find(x => x.name === name)
champion.attackDamage = attackDamage
    return champion
}
}

In a more realistically example, you might execcute an SQL query to update a record in a database, or do some validation. We have to return a Champion type, since we specified so in the mutation declaration. GraphQL will automatically select the correct fields to return, based on the request — we will ask for the name and updated attackDamage, so shown below:

methods: {
/* ... */
  async updateAttackDamage () {
const res = await axios.post('http://localhost:4000/graphql', {
query: `
mutation UpdateAttackDamage(
$championName: String!, $attackDamage: Float) {
updateAttackDamage(name: $championName, attackDamage: $attackDamage) {
name
attackDamage
}
}`,
variables: {
championName: this.name,
attackDamage: this.attack
}
})
this.updatedChampion = res.data.data.updateAttackDamage
}
}

The only real difference here is we declared the operation name to be a mutation type instead of a query type.

The fully updated example is as follow:

<template>
<div>
<!-- ... -->
<h3>Example 4</h3>
Name: <input v-model="name">
Attack Damage: <input v-model.number="attack">
<div>
Data:
{{ updatedChampion }}
</div>
<button @click="updateAttackDamage">Update Champion</button>
  </div>
</template>
<script>
import axios from 'axios'
export default {
  data () {
return {
/* ... */
updatedChampion: {},
attack: 5.5
}
},
  methods: {
/* ... */
async updateAttackDamage () {
const res = await axios.post('http://localhost:4000/graphql', {
query: `
mutation UpdateAttackDamage($championName: String!, $attackDamage: Float) {
updateAttackDamage(name: $championName, attackDamage: $attackDamage) {
name
attackDamage
}
}`,
variables: {
championName: this.name,
attackDamage: this.attack
}
})
this.updatedChampion = res.data.data.updateAttackDamage
}
}
}

As usual, restart the GraphQL server. The result is as follows:

Updating a record with a mutation

You can click “Get Champion”, and see if the data was saved correctly (it should return the newly updated attack damage):

Checking the record was correctly updated

Testing

I did not go over testing. However, testing the server side endpoints is esay, since it’s just plain JavaScript — just export the rootValue object, and tests the functions like you normally would. I’ll explore testing a GraplQL API in a future post.

Conclusion

There is a ton of other things GraphQL can do. Read more on the official site. I hope to explore more in future posts. It is a refreshing alternative to REST, and a great fit for Vue.js based single page applications.

The source code for this article is here.

Like what you read? Give Lachlan Miller a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.