DEV Community

John Au-Yeung
John Au-Yeung

Posted on

Use GraphQL APIs in Vue.js Apps

Subscribe to my email list now at http://jauyeung.net/subscribe/

Follow me on Twitter at https://twitter.com/AuMayeung

GraphQL is a query language made by Facebook for sending requests over the internet. It uses its own query but still sends data over HTTP. It uses one endpoint only for sending data.

The benefits of using GraphQL include being able to specify data types for the data fields you are sending and being able to specify the types of data fields that are returned.

The syntax is easy to understand, and it is simple. The data are still returned in JSON for easy access and manipulation. This is why GraphQL has been gaining traction in recent years.

GraphQL requests are still HTTP requests. However, you are always sending and getting data over one endpoint. Usually, this is the graphql endpoint. All requests are POST requests, no matter if you are getting, manipulating, or deleting data.

To distinguish between getting and manipulating data, GraphQL requests can be classified as queries and mutations. Below is one example of a GraphQL request:

{  
  getPhotos(page: 1) {  
    photos {  
      id  
      fileLocation  
      description  
      tags  
    }  
    page  
    totalPhotos  
  }  
}

In this story, we will build a Vue.js app that uses the GraphQL Jobs API located at https://graphql.jobs /to display jobs data. To start building the app, we first install the Vue CLI by running npm i @vue/cli. We need the latest version of Node.js LTS installed. After that, we run vue create jobs-app to create new Vue.js project files for our app.

Then, we install some libraries we need for our app, which include a GraphQL client, Vue Material, and VeeValidate for form validation. We run:

npm i vue-apollo vue-material vee-validate@2.2.14 graphql-tag

This installs the packages. Vue Apollo is the GraphQL client, and graphQL-tag converts GraphQL query strings into queries that are usable by Vue Apollo.

Next, we are ready to write some code. First, we write some helper code for our components. We add a mixin for making the GraphQL queries to the Jobs API. Create a new folder called mixins, and add a file called jobMixins.js to it. Then in the file, we add:

import { gql } from "apollo-boost";

export const jobsMixin = {  
  methods: {  
    getJobs(type) {  
      const getJobs = gql`  
      query jobs(  
          $input: JobsInput,  
        ){  
          jobs(  
            input: $input  
          ) {  
            id,  
            title,  
            slug,  
            commitment {  
              id,  
              title,  
              slug  
            },  
            cities {  
              name  
            },  
            countries {  
              name  
            },  
            remotes {  
              name  
            },  
            description,  
            applyUrl,  
            company {  
              name  
            }  
          }  
        }  
      `;  
      return this.$apollo.query({  
        query: getJobs,  
        variables: {  
          type  
        }  
      });  
    }, 

    getCompanies() {  
      const getCompanies = gql`  
      query companies{  
          companies {  
            id,  
            name,  
            slug,  
            websiteUrl,  
            logoUrl,  
            twitter,  
            jobs {  
              id,  
              title  
            }  
          }  
        }  
      `;  
      return this.$apollo.query({  
        query: getCompanies  
      });  
    }  
  }  
}

These functions will get the data we require from the GraphQL Jobs API. The gql in front of the string is a tag. A tag is an expression, which is usually a function that is run to map a string into something else.

In this case, it will map the GraphQL query string into a query object that can be used by the Apollo client.

this.$apollo is provided by the Vue Apollo library. It is available since we will include it in main.js.

Next, in the view folder, we create a file called Companies.vue, and we add:

<template>  
  <div class="home">  
    <div class="center">  
      <h1>Companies</h1>  
    </div>  
    <md-card md-with-hover v-for="c in companies" :key="c.id">  
      <md-card-header>  
        <div class="md-title">  
          <img :src="c.logoUrl" class="logo" />  
          {{c.name}}  
        </div>  
        <div class="md-subhead">  
          <a :href="c.websiteUrl">Link</a>  
        </div>  
        <div class="md-subhead">Twitter: {{c.twitter}}</div>  
      </md-card-header><md-card-content>  
        <md-list>  
          <md-list-item>  
            <h2>Jobs</h2>  
          </md-list-item>  
          <md-list-item v-for="j in c.jobs" :key="j.id">{{j.title}}</md-list-item>  
        </md-list>  
      </md-card-content>  
    </md-card>  
  </div>  
</template>

<script>  
import { jobsMixin } from "../mixins/jobsMixin";  
import { photosUrl } from "../helpers/exports";

export default {  
  name: "home",  
  mixins: [jobsMixin],  
  computed: {  
    isFormDirty() {  
      return Object.keys(this.fields).some(key => this.fields[key].dirty);  
    }  
  },  
  async beforeMount() {  
    const response = await this.getCompanies();  
    this.companies = response.data.companies;  
  },  
  data() {  
    return {  
      companies: []  
    };  
  },  
  methods: {}  
};  
</script>

<style lang="scss" scoped>  
.logo {  
  width: 20px;  
}

.md-card-header {  
  padding: 5px 34px;  
}  
</style>

It uses the mixin function that we created to get the companies’ data and displays it to the user.

In Home.vue, we replace the existing code with the following:

