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.