Using Apollo and GraphQL with Nuxt.js
– Published 23rd Feb, 2021
Please note that this tutorial was created using Nuxt 2.
Let's get started
Let's get the project set up and base config in place.
npx create-nuxt-app my-project-name
In the create-nuxt-app CLI, there are no specific settings/addons you need – so select the options that suit your project. You could use Universal mode with either SSR or SSG, or SPA mode. I'm going to use TailwindCSS so I can throw some basic styling at the project.
cd my-project-name
npm install graphql-tag @nuxtjs/apollo
Now let's set up Apollo so Nuxt can make requests to the GraphQL API. In this walkthrough, I'm going to be using the Rick and Morty API as a proof of concept..
/nuxt.config.js
// Modules: https://go.nuxtjs.dev/config-modules
modules: [
// https://go.nuxtjs.dev/axios
'@nuxtjs/apollo',
],
// Apollo configuration
apollo: {
clientConfigs: {
default: {
httpEndpoint: 'https://rickandmortyapi.com/graphql',
},
},
},
Smart Queries with Apollo
Get the app up and running on a local server with 'npm run dev', and navigate to /pages/index.vue. Clear out the whole file, as we're going to write it from scratch.
/pages/index.vue
<template>
<div class="p-8">
<h1 class="font-bold text-2xl mb-8">Rick and Morty Characters</h1>
<div class="grid grid-cols-3 gap-8">
<article
v-for="character in characters.results"
:key="character.id"
class="flex flex-col items-start"
>
<h2 class="font-bold">{{ character.name }}</h2>
<div class="text-sm text-gray-800">
<p>Gender: {{ character.gender }}</p>
<p>Location: {{ character.location.name }}</p>
</div>
<NuxtLink
:to="`/character/${character.id}`"
class="border rounded px-2 py-1 text-gray-800 border-gray-800 text-sm mt-2"
>
Read more
</NuxtLink>
</article>
</div>
</div>
</template>
<script>
import gql from 'graphql-tag'
const ALL_CHARACTERS_QUERY = gql`
query ALL_CHARACTERS_QUERY {
characters {
results {
id
name
gender
location {
name
}
}
}
}
`;
export default {
apollo: {
characters: {
query: ALL_CHARACTERS_QUERY,
prefetch: true,
},
},
}
</script>
So what's happening here is mostly self explanatory. We're just looping over the results in the template, and I've got some Tailwind classes applied so it doesn't look terrible.
I've created my query here within the Single File Component, but you could have these in a separate file if they're being reused elsewhere.
The @nuxtjs/apollo module gives us the ability to run 'Smart queries' in our Vue page components. We can either do it this way, or with Nuxt's own asyncData method, which I'll run through next.
Apollo GraphQL queries with asyncData
Let's create a new directory called "character" within the "pages" directory. In here, we'll create a file called "_id.vue". This is going to be a dynamic page, which fetches a single character's data by their ID.
Ideally this would be a slug if we're going for nice looking URLs, but the Rick and Morty API in this example doesn't have slugs.
/pages/character/_id.vue
<template>
<div class="flex flex-col items-start p-4 space-y-4">
<h1 class="font-bold text-2xl">{{ character.name }}</h1>
<hr class="w-full">
<div class="text-gray-800">
<p>Gender: {{ character.gender }}</p>
<p>Species: {{ character.species }}</p>
<p>Origin: {{ character.origin.name }}</p>
</div>
<hr class="w-full">
<h2 class="font-bold mb-2">Appears in:</h2>
<div class="grid grid-cols-4 gap-4">
<article
v-for="episode in character.episode"
:key="episode.id"
>
<h3 class="font-bold">{{ episode.name }}</h3>
<p class="">Air Date: {{ episode.air_date }}</p>
</article>
</div>
<NuxtLink
to="/"
class="border rounded px-2 py-1 text-gray-800 border-gray-800 text-sm"
>
Go back
</NuxtLink>
</div>
</template>
<script>
import gql from 'graphql-tag'
const SINGLE_CHARACTER_QUERY = gql`
query SINGLE_CHARACTER_QUERY ($id: ID!) {
character(id: $id) {
name
species
gender
origin {
name
}
episode {
id
name
air_date
}
}
}
`
export default {
async asyncData({ app, params }) {
const client = app.apolloProvider.defaultClient;
const { id } = params;
const res = await client.query({
query: SINGLE_CHARACTER_QUERY,
variables: {
id,
},
})
const { character } = res.data;
return {
character,
}
},
}
</script>
Here what we're doing is using Nuxt's asyncData method to fetch data with our preconfigured Apollo client. By destructuring the ID param from the URL, we can pass this in to the GraphQL query within the "variables" object.
Building a search form using GraphQL
Now let's build a search form on the home page which can run GraphQL queries client-side.
/pages/index.vue
<template>
<div class="p-8">
<h1 class="font-bold text-2xl mb-8">Rick and Morty Characters</h1>
<!-- Search form -->
<form
@submit.prevent="search"
class="flex items-center mb-8"
>
<input
type="text"
name="Search"
id="Search"
class="border border-gray-600 rounded py-2 px-4 mr-2"
v-model="searchText"
placeholder="Search by name"
>
<button
type="submit"
class="bg-gray-200 px-4 py-2 border border-gray-200 rounded"
>
Search
</button>
</form>
<div v-if="loading">Loading..</div>
<div
v-if="searchResults"
class="mb-8"
>
<div v-if="searchResults.length">
<p class="font-bold mb-2">Your search results:</p>
<div class="flex flex-wrap">
<div
v-for="character in searchResults"
:key="character.id"
class="mr-4 mb-2 flex"
>
<NuxtLink
:to="`/character/${character.id}`"
class="border rounded px-2 py-1 text-gray-800 border-gray-800 text-sm mt-2 whitespace-no-wrap"
>
{{ character.name }}
</NuxtLink>
</div>
</div>
</div>
<p
class="font-bold"
v-else
>
No results found.
</p>
</div>
<!-- Pre-rendered data -->
<div class="grid grid-cols-3 gap-8">
<article
v-for="character in characters.results"
:key="character.id"
class="flex flex-col items-start"
>
<h2 class="font-bold">{{ character.name }}</h2>
<div class="text-sm text-gray-800">
<p>Gender: {{ character.gender }}</p>
<p>Location: {{ character.location.name }}</p>
</div>
<NuxtLink
:to="`/character/${character.id}`"
class="border rounded px-2 py-1 text-gray-800 border-gray-800 text-sm mt-2"
>
Read more
</NuxtLink>
</article>
</div>
</div>
</template>
<script>
import gql from 'graphql-tag'
const ALL_CHARACTERS_QUERY = gql`
query ALL_CHARACTERS_QUERY {
characters {
results {
id
name
gender
location {
name
}
}
}
}
`;
const CHARACTERS_BY_NAME_QUERY = gql`
query CHARACTERS_BY_NAME($name: String!) {
characters(filter: { name: $name }) {
results {
id
name
}
}
}
`;
export default {
apollo: {
characters: {
query: ALL_CHARACTERS_QUERY,
prefetch: true,
},
},
data() {
return {
searchText: '',
searchResults: null,
loading: false,
}
},
methods: {
async search() {
try {
const res = await this.$apollo.query({
query: CHARACTERS_BY_NAME_QUERY,
variables: {
name: this.searchText,
},
});
if (res) {
this.loading = false;
const { results } = res.data.characters;
this.searchResults = results;
}
} catch (err) {
this.loading = false;
this.searchResults = [];
}
}
}
}
</script>
With this search form, we can now handle client-side GraphQL queries, as well as server-side queries in the same file.
The code here is a bit rough and messy, but serves the purpose as an example of the various ways we can use Apollo with Nuxt.js. Any feedback is welcome, feel free to contact me.