By Joe Previte, on Mar 13, 2019

Implementing GraphQL with a REST API

GraphQL has been available since its public release in 2015, but it’s still making people nervous. Managers and decision-makers might be pushing back on its implementation, leading to a lot of discussions that go a little something like this:

"Have you heard about GraphQL? Developers are saying that it’s the successor to REST. Perhaps we should have the backend team implement a GraphQL server for this next project?"

"I’m still skeptical about it. Let’s revisit this in a 6 months when there has been more adoption."

When people are cautious about incorporating new technologies into a team’s stack, it’s generally a good thing. Why break or mess with something that’s working? But how are you supposed to grow and stay up-to-date if you always have to await the approval of someone higher up?

Well, the wait is over 😏. The team over at Apollo is actively working on a small library called apollo-link-rest for developers like you. They’re targeting those interested in GraphQL but who want to test it out on a project without having to request for the backend to fully integrate with a GraphQL server. Or wait for your manager’s approval.

By the end of this article, you’ll understand how to:

  • Use apollo-link-rest
  • Implement a GraphQL client that talks to REST endpoints
  • Create a POC to convince your team to adopt GraphQL

Brace yourself! After this, you’ll want to use GraphQL in every project.

Prerequisites

In order to be successful in this tutorial, it’s important you have a basic understanding of the workings of JavaScript, React and REST APIs.

If you’re not confident in these topics, I recommend refreshing your knowledge before returning to this article when you’re ready. Don’t worry, we’ll wait for you.

The Benefits of GraphQL

First, let’s review some of the benefits of GraphQL from a high-level.

GraphQL is a query language that serves as a specification for APIs.

  • Tell it what you want: You get to decide what comes back. The results are always predictable because you get what you ask for.
  • One endpoint: Instead of having multiple endpoints, you only have one endpoint. That’s it. It makes life simpler for the frontend team.
  • Typed system: Declare the types of data you can ask for. This limits the number of mistakes you’ll make and allows the system to provide better error messages.

If you want to read more about GraphQL and its inner-workings, you can visit graphql.org.

Let’s See GraphQL in Action!

Now that we have moved past the prerequisites and covered the benefits of GraphQL from a high-level, we’re ready to build an app and see GraphQL in action

We will look at a basic React app that uses fetch to communicate with 4 CRUD endpoints (GET, POST, PUT, DELETE) and refactor it using apollo-rest-link.

I’ve already set up the project for this example app in Codesandbox.

As you can see, our project Spaceheroes uses a simple fake API called Reqres that fetches a list of users and allows you to edit, delete, or add a user. I’ve cloned the same project so that we can refactor it to use apollo-rest-link to communicate with the same endpoints.

To make it easy to follow, here’s a list of tasks we’ll be walking through:

  1. Add apollo-rest-link to app
  2. Refactor getting the list of users
  3. Refactor adding a new user
  4. Refactor deleting a user
  5. Refactor editing a user

Pretty straightforward, right? If you’re ready, I’m ready. Let’s do this.

1. Add apollo-rest-link

Before we do anything, let’s add the following dependencies:

npm install apollo-client apollo-cache-inmemory react-apollo apollo-link-rest apollo-link graphql graphql-anywhere qs --save

Our first task is to set up the Apollo client with our app. We’ll accomplish this in three steps.

First, we’ll create a new variable called restLink inside our index.js file. This initializes a RestLink instance to tell our Apollo App which endpoint we’ll be working with.

javascript
1
2
3
4
5
6
7
8
9
10
11
import React from ‘react’
import ReactDOM from 'react-dom';
import { ApolloClient } from 'apollo-client';
import App from './components/App.jsx';

const restLink = new RestLink({
  uri: ‘https://reqres.in/api/'
});

const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);

Next, we’ll set up our client.

javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React from ‘react’
import ReactDOM from 'react-dom';
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import App from './components/App.jsx';

const restLink = new RestLink({
  uri: ‘https://reqres.in/api/'
});

