March 22, 2019
tl;dr: Use
relay --validate
to catch Relay validation errors in CI!
I’m a big fan of GraphQL (seriously, ask me about GraphQL). One tool I’ve been absolutely loving lately has been Relay. We use Relay at Clubhouse to build some really awesome features in Clubhouse for iOS. As of the writing of this blog post, we’ve got nearly 100 different components using Relay in some way, shape or form!
Relay is to GraphQL and data fetching what React is to DOM and UI. It’s a declarative way to dictate what data your components need, rather than how and when to fetch it.
A typical use-case of Relay looks something like this. Say you’ve got an social media app that shows a list of friends for the current user.
Each of these components gets data from a different part of
our tree. We can colocate our queries of how to fetch
the data for each component using Relay. A common way to do
this is using Relay’s createFragmentContainer
(there’s
other methods to do this too, to handle pagination,
refetching, etc. , but for the sake brevity in this post,
let’s focus on fragment containers).
Some of the components may look like this:
// in src/Avatar.js
const Avatar = ({ user }) => (
<Image src={user.thumbnailImgSrc} />
);
const AvatarContainer = createFragmentContainer(
Avatar,
graphql`
fragment AvatarContainer_user on User {
thumbnailImgSrc
}
`
);
// in src/FriendListItem.js
const FriendListItem = ({ user }) => (
<View>
<Avatar user={user.thumbnailImgSrc} />
<Text>{user.name}</Text>
</View>
);
const FriendListItemContainer = createFragmentContainer(
FriendListItem,
graphql`
fragment FriendListItemContainer_user on User {
name
...AvatarContainer_user
}
`
);
// in src/FriendsList.js
const FriendsList = ({ currentUser }) => (
<FlatList
data={currentUser.friends}
renderItem={({ item }) => (
<FriendListItem user={item} />
)}
/>
);
const FriendListItemContainer = createFragmentContainer(
FriendListItem,
graphql`
fragment FriendListItemContainer_currentUser on CurrentUser {
friends {
...FriendListItemContainer_user
}
}
`
);
Now each of our components composes together both is UI components and it’s data-requirements, declaratively!
__generated__
directory I’ve got here?The Relay Compiler will read through our source code to find Relay components and their colocated GraphQL queries to generate artifacts that will be used by the Relay runtime.
These artifacts look something like this (abbreviated here):
// from src/__generated__/AvatarContainer_user.graphql.js
const node /*: ReaderFragment*/ = {
kind: 'Fragment',
name: 'AvatarContainer_user',
type: 'User',
metadata: null,
argumentDefinitions: [],
selections: [
{
kind: 'ScalarField',
alias: null,
name: 'thumbnailImgSrc',
args: null,
storageKey: null
}
]
};
(node/*: any*/).hash = '693ff4889bc9965ae9f6512d628b7292'; // prettier-ignore
module.exports = node;
We can see that for the Avatar
component, the compiler
generates a static set of data for the GraphQL fragment it
needs to fetch the data.
Relay recommends checking in these artifacts from the Relay Compiler into your source control, as they’re crucial and necessary to run your app.
As you work through your app, you’ll likely run
yarn relay --watch
to run the compiler in watch mode to
automatically generate these artifacts and as you build new
components, pages, features and so on. You’ll see that as
your change your GraphQL queries, the Relay Compiler will
indicate what is changed, and you can see the changes in
your git diff
as well.
❯ yarn relay
yarn run v1.15.2
$ relay-compiler --src ./src --schema ./schema.graphql --watch
Writing js
Updated:
- AvatarContainer_user.graphql.js
Unchanged: 2 files
❯ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: src/Avatar.js
modified: src/__generated__/AvatarContainer_user.graphql.js
However, these artifacts are only autogenerated if you run
the Relay Compiler while you’re working! If a team member
(or even yourself) forgets to run yarn relay
while you’re
working, and a GraphQL query changes, your generated
artifacts will be out of sync with your product code! 😰
🤔 So how do we fix this? How do we prevent our product code’s queries from coming out of sync with our autogenerated Relay artifacts?
Luckily, Relay makes this easy.
The Relay Compiler includes
a flag called --validate
.
This flag will run the Relay Compiler and if there are any
autogenerated artifacts that will be overwritten based on
the GraphQL queries, the compiler will indicate that an
artifact is our of date and exit with an error.
❯ yarn relay --validate
yarn run v1.15.2
$ relay-compiler --src ./src --schema ./schema.graphql --validate
Writing js
Out of date:
- AvatarContainer_user.graphql.js
error Command failed with exit code 101.
This makes it really easy to validate your Relay codebase in
CI, just as you might run your tests in CI! Add
relay --validate
to your CI flow today and catch changes
in GraphQL queries before they land on master.
Written by Eli Perkins, a mobile engineer based in New York City. Say hello on Twitter.