---
title: How to stop using your GraphQL API as a REST API
teaser: 'It''s so easy to get lost in the flexibility of GraphQL that we can miss
  out on some of its benefits.

  '
tags: graphql,rest,react,elm
author: Amanda Beiner
published_on: 2021-01-06
---

There have been few tools I've used in the last few years that have been more
exciting to me than [GraphQL].

GraphQL APIs have many benefits. They let us push more logic onto the server to
let our front ends focus on user interaction. They let us minimize our payloads
by only returning the information we ask for. They let us understand the shape
of our data with an auto-generated schema and documentation. For all of these
reasons, I've really enjoyed working with GraphQL in [React] and [Elm projects].

A common pain point that I've seen in front ends consuming a GraphQL API is that
GraphQL allows for _so_ much flexibility that teams can struggle to figure out
how they want to structure their projects. How do you plan for an infinite
number of possible datasets?

In the middle of this structure churn, it's common to see something like this:

```javascript
const HomePage: FunctionComponent = () => {
  const { data } = useQuery(userQuery)

  return (
    <div>
      <h1>
        `Welcome back, ${data.user.firstName}!`
      </h1>
      <Settings user={user}>
    </div>
  )
}

const userQuery = gql`
  query userQuery {
    user {
      id
      firstName
      lastName
      email
      profile {
        verified
        avatarUrl
      }
      events {
        id
        name
        startTime
        endTime
      }
      ...
    }
  }
`
```

We have a component that only needs the user's first name, but is querying for a
lot of extra information, and even some associations. We've started using our
GraphQL API as a REST API&mdash; we're sending data that we don't need right now over
the wire because we might need it somewhere else.

This pain point is extremely common. Let's take a look at some reasons we might
be tempted to use some REST-ful patterns, and consider which more
GraphQL-friendly patterns we can apply instead.

[GraphQL]: https://graphql.org/
[React]: https://thoughtbot.com/blog/tdding-with-react-and-apollo
[Elm projects]: https://thoughtbot.com/blog/optional-arguments-in-elm-queries

# Something further down the tree needs this data

In the example above, the `HomePage` component directly renders a `Settings`
component. If the `Settings` component requires the extra data that our
`userQuery` is requesting, it makes sense that our `HomePage` component has to
fetch it in order to pass it down to its child component. But that still leaves
one problem: I can't tell at a glance _why_ the `HomePage` is requesting this
data, or if it is used at all.

In these cases, I like using GraphQL [fragments]. Fragments let you construct a
set of fields and include them in a query later. If I were to rewrite the query
above, I would extract the data that the Settings component needs into a
fragment:

```javascript
const Settings: FunctionComponent<SettingsProps> = ({ user }) => {
  return (
  <div>
    <p>First name:</p>
    <p>{user.firstName}</p>
    <p>Last name:</p>
    <p>{user.lastName}</p>
    <p>Email:</p>
    <p>{user.email}</p>
  </div>
  )
}

export const settingsFragment = gql`
  fragment SettingsFragment on User {
    id
    firstName
    lastName
    email
  }
`
```

Then, you can import a component's fragment alongside it where it is used. In
this case, I would refactor the `userQuery` to use the `SettingsFragment`:

```javascript
import { Settings, settingsFragment } from './Settings'

const userQuery = gql`
  query userQuery {
    user {
      id
      firstName
      ...SettingsFragment
    }
  }

  ${settingsFragment}
`
```

This approach has a few benefits that have been really helpful in development.
By co-locating our fragment definition with the component that will consume the
data, we are being explicit about the relationship between the data we're
requesting and how the component functions with it. If the `Settings`
component no longer requires an `email` in the future, we're more likely to see
that it should be removed from the query than if we had to comb through all of
the requested fields on a bloated `HomePage` query.

We've also cleaned up the `userQuery` as defined on the `HomePage` component to
make it easier to understand what data _it_ needs. We still get the information
that the child component requires, but we can hide the implementation details
that aren't relevant to the parent.

We'll also get more descriptive automated type annotations, but we'll get to
that point in a bit 😉.

[fragments]: https://spec.graphql.org/June2018/#sec-Language.Fragments

# I want to make my queries reusable