const client = new ApolloClient({
  link: restLink,
  cache: new InMemoryCache(),
});

const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);

The third and final step for this section is to wrap our <App/> in the <ApolloProvider /> component and pass that to ReactDOM.render:

javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import React from 'react';
import ReactDOM from 'react-dom';
import { RestLink } from 'apollo-link-rest';
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloProvider } from 'react-apollo';
import App from './components/App.jsx';

const restLink = new RestLink({
  uri: 'https://reqres.in/api/'
});

const client = new ApolloClient({
  link: restLink,
  cache: new InMemoryCache()
});

const ApolloApp = () => (
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>
);

const rootElement = document.getElementById('root');
ReactDOM.render(<ApolloApp />, rootElement);

Woo! Our Apollo app is all configured and ready for our queries and mutations.

2. Refactor getting the list of users

We’re going to refactor our <UsersList /> component by wrapping it in Apollo’s Query component and passing that data to the component directly. This is done with these three steps:

  1. Write the query
  2. Wrap component in Query component
  3. Update our App.jsx file

Before we can write our graphql query, we need to install a dependency called graphql-tag.

bash
1
npm install --save graphql-tag

a. Write the query

At the top of the utils.js file, we’re going to write our GraphQL query:

javascript
1
2
3
4
5
6
7
export const GET_USERS = gql`
  query getUsers {
    users @rest(type: "Users", path: “users?page=2”) {
      data
    }
}
`

We’re calling our query ‘getUsers’ which queries a REST endpoint at ‘users?page=2’ and returns the data key from the response payload.

b. Wrap component in Query component

Inside src/components/Userslist.jsx, add the following imports to the top of file:

javascript
1
2
import { Query } from 'react-apollo';
import { GET_USERS } from '../utils';

Change const UserList to const List. Then underneath that, create a new component called "UsersList":

javascript
1
2
3
4
5
6
7
8
9
10
11
const UsersList = () => (
  <Query query={GET_USERS}>
    {({ loading, error, data }) => {
      if (loading) return 'Loading...';
      if (error) return `Error! ${error.message}`;
      if (data) {
        return <List users={data.users.data} />;
      }
    }}
  </Query>
);

Here, we’re using Apollo’s <Query /> component which fetches our data based on the query we pass to query and then returns the data to us using the render-props pattern. When the data comes back, we pass it to our <List /> component.

Our final step is to go into src/components/App.jsx and do a quick clean-up:

  • Remove our state (we no longer store users here, instead they’re in the cache)
  • Remove componentDidMount and update import from utils
  • Remove props from <UsersList />

If we reload our app, we notice it loads our users just like before. However, the functionality to edit, remove and add new users is now broken but we’ll fix that next.

3. Refactor adding a new user

Since we’re storing the users in our cache instead of using local state, we need to refactor the way we add any new users to the list of Spaceheroes.

We’ll do these three things to solve that:

  1. Write the mutation
  2. Wrap <CreateUser /> in Mutation component
  3. Update our App.jsx file

Head back into our utils file and add the following mutation underneath our GET_USERS query:

javascript
1
2
3
4
5
6
7
8
9
10
11
export const ADD_USER = gql`
  mutation addUser($input: newUser!) {
    addUser(input: $newUser)
      @rest(type: "User", path: "users", method: "POST") {
      first_name
      last_name
      avatar
      id
    }
  }
`

Our mutation accepts a newUser object as the input. It makes a POST call to the users path and then returns the properties needed from the user object.

We’re going to refactor to our <CreateUser /> component by doing a few things. We’ll look at the code first and then I’ll walk you through everything that’s changed.

First we need to import the <Mutation /> component from react-apollo and import our mutation and query from our utils.js file.

javascript
1
2
3
import React from 'react'
import { Mutation } from 'react-apollo'
import { ADD_USER, GET_USERS } from '../utils'

After that, we’re going to change our component to the following:

javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const CreateUser = ({ onClick }) => {
  const newUser = {
    first_name: 'Ken',
    last_name: 'Wheeler',
    avatar:
      'https://i1.sndcdn.com/avatars-000068789647-lzbpw2-t200x200.jpg'
  }
  return (
    <Mutation
      mutation={ADD_USER}
      update={(cache, { data: { addUser } }) => {
        const { users } = cache.readQuery({ query: GET_USERS })
        cache.writeQuery({
          query: GET_USERS,
          data: {
            users: {
              data: users.data.concat([addUser]),
              __typename: 'Users'
            }
          }
        })
      }}
    >
      {(addUser, { data }) => (
        <div>
          <button
            onClick={() => {
              addUser({
                variables: { newUser }
              })
            }}
          >
            Add New User
          </button>
        </div>
      )}
    </Mutation>
  )
}

export default CreateUser

Stepping through this code, the first thing we do is remove the implicit return and add a function block. This is so we can declare const newUser with the new user data and then return the component.

We wrap our component in the <Mutation /> component which takes our two props: mutation and update:

  • The mutation prop takes our actual mutation which we defined earlier as ADD_USER (keep in mind it’s different from newUser).
  • The update prop accepts a function that allows us to perform logic to read the current list of users from the cache. This is accomplished by passing in the GET_USERS that we used earlier to fetch the users from our API.

Next, we use the writeQuery method from the cache to update the users by concatenating the new user on.

Using the render props method inside of <Mutation /> component, we render a function which receives the addUser method from our mutation. We can call on this during the onClick event of our <button /> and pass the newUser object inside the variables object.

To summarize what’s happening, the flow goes like this:

  1. The user clicks the <button /> which calls a function called addUser. This follows how we defined it in our mutation in the utils file on line 12, which accepts one variable called newUser.
  2. This causes our Apollo Client to run the update function of our <Mutation /> component. Our function reads the list of users from the cache then writes it, updating the users by concatenating addUser. That consists of all the data that is returned by our addUser mutation.
  3. Because we’ve updated the cache, which is where the users are stored, the <UsersList /> component re-renders and we see the new user in our app.

Nice work! We have the ability to add new users.

4. Refactor deleting a user

Making the user deletion function work is similar to creating a user. First, let’s create a new component called RemoveUser.jsx. Go ahead and copy everything from CreateUser.jsx.

Open up the utils.js file and add the following mutation:

javascript
1
2
3
4
5
6
7
8
9
10
11
12
export const REMOVE_USER = gql`
  mutation removeUser($input: userId!) {
    removeUser(input: $userId)
      @rest(
        type: "User"
        path: "users/{args.userId}"
        method: "DELETE"
      ) {
      userId
    }
  }
`

To delete the user, we’re passing in the userId to removeUser. We should include a response as part of good practice so we’re returning userId, even if our endpoint would really return a null response otherwise.

We have the mutation so now we can head back into RemoveUser.jsx to actually refactor it and get it working. Update line 3 to import the REMOVE_USER mutation we just created:

javascript
1
2
3
import React from 'react'
import { Mutation } from 'react-apollo'
import { REMOVE_USER, GET_USERS } from '../utils'

Then we’ll change the component to the following:

javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const RemoveUser = ({ userId }) => (
  <Mutation
    mutation={REMOVE_USER}
    update={(cache, { data: { removeUser } }) => {
      const { users } = cache.readQuery({ query: GET_USERS })
      cache.writeQuery({
        query: GET_USERS,
        data: {
          users: {
            data: users.data.filter(user => user.id !== userId),
            __typename: 'Users'
          }
        }
      })
    }}
  >
    {(removeUser, { data }) => (
      <span
        onClick={() => {
          removeUser({
            variables: { userId }
          })
        }}
      >
        Remove
      </span>
    )}
  </Mutation>
)

export default RemoveUser

