<p>There have been few tools I’ve used in the last few years that have been more<br />
exciting to me than GraphQL. </p>
<p>GraphQL APIs have many benefits. They let us push more logic onto the server to<br />
let our front ends focus on user interaction. They let us minimize our payloads<br />
by only returning the information we ask for. They let us understand the shape<br />
of our data with an auto-generated schema and documentation. For all of these<br />
reasons, I’ve really enjoyed working with GraphQL in React and Elm projects. </p>
<p>A common pain point that I’ve seen in front ends consuming a GraphQL API is that<br />
GraphQL allows for so much flexibility that teams can struggle to figure out<br />
how they want to structure their projects. How do you plan for an infinite<br />
number of possible datasets? </p>
<p>In the middle of this structure churn, it’s common to see something like this:<br />
const HomePage: FunctionComponent = () = > {<br />
const { data } = useQuery(userQuery) </p>
<p> return ( </p>
<p> `Welcome back, ${data.user.firstName}!` </p>
<p> )<br />
} </p>
<p>const userQuery = gql`<br />
query userQuery {<br />
user {<br />
id<br />
firstName<br />
lastName<br />
email<br />
profile {<br />
verified<br />
avatarUrl<br />
}<br />
events {<br />
id<br />
name<br />
startTime<br />
endTime<br />
}<br />
…<br />
}<br />
}<br />
` </p>
<p>We have a component that only needs the user’s first name, but is querying for a<br />
lot of extra information, and even some associations. We’ve started using our<br />
GraphQL API as a REST API— we’re sending data that we don’t need right now over<br />
the wire because we might need it somewhere else. </p>
<p>This pain point is extremely common. Let’s take a look at some reasons we might<br />
be tempted to use some REST-ful patterns, and consider which more<br />
GraphQL-friendly patterns we can apply instead. </p>
<p> Something further down the tree needs this data </p>
<p>In the example above, the HomePage component directly renders a Settings<br />
component. If the Settings component requires the extra data that our<br />
userQuery is requesting, it makes sense that our HomePage component has to<br />
fetch it in order to pass it down to its child component. But that still leaves<br />
one problem: I can’t tell at a glance why the HomePage is requesting this<br />
data, or if it is used at all. </p>
<p>In these cases, I like using GraphQL fragments. Fragments let you construct a<br />
set of fields and include them in a query later. If I were to rewrite the query<br />
above, I would extract the data that the Settings component needs into a<br />
fragment:<br />
const Settings: FunctionComponent = ({ user }) = > {<br />
return ( </p>
<p> First name:<br />
{user.firstName}<br />
Last name:<br />
{user.lastName}<br />
Email:<br />
{user.email} </p>
<p> )<br />
} </p>
<p>export const settingsFragment = gql`<br />
fragment SettingsFragment on User {<br />
id<br />
firstName<br />
lastName<br />
email<br />
}<br />
` </p>
<p>Then, you can import a component’s fragment alongside it where it is used. In<br />
this case, I would refactor the userQuery to use the SettingsFragment:<br />
import { Settings, settingsFragment } from ‘./Settings’ </p>
<p>const userQuery = gql`<br />
query userQuery {<br />
user {<br />
id<br />
firstName<br />
…SettingsFragment<br />
}<br />
} </p>
<p> ${settingsFragment}<br />
` </p>
<p>This approach has a few benefits that have been really helpful in development.<br />
By co-locating our fragment definition with the component that will consume the<br />
data, we are being explicit about the relationship between the data we’re<br />
requesting and how the component functions with it. If the Settings<br />
component no longer requires an email in the future, we’re more likely to see<br />
that it should be removed from the query than if we had to comb through all of<br />
the requested fields on a bloated HomePage query. </p>
<p>We’ve also cleaned up the userQuery as defined on the HomePage component to<br />
make it easier to understand what data it needs. We still get the information<br />
that the child component requires, but we can hide the implementation details<br />
that aren’t relevant to the parent. </p>
<p>We’ll also get more descriptive automated type annotations, but we’ll get to<br />
that point in a bit 😉. </p>
<p> I want to make my queries reusable </p>
<p>This is one of the most common concerns that I hear when people write their<br />
first queries. Maybe you have a few different pages in the app that will need<br />
access to user data, and it seems easier to create a centrally located query<br />
that you can import and use in those cases. A file tree might look like:<br />
.<br />
├── components<br />
├── queries<br />
├── userQuery.ts<br />
├── eventsQuery.ts<br />
└── todosQuery.ts </p>
<p>Just import the userQuery and bam! User achieved. </p>
<p>The problem with this approach is that there is no such thing as the user<br />
query in GraphQL. There are many user queries, each with a unique combination<br />
of fields to be returned. The query for a user’s profile page vs their account<br />
details page can be completely different. Even though they are both querying on<br />
the User, they may be asking for different fields. Creating one query that<br />
returns the data for both of these scenarios means we’re missing out on<br />
selectively querying for only the data we need. </p>
<p>Unlike REST APIs, where we assume we should use a pre-existing endpoint unless a<br />
special case warrants it, I like to assume that GraphQL queries will be unique<br />
by default and co-locate them in the files where they are executed.<br />
Occassionally there will actually be a need for the same exact query later, and<br />
even in those cases, I prefer to just repeat the query definition. This way, if<br />
the needs of the two components diverge in the future, the extra data isn’t<br />
automatically added to two pages. </p>
<p> I want type annotations for my query results </p>
<p>When working with a client written in a typed language such as TypeScript or Elm,<br />
writing types for the query responses can quickly become tricky. Just like there<br />
is no one user query, there’s no one user type. The type changes depending<br />
on the fields requested in the query, which can become cumbersome when you<br />
have to update the types manually. </p>
<p>Luckily, many GraphQL clients have built-in tooling to auto-generate types based<br />
on the GraphQL queries, mutations, and fragments you define in your app. Relay<br />
has type emission support for Flow and Typescript, while Apollo CLI<br />
additionally supports code generation for Swift and Scala.<br />
dillonkearns/elm-graphql generates Elm decoders for valid GraphQL queries. </p>
<p>In addition to automating the repetitive tasks of re-defining slightly<br />
different versions of a User, these libraries will also ensure that the<br />
queries and mutations you compose are valid according to your GraphQL schema,<br />
which can save you time debugging failed requests. </p>
<p>In the example of our Settings component, I can now import the auto-generated<br />
type to use in my props definition:<br />
import { SettingsFragment } from ‘./__generated__/SettingsFragment’ </p>
<p>const Settings: FunctionComponent = ({ lastName, email }) = > {<br />
…<br />
} </p>
<p>Now, if I change the SettingsFragment to request different data, running the<br />
type generator will automatically update the type definition of<br />
SettingsFragment, and I will see type errors if I’m trying to use fields in<br />
my TypeScript code that I haven’t requested from the API. </p>
<p> I want to limit my network calls </p>
<p>Sometimes teams try to prevent extra round trips to the database by saving query<br />
responses to application state or context in order to prevent additional network<br />
requests on re-rendering. If you’re using Apollo or Relay, they can smartly<br />
handle this in-memory caching for you, returning records from the cache when<br />
they are queried multiple times, and ensuring that they are updated locally<br />
after data-altering mutations. These sophisticated caching strategies can<br />
simplify our application logic by removing the need for more complex state<br />
management libraries, and can save us time we might normally spend fussing over<br />
lifecycle renders or debugging inconsistencies in our DIY application state<br />
cache. </p>
<p>Apollo identifies records in its cache based on their __typename and unique<br />
identifier fields. The __typename field is added to responses by default<br />
unless you remove it, and the required fields validation from<br />
eslint-plugin-graphql can be a helpful nudge to make sure you request ids in<br />
queries and mutations. Apollo’s default cache-first policy will check if the<br />
data exists locally, and only query your GraphQL server if the data isn’t found.<br />
You can always opt for a different fetch policy if your use case requires a<br />
different behavior. </p>
<p> Taking full advantage </p>
<p>Moving away from a REST API and towards a GraphQL API is more than a shift in<br />
tooling; it’s also a shift to a new paradigm of how we think about the data and<br />
structure of our applications. When making decisions about how to structure our<br />
apps, it can be helpful to take a step back, and remember what we hoped to<br />
achieve by reaching for GraphQL in the first place.</p>