Configure the Apollo Client

Last chapter we dockerized Gatsby giving us both our development and our production environments. Next step is to setup Gatsby so that it can run queries against our Django Graphene server using the Apollo Client.

What is Apollo?

Quoting from the Apollo docs:

Apollo Client is a comprehensive state management library for JavaScript that enables you to manage both local and remote data with GraphQL. Use it to fetch, cache, and modify application data, all while automatically updating your UI.

Apollo Docs

It is with Apollo that Gatsby will be able to query, edit and post to our Django Graphene server.

Configuring Apollo

We will be needing to install two node packages:

  • @apollo/client, to manage both local and remote data with GraphQL. Use it to fetch, cache, and modify application data, all while automatically updating your UI
  • cross-fetch, a Universal Fetch API for Node, Browsers and React Native.
Install NPM Packages
# Very important, cd to frontend
cd frontend

npm install --save @apollo/client
npm install --save cross-fetch

Configure Apollo in Gatsby

Next we need to create a directory for Apollo and four config files:

# cd to the root of our project
mkdir frontend/src/apollo
touch frontend/src/apollo/client.js
touch frontend/src/apollo/wrap-root-element.js
touch frontend/gatsby-browser.js
touch frontend/gatsby-ssr.js
Configure client.js
# frontend/src/apollo/client.js

import fetch from 'cross-fetch';
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';

export const client = new ApolloClient({
  link: new HttpLink({
    uri: 'http://localhost:5555/graphql/',
    fetch,
  }),
  cache: new InMemoryCache()
});

Note the trailing slash in http://localhost:5555/graphql/ .
Django needs the trailing slash or you will get and error. I’ve been bit by this gotcha more than once. 🙂

Configure wrap-root-element.js

Gatsby will generate separate static pages for all the components in the pages directory. Above all these pages is a root component which will allow us to wrap this root component with Apollo.

# frontend/src/apollo/wrap-root-element.js

import React from 'react';
import { ApolloProvider } from '@apollo/client';
import { client } from './client';

export const wrapRootElement = ({ element }) => (
  <ApolloProvider client={client}>{element}</ApolloProvider>
);
Configure gatsby-browser.js

The gatsby-browser.js file allows us to respond to actions within the browser and wrap our site with wrapRootElement.

# frontend/gatsby-browser.js

export { wrapRootElement } from './src/apollo/wrap-root-element';
Configure gatsby-ssr.js

The gatsby-ssr.js file allows us to alter content of static HTML files as they are being rendered with Server-Side Rendering by Gatsby and Node.js.

# frontend/gatsby-ssr.js

export { wrapRootElement } from './src/apollo/wrap-root-element';

Configure Allowed Hosts and Cors in Django

In order for Django to allow a connection with our Gatsby frontend we need to update the Django settings file’s ALLOWED_HOSTS ip address and add the Django Cors Headers app which adds Cross-Origin Resource Sharing (CORS) headers to responses. This allows in-browser requests to our Django app from other origins, in this case the other origin being our Gatsby app running on localhost port 8000.

If you cut and pasted the Django requirements.txt file from way back in Part 1, Chapter 1 of this tutorial, then you already have django-cors-headers installed. If not, you will need to add the Cors app to the Django requirements.txt file:

# server/requirements.txt

# code ...

django-cors-headers>=3.5.0

# code ...
Add Gatsby IP to Django Allowed Hosts
# server/todo_proj/settings.py

# code ...

ALLOWED_HOSTS = ['localhost']
Add Django Cors Headers App to Django Settings
# server/todo_proj/settings.py

# code ...

ALLOWED_HOSTS = ['localhost']
CORS_ORIGIN_ALLOW_ALL = False
CORS_ORIGIN_WHITELIST = [
    "http://localhost:8000",
]

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'todo_app',
    'mptt',
    'graphene_django', 
    'corsheaders',
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
]

# code ...

Now we need to power down our app and rebuild:

docker-compose down

… and rebuild:

docker-compose build

If you run into errors you can always rebuild the Docker containers with the no cache flag:

docker-compose build --no-cache

Querying the Django Server from Gatsby

Now the moment of truth where we make our first query from our new Gatsby frontend to the Django backend.

From the Django Admin UI: http://localhost:5555/admin add a todo and note the todo’s id number.

As a test we will try something simple from the Gatsby index page:

// frontend/src/pages/index.js

import React from "react";

// 1)
import { useQuery } from '@apollo/client';

// 2)
import gql from 'graphql-tag';

// 3)
const APOLLO_QUERY = gql`
  query {
    todo(id: 1) { 
      id
      title
      task
    }
  }
`;

export default function Home() {

  // 4)
  const { loading, error, data } = useQuery(APOLLO_QUERY)
  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error!</p>;

  return(
    <div>
      <div>Hello World!</div>
      <div>
        Title: {data.todo.title}
      </div>
    </div>
  )
}

Hitting http://localhost:8000 you should see something like this:

http://localhost:8000
What is happening in our Apollo query?

Lets walk through the script we just wrote.

1)
import { useQuery } from '@apollo/client';

The Apollo useQuery React Hook will fetch our data automatically.

2)
import gql from 'graphql-tag';

Remember Django’s GraphiQL playground, (http://localhost:5555/graphql) which allows us to test our queries? Lets say we want to query all the todos we currently have in our Django backend database:

http://localhost:5555/graphql

Voila! We get all our todos, in my case only one with the id of 1. Note, check your Django backend http://localhost:5555/admin to confirm that you have a todo in your db with an id of 1 otherwise you will get an error. You can use any todo id that is available in your db.

Lets query for a specific todo using my todo’s id as the query argument:

http://localhost:5555/graphql

Now how do we run this query from our Gatsby index.js page? That’s what Gatsby’s graphql-tag is all about because in step 3):

// frontend/src/pages/index.js

// code ...

import gql from 'graphql-tag';

// 3)
// your id number may be different
const APOLLO_QUERY = gql`
  query {
    todo(id: 1) { 
      id
      title
      task
    }
  }
`;

The Gatsby graphql-tag, in this case gql“ wraps the same query we used in our Django GraphiQL query above. This allows us to cut and paste from the GraphiQL query directly into our graphql-tag above.

Now for loading the result from our query:

// frontend/src/pages/index.js

 4)
 const { loading, error, data } = useQuery(APOLLO_QUERY)
 if (loading) return <p>Loading...</p>;
 if (error) return <p>Error!</p>;

Here we use useQuery to fetch the data and de-structure the result into the loading status, (boolean true false), error if there is any and data which if the query is successful will contain the result.

Step 5 we print to the screen our result:

// frontend/src/pages/index.js

   <div>
      <div>Hello World!</div>
      <div>
        5)
        Todo Title: {data.todo.title}
      </div>
    </div>

… unless we have an error we then print to the screen.

Congratulations! We can now query our Django backend from Gatsby.

Next chapter we setup TDD for Gatsby.