Similar to <CreateUser />, we’re placing our <span> inside of the <Mutation /> component provided by react-apollo. Our component also receives the userId via props, which we can then pass to removeUser as a variable to be used inside of our mutation.

Once the user clicks the span, it will grab the list of users from the cache and remove the one with the id matching userId.

Before it will actually work, we need to do one last thing. Head into the User.jsx component. Import RemoveUser from the file we just created:

javascript
1
2
3
import React from 'react'
import styled from 'styled-components'
import RemoveUser from './RemoveUser.jsx'

Inside the <User /> component, replace the <span /> with <RemoveUser userId={id} />. And because we didn’t specifically pass it before, go into <UserList /> and inside the <User /> component, add the prop id={user.id}. That will make sure we have everything we need

Test it out. Ta-dah! We can now remove users.

5. Refactor editing a user

We have made it to the final step. Lucky for us this will also be quick. Go ahead and create a new component called <EditUser />.

Following the same steps as before, go into utils.jsx and add the following mutation:

javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export const EDIT_USER = gql`
  mutation editUser($input: updatedUser!) {
    editUser(input: $updatedUser)
      @rest(
        type: "User"
        path: "users/{args.updatedUser.id}"
        method: "PUT"
      ) {
      first_name
      last_name
      avatar
      id
    }
 }
`

Head back over to EditUser.jsx and add the following code:

javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import React from 'react'
import { Mutation } from 'react-apollo'
import { EDIT_USER, GET_USERS } from '../utils'

const EditUser = ({ userId }) => {
  const updatedUser = {
    id: userId,
    avatar:

    'https://www.gravatar.com/avatar/6da5da4155cdc12ed4be471afc912d7c?s=328&d=identicon&r=PG',

    first_name: 'Ken',
    last_name: 'Wheeler'
  }
  return (
    <Mutation
      mutation={EDIT_USER}
      update={(cache, { data: { editUser } }) => {
        const { users } = cache.readQuery({ query: GET_USERS })
        const copiedUsers = users.data.map((user, index) => {
          if (user.id === userId) {
            return editUser
          }
          return user
        })
        cache.writeQuery({
          query: GET_USERS,
          data: {
            users: {
              data: copiedUsers,
              __typename: 'Users'
            }
          }
        })
      }}
    >
      {(editUser, { data }) => (
        <span
          onClick={() => {
            editUser({
              variables: {
                updatedUser,
                userId: updatedUser.id
              }
            })
          }}
        >
          Edit
        </span>
      )}
    </Mutation>
  )
}

export default EditUser

Hopefully by now, you’ve noticed the pattern. We wrap the <Mutation /> component around our <span /> so that it had access to the editUser function which we call on click. When we trigger the editUser function, we pass the updatedUser (which in this case is hard-coded) and the userId. Our mutation updates the cache by replacing the old user data with the new user data.

To integrate this new component, go into User.jsx and update the imports:

javascript
1
2
3
4
import React from 'react'
import styled from 'styled-components'
import RemoveUser from './RemoveUser.jsx'
import EditUser from './EditUser.jsx'

Then replace the <span /> with <EditUser userId={id} />.

Open the app in the browser and watch the user update when you click update

Summary

As you can see, GraphQL is an exciting new technology that can be implemented on the frontend with a relatively low amount of effort.

To reiterate, here’s what we did:

Although our app is trivial, hopefully it demonstrates one way you can try out GraphQL in a frontend app that works with a REST API. For next steps, I suggest checking out Apollo’s fullstack tutorial where "you’ll learn how to build a graph API and connect it to a React frontend".

Thanks for reading! If you enjoyed this or found it helpful, please let me know on Twitter @jsjoeio - I would love to hear from you 😄

Links:

Joe Previte
Scottsdale, AZ, USA

Joe Previte, also known as JavaScript Joe, is a front-end developer spending most of working with React, Redux, Gatsby, Node and Express. In his spare time, he enjoys creating videos for egghead, writing articles relating to coding and leads an online meditation study group.

Community Contributor
Become a contributer