This is one of the most common concerns that I hear when people write their
first queries. Maybe you have a few different pages in the app that will need
access to user data, and it seems easier to create a centrally located query
that you can import and use in those cases. A file tree might look like:

```
.
├── components
├── queries
    ├── userQuery.ts
    ├── eventsQuery.ts
    └── todosQuery.ts

```

Just import the `userQuery` and bam! User achieved.

The problem with this approach is that there is no such thing as *the* user
query in GraphQL. There are *many* user queries, each with a unique combination
of fields to be returned. The query for a user's profile page vs their account
details page can be completely different. Even though they are both querying on
the `User`, they may be asking for different fields. Creating one query that
returns the data for both of these scenarios means we're missing out on
selectively querying for only the data we need.

Unlike REST APIs, where we assume we should use a pre-existing endpoint unless a
special case warrants it, I like to assume that GraphQL queries will be unique
by default and co-locate them in the files where they are executed.
Occassionally there will actually be a need for the same exact query later, and
even in those cases, I prefer to just repeat the query definition. This way, if
the needs of the two components diverge in the future, the extra data isn't
automatically added to two pages.

# I want type annotations for my query results

When working with a client written in a typed language such as TypeScript or Elm,
writing types for the query responses can quickly become tricky. Just like there
is no *one* user query, there's no *one* user type. The type changes depending
on the fields requested in the query, which can become cumbersome when you
have to update the types manually.

Luckily, many GraphQL clients have built-in tooling to auto-generate types based
on the GraphQL queries, mutations, and fragments you define in your app. [Relay]
has type emission support for Flow and Typescript, while [Apollo CLI]
additionally supports code generation for Swift and Scala.
[dillonkearns/elm-graphql] generates Elm decoders for valid GraphQL queries.

In addition to automating the repetitive tasks of re-defining _slightly_
different versions of a `User`, these libraries will also ensure that the
queries and mutations you compose are valid according to your GraphQL schema,
which can save you time debugging failed requests.

In the example of our `Settings` component, I can now import the auto-generated
type to use in my `props` definition:

```javascript
import { SettingsFragment } from './__generated__/SettingsFragment'

const Settings: FunctionComponent<SettingsFragment> = ({ lastName, email }) => {
  ...
}
```

Now, if I change the `SettingsFragment` to request different data, running the
type generator will automatically update the type definition of
`SettingsFragment`, and I will see type errors if I'm trying to use fields in
my TypeScript code that I haven't requested from the API.

[Relay]: https://relay.dev/docs/en/type-emission
[Apollo CLI]: https://github.com/apollographql/apollo-tooling#code-generation
[dillonkearns/elm-graphql]: https://package.elm-lang.org/packages/dillonkearns/elm-graphql/latest

# I want to limit my network calls

Sometimes teams try to prevent extra round trips to the database by saving query
responses to application state or context in order to prevent additional network
requests on re-rendering. If you're using Apollo or Relay, they can smartly
handle this in-memory caching for you, returning records from the cache when
they are queried multiple times, and ensuring that they are updated locally
after data-altering mutations. These sophisticated caching strategies can
simplify our application logic by removing the need for more complex state
management libraries, and can save us time we might normally spend fussing over
lifecycle renders or debugging inconsistencies in our DIY application state
cache.

Apollo identifies records in its cache based on their `__typename` and unique
identifier fields. The `__typename` field is added to responses by default
unless you remove it, and the [required fields validation] from
`eslint-plugin-graphql` can be a helpful nudge to make sure you request `id`s in
queries and mutations. Apollo's default `cache-first` policy will check if the
data exists locally, and only query your GraphQL server if the data isn't found.
You can always opt for a [different fetch policy] if your use case requires a
different behavior.

[required fields validation]: https://github.com/apollographql/eslint-plugin-graphql#required-fields-validation-rule
[different fetch policy]: https://www.apollographql.com/docs/react/data/queries/#supported-fetch-policies

# Taking full advantage

Moving away from a REST API and towards a GraphQL API is more than a shift in
tooling; it's also a shift to a new paradigm of how we think about the data and
structure of our applications. When making decisions about how to structure our
apps, it can be helpful to take a step back, and remember what we hoped to
achieve by reaching for GraphQL in the first place.