<template>  
  <div class="home">  
    <div class="center">  
      <h1>Home</h1>  
    </div>  
    <form @submit="search" novalidate>  
      <md-field :class="{ 'md-invalid': errors.has('term') }">  
        <label for="term">Search</label>  
        <md-input type="text" name="term" v-model="searchData.type" v-validate="'required'"></md-input>  
        <span class="md-error" v-if="errors.has('term')">{{errors.first('term')}}</span>  
      </md-field> <md-button class="md-raised" type="submit">Search</md-button>  
    </form>  
    <br />  
    <md-card md-with-hover v-for="j in jobs" :key="j.id">  
      <md-card-header>  
        <div class="md-title">{{j.title}}</div>  
        <div class="md-subhead">{{j.company.name}}</div>  
        <div class="md-subhead">{{j.commitment.title}}</div>  
        <div class="md-subhead">Cities: {{j.cities.map(c=>c.name).join(', ')}}</div>  
      </md-card-header> <md-card-content>  
        <p>{{j.description}}</p>  
      </md-card-content><md-card-actions>  
        <md-button v-on:click.stop.prevent="goTo(j.applyUrl)">Apply</md-button>  
      </md-card-actions>  
    </md-card>  
  </div>  
</template>

<script>  
import { jobsMixin } from "../mixins/jobsMixin";  
import { photosUrl } from "../helpers/exports";export default {  
  name: "home",  
  mixins: [jobsMixin],  
  computed: {  
    isFormDirty() {  
      return Object.keys(this.fields).some(key => this.fields[key].dirty);  
    }  
  },  
  beforeMount() {},  
  data() {  
    return {  
      searchData: {  
        type: ""  
      },  
      jobs: []  
    };  
  },  
  methods: {  
    async search(evt) {  
      evt.preventDefault();  
      if (!this.isFormDirty || this.errors.items.length > 0) {  
        return;  
      }  
      const { type } = this.searchData;  
      const response = await this.getJobs(this.searchData.type);  
      this.jobs = response.data.jobs;  
    }, goTo(url) {  
      window.open(url, "_blank");  
    }  
  }  
};  
</script>

<style lang="scss">  
.md-card-header {  
  .md-title {  
    color: black !important;  
  }  
}

.md-card {  
  width: 95vw;  
  margin: 0 auto;  
}  
</style>

In the code above, we have a search form to let users search for jobs with the keyword they entered. The results are displayed in the card.

In App.vue, we replace the existing code with the following:

<template>  
  <div id="app">  
    <md-toolbar>  
      <md-button class="md-icon-button" @click="showNavigation = true">  
        <md-icon>menu</md-icon>  
      </md-button>  
      <h3 class="md-title">GraphQL Jobs App</h3>  
    </md-toolbar>  
    <md-drawer :md-active.sync="showNavigation" md-swipeable>  
      <md-toolbar class="md-transparent" md-elevation="0">  
        <span class="md-title">GraphQL Jobs App</span>  
      </md-toolbar><md-list>  
        <md-list-item>  
          <router-link to="/">  
            <span class="md-list-item-text">Home</span>  
          </router-link>  
        </md-list-item><md-list-item>  
          <router-link to="/companies">  
            <span class="md-list-item-text">Companies</span>  
          </router-link>  
        </md-list-item>  
      </md-list>  
    </md-drawer><router-view />  
  </div>  
</template>

<script>  
export default {  
  name: "app",  
  data: () => {  
    return {  
      showNavigation: false  
    };  
  }  
};  
</script>

<style lang="scss">  
.center {  
  text-align: center;  
}

form {  
  width: 95vw;  
  margin: 0 auto;  
}

.md-toolbar.md-theme-default {  
  background: #009688 !important;  
  height: 60px;  
}

.md-title,  
.md-toolbar.md-theme-default .md-icon {  
  color: #fff !important;  
}  
</style>

This adds a top bar and left menu to our app and allows us to toggle the menu. It also allows us to display the pages we created in the router-view element.

In main.js, we put:

import Vue from 'vue'  
import App from './App.vue'  
import router from './router'  
import store from './store'  
import VueMaterial from 'vue-material';  
import VeeValidate from 'vee-validate';  
import 'vue-material/dist/vue-material.min.css'  
import 'vue-material/dist/theme/default.css'  
import VueApollo from 'vue-apollo';  
import ApolloClient from 'apollo-boost';

Vue.config.productionTip = false  
Vue.use(VeeValidate);  
Vue.use(VueMaterial);  
Vue.use(VueApollo);const client = new ApolloClient({  
  uri: '[https://api.graphql.jobs'](https://api.graphql.jobs'),  
  request: operation => {  
    operation.setContext({  
      headers: {  
        authorization: ''  
      },  
    });  
  }  
});\

const apolloProvider = new VueApollo({  
  defaultClient: client,  
})

new Vue({  
  router,  
  store,  
  apolloProvider,  
  render: h => h(App)  
}).$mount('#app')

This adds the libraries we use in the app (such as Vue Material) and adds the Apollo Client to our app so we can use them in our app.

The this.$apollo object is available in our components and mixins because we inserted apolloProvider in the object we use in the argument of new Vue.

In router.js, we put:

import Vue from 'vue'  
import Router from 'vue-router'  
import Home from './views/Home.vue'  
import Companies from './views/Companies.vue'Vue.use(Router)export default new Router({  
  mode: 'history',  
  base: process.env.BASE_URL,  
  routes: [  
    {  
      path: '/',  
      name: 'home',  
      component: Home  
    },  
    {  
      path: '/companies',  
      name: 'companies',  
      component: Companies  
    }  
  ]  
})

Now we can see the pages we created when we navigate to them.

Top comments (0)