<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 /> &#8230;<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 &#8216;./Settings&#8217; </p> <p>const userQuery = gql`<br /> query userQuery {<br /> user {<br /> id<br /> firstName<br /> &#8230;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 &#8216;./__generated__/SettingsFragment&#8217; </p> <p>const Settings: FunctionComponent = ({ lastName, email }) = > {<br /> &#8230;<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>

Breakdown

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

...