Introduction
GraphQL is a graph-oriented query language written by Facebook. In contrast to REST APIs, GraphQL introduces features that make API development more efficient and in tune with database models.
GraphQL Features
- Unlike REST, there is only one endpoint where all requests are going to be sent to. So instead of querying
/users
to get a list of users, or/user/:id
to get a particular user, the endpoint will look like/graphql
for all the requests. - In GraphQL, the data coming back from a response is set by the query library stated and it can be set to only send a few data properties, therefore, queries in GraphQL have better performance.
- No need to set method verbs in GraphQL. Keywords such as Query or Mutation will decide what the request will perform.
- REST API routes usually are handled by one route handler. In GraphQL you can have a single query trigger multiple mutations and get a compound response from multiple sources.
Queries
A query is a GraphQL method that allows us to GET data from our API. Even though it may receive parameters to filter, order, or simply search for a particular document a query can not mutate this data.
Mutations
Mutations are everything that is not what would refer to a GET verb in regular APIs. Updating, creating, or deleting data from our API is done via mutations
Subscriptions
With the use of web sockets, a subscription refers to a connection between the client and the server.
The server is constantly watching for mutations or queries that are attached to a particular subscription, and communicate any changes to the client in real time. Subscriptions are mostly used for real-time widgets/apps.
Types and Inputs
To make sure our queries and mutations can process the data to query a database, types
work much like a model ORM for databases. By setting types up we can define the type of variable our resolvers will return.
Similarly, we need to set input types for our resolvers to receive.
For example, we will define a couple types
and inputs
:
type User {
id: ID
name: String!
age: Int!
address: Address
followers: [ID]
}
type Address {
street: String
city: String
country: String
}
input UserInput {
name: String!
age: Int!
}
type Query {
getAllUsers: [User]
}
type Mutation {
createUser(user: UserInput!): ID
}
Properties can have a custom type as its type apart from the primitive ones, such as:
- String
- Int
- Float
- Boolean
- ID
And they can also be an array of a certain type determined by the brackets, which is shown in the example above.
Furthermore, the mandatory status of a property can be set with the !
, meaning that the property needs to be present.
Resolvers
These are the actions that are performed when calling queries and mutations.
getAllUsers
and createUser
are going to be connected to a resolver that will perform the actual calculations and database queries.
Creating our Project
For this tutorial, we will be creating a Vue.js project using the Vue CLI 3.0, which will bootstrap a project with a folder structure that looks like this:
If you need help setting up the project, you can look at this tutorial for the command line interface.
We can start serving our application with the command:
$ npm run serve
Apollo Client
Apollo Client brings a tool to front-end development to make GraphQL queries/mutations easier. It acts as an HTTP client that connects to a GraphQL API and provides caching, error handling, and even state management capabilities.
For this tutorial, Vue-Apollo will be used, which is the Apollo integration specially designed for Vue.js.
Apollo Configuration
To start our Apollo configuration a few packages will need to be installed:
$ npm install apollo-client apollo-link-http apollo-cache-inmemory vue-apollo graphql graphql-tag
Inside a /graphql
folder in our project, we will create apollo.js
:
// apollo.js
import Vue from 'vue'
import { ApolloClient } from 'apollo-client'
import { HttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import VueApollo from 'vue-apollo'
const httpLink = new HttpLink({
uri: process.env.VUE_APP_GRAPHQL_ENDPOINT
})
// Create the apollo client
export const apolloClient = new ApolloClient({
link: httpLink,
cache: new InMemoryCache(),
connectToDevTools: true
})
// Install the Vue plugin
Vue.use(VueApollo)
export const apolloProvider = new VueApollo({
defaultClient: apolloClient
})
HttpLink
is an object that requires a uri
property, which refers to the GraphQL endpoint from the API being used. Ex: localhost:8081/graphql
Then, a new ApolloClient
instance needs to be created, where the link, cache instance, and further options can be set.
Finally, we wrap our ApolloClient
inside a VueApollo
instance so we can use its hooks inside our Vue components.
Global Error Handling
There is a way of handling errors globally inside the configuration file. For that we need to install an npm package called apollo-link-error
, which inspects and manages errors from the network:
// apollo.js
import Vue from 'vue'
import { ApolloClient } from 'apollo-client'
import { HttpLink } from 'apollo-link-http'
import { onError } from "apollo-link-error"
import { InMemoryCache } from 'apollo-cache-inmemory'
import VueApollo from 'vue-apollo'
const httpLink = new HttpLink({
uri: process.env.VUE_APP_GRAPHQL_ENDPOINT
})
// Error Handling
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.map(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
)
if (networkError) console.log(`[Network error]: ${networkError}`)
})
// Create the apollo client
export const apolloClient = new ApolloClient({
link: errorLink.concat(httpLink),
cache: new InMemoryCache(),
connectToDevTools: true
})
// Install the Vue plugin
Vue.use(VueApollo)
export const apolloProvider = new VueApollo({
defaultClient: apolloClient
})
After importing the onError
function from the package, we can implement it as a sort of middleware of Apollo Client. It'll catch any network or GraphQL errors, giving us the chance to manage them globally.
The callback gets called with an object with some properties whenever an error has happened:
- operation: The operation that triggered the callback because an error was found.
- response: The result of the operation.
- graphQLErrors: An array of errors from the GraphQL endpoint
- networkError: Any error during the execution of the operation or server error.
- forward: The next link referenced in the chain.
Managing State with Apollo Client
A different alternative to using Vuex with Vue projects, and when using the Apollo Client is to use a package called apollo-link-state
.
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
It works as a local data management tool that works as if you were querying a server, but it does it locally.
Also, it is a great way of managing the cache for our application, thus making Apollo Client an HTTP client and state/cache management tool.
For more information, you can check the official documentation for Apollo-link-state.
Creating Queries
For creating queries we need to set up a string-type tag with the package graphql-tag. For keeping a tidy and structured project, we will create a folder called queries
inside the graphql folder.
Assuming the server receiving the query is set up properly to interpret this query, for example, we can trigger a resolver called getAllUsers
:
import gql from 'graphql-tag'
export const GET_ALL_USERS_QUERY = gql`
query getAllUsers {
getAllUsers {
// Fields to retrieve
name
age
}
}
`
The default operation in GraphQL is query
, so the query
keyword is optional.
If a retrieved field has subfields, then at least one of them should be fetched for the query to succeed.
Using Mutations
Much like queries, we can also use mutations by creating a gql-string
.
import gql from 'graphql-tag'
export const CREATE_USER_MUTATION = gql`
mutation createUser($user: UserInput!) {
createUser(user: $user)
}
`
Our createUser
mutation expects a UserInput
input, and, to be able to use parameters passed by Apollo. We will first define a variable with the $
called user
. Then, the outside wrapper will pass the variable to the createUser
mutation, as expected by the server.
Fragments
In order to keep our gql-type
strings tidy and readable, we can use fragments to reuse query logic.
fragment UserFragment on User {
name: String!
age: Int!
}
query getAllUsers {
getAllUsers {
...UserFragment
}
}
Using GraphQL in Vue components
Inside the main.js
file, to configure the Apollo Client, we need to import and attach the client to our instance.
// main.js
import Vue from 'vue'
import { apolloProvider } from './graphql/apollo'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
apolloProvider,
render: h => h(App)
})
Since we have added our ApolloProvider to the Vue instance, we can access the client through the $apollo
keyword:
// GraphQLTest.vue
<template>
<div class="graphql-test">
<h3 v-if="loading">Loading...</h3>
<h4 v-if="!loading">{{ getAllUsers }}</h4>
</div>
</template>
<script>
import { GET_ALL_USERS_QUERY } from '../graphl/queries/userQueries'
export default {
name: 'GraphQLTest',
data () {
return {
users: []
}
},
async mounted () {
this.loading = true
this.users = await this.$apollo.query({ query: GET_ALL_USERS_QUERY })
this.loading = false
}
}
</script>
If we want to create a user, we can use a mutation
:
// GraphQLTest.vue
<template>
<div class="graphql-test">
<input v-model="user.name" type="text" placeholder="Name" />
<input v-model="user.age" placeholder="Age" />
<button @click="createUser">Create User</button>
</div>
</template>
<script>
import { CREATE_USER_MUTATION } from '../graphl/queries/userQueries'
export default {
name: 'GraphQLTest',
data() {
return {
user: {
name: null,
age: null
}
}
},
methods: {
async createUser () {
const userCreated = await this.$apollo.mutate({
mutation: CREATE_USER_MUTATION,
variables: {
user: this.user // this should be the same name as the one the server is expecting
}
})
// We log the created user ID
console.log(userCreated.data.createUser)
}
}
}
</script>
Using this approach lets us micro-manage when and where our mutations and queries will execute. Now we will see some other ways of handling these methods that Vue Apollo gives us.
The Apollo Object
Inside our Vue components, we get access to the Apollo
object, which can be used to easily manage our queries and subscriptions:
<template>
<div class="graphql-test">
{{ getAllUsers }}
</div>
</template>
<script>
import { GET_ALL_USERS_QUERY } from '../graphl/queries/userQueries'
export default {
name: 'GraphQL-Test',
apollo: {
getAllUsers: {
query: GET_ALL_USERS_QUERY
}
}
}
</script>
Refetching Queries
When defining a query inside the Apollo object, it is possible to refetch this query when calling a mutation or another query with the refetch
method or the refetchQueries
property:
<template>
<div class="graphql-test">
{{ getAllUsers }}
</div>
</template>
<script>
import { GET_ALL_USERS_QUERY, CREATE_USER_MUTATION } from '../graphl/queries/userQueries'
export default {
name: 'GraphQL-Test',
apollo: {
getAllUsers: {
query: GET_ALL_USERS_QUERY
}
},
methods: {
refetch () {
this.$apollo.queries.getAllUsers.refetch()
},
queryUsers () {
const user = { name: Lucas, age: 26 }
this.$apollo.mutate({
mutation: CREATE_USER_MUTATION,
variables: {
user
}
refetchQueries: [
{ query: GET_ALL_USERS_QUERY }
]
})
}
}
}
</script>
By using the Apollo
object, provided to us by Vue-Apollo, we no longer need to actively use the Apollo client way of triggering queries/subscriptions and some useful properties and options become available to us.
Apollo Object Properties
- query: This is the
gql
type string referring to the query that wants to get triggered. - variables: An object that accepts the parameters being passed to a given query.
- fetchPolicy: A property that sets the way the query will interact with the cache. The options are
cache-and-network
,network-only
,cache-only
,no-cache
,standby
and the default iscache-first
. - pollInterval: Time in milliseconds that determines how often a query will automatically trigger.
Special Options
- $error to catch errors in a set handler.
- $deep watches deeply for changes in a query.
- $skip: disables all queries and subscriptions in a given component.
- $skipAllQueries: disables all queries from a component.
- $skipAllSubscriptions: to disable all subscriptions in a component.
Apollo Components
Inspired by the way the Apollo Client is implemented for React (React-Apollo), Vue-Apollo provides us with a few components that we can use out of the box to manage the UI and state of our queries and mutations with a Vue component inside the template.
ApolloQuery
Simpler way of managing our queries in a more intuitive manner:
<ApolloQuery
:query="GET_ALL_USERS_QUERY"
>
<template slot-scope="{ result: { loading, error, data } }">
<!-- Loading -->
<div v-if="loading">Query is loading.</div>
<!-- Error -->
<div v-else-if="error">We got an error!</div>
<!-- Result -->
<div v-else-if="data">{{ data.getAllUsers }}</div>
<!-- No result (if the query succeed but there's no data) -->
<div v-else>No result from the server</div>
</template>
</ApolloQuery>
ApolloMutation
Very similar to the example above, but we must trigger the mutation with the mutate
function call:
<ApolloMutation
:mutation="CREATE_USER_MUTATION"
:variables="{
name,
age
}"
@done="mutationFinished"
>
<template slot-scope="{ mutate, loading, error }">
<!-- Loading -->
<h4 v-if="loading">The mutation is loading!</h4>
<!-- Mutation Trigger -->
<button @click="mutate()">Create User</button>
<!-- Error -->
<p v-if="error">An error has occurred!</p>
</template>
</ApolloMutation>
Conclusion
GraphQL brings a lot of flexibility to API development, from performance, ease-of-use, and an overall different perspective of what an API should look and behave like. Furthermore, ApolloClient and Vue Apollo deliver a set of tools for better management of our UI, state and operations, even error handling and cache!
For more information about GraphQL and Apollo Client you can visit the following: