Read from Redux, declare PropTypes, and filter data with GraphQL
Sashko Stubailo
GraphQL is a powerful API system based around declaring a schema describing the capabilities of your backend, and then querying that schema with a nice language that projects the data into the shape you want. But that’s just one way to use it. There are so many other uses for the GraphQL query language, especially for cases on the client where importing and running your whole GraphQL schema doesn’t make sense. Enter graphql-anywhere, the library that powers Apollo Client.
What does it do?
As you may recall, writing a GraphQL schema on the server requires you to declare a set of types with fields and resolvers. Since graphql-anywhere doesn’t use a schema, instead you can write just one resolver that is called for every field of the query:
(fieldName, rootValue, args, context, info) => any
Then, you can run this resolver with a GraphQL query like so:
import graphql from 'graphql-anywhere' graphql(resolver, query)
It supports all of the features of GraphQL, including fragments, variables, directives, aliases, and more. Read the full docs for all the boring details, but let’s look at some exciting examples!
Examples
It turns out you can do a lot of useful things with a library that traverses GraphQL queries for you.
Filtering a large result
One of the best features of GraphQL is that you only get the data you ask for. Now you can take advantage of this even without a GraphQL server, by writing just two lines of code using graphql-anywhere (all the other lines set up the data):
import gql from 'graphql-tag';
import graphql from 'graphql-anywhere';
// I don't need all this stuff!
const gitHubAPIResponse = {
"url": "https://api.github.com/repos/octocat/Hello-World/issues/1347",
"title": "Found a bug",
"body": "I'm having a problem with this.",
"user": {
"login": "octocat",
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
"url": "https://api.github.com/users/octocat",
},
"labels": [
{
"url": "https://api.github.com/repos/octocat/Hello-World/labels/bug",
"name": "bug",
"color": "f29513"
}
],
};
// Write a query that gets just the fields we want
const query = gql`
{
title
user {
login
}
labels {
name
}
}
`;
// Define a resolver that just returns a property
const resolver = (fieldName, root) => root[fieldName];
// Filter the data!
const result = graphql(
resolver,
query,
gitHubAPIResponse
);
assert.deepEqual(result, {
"title": "Found a bug",
"user": {
"login": "octocat",
},
"labels": [
{
"name": "bug",
}
],
});
You can also use this feature as part of a utility to filter props for your React components, as documented here!
Reading from a Redux store
If you use Apollo Client already, then your Redux GraphQL data is managed for you. But if you’re using some other method to populate your Redux store and want to use GraphQL just to read from it (perhaps in preparation for getting started with a real GraphQL API on the server), you’ll need something to read that data out of the store and make it match the shape of a GraphQL query. You can use graphql-anywhere for that as well!
Here’s an example of reading from a store generated with the Redux companion Normalizr:
const data = {
result: [1, 2],
entities: {
articles: {
1: { id: 1, title: 'Some Article', author: 1 },
2: { id: 2, title: 'Other Article', author: 1 },
},
users: {
1: { id: 1, name: 'Dan' },
},
},
};
const query = gql`
{
result {
title
author {
name
}
}
}
`;
const schema = {
articles: {
author: 'users',
},
};
// This resolver is a bit more complex than others, since it has to
// correctly handle the root object, values by ID, and scalar leafs.
const resolver = (fieldName, rootValue, args, context): any => {
if (!rootValue) {
return context.result.map((id) => assign({}, context.entities.articles[id], {
__typename: 'articles',
}));
}
const typename = rootValue.__typename;
// If this field is a reference according to the schema
if (typename && schema[typename] && schema[typename][fieldName]) {
// Get the target type, and get it from entities by ID
const targetType: string = schema[typename][fieldName];
return assign({}, context.entities[targetType][rootValue[fieldName]], {
__typename: targetType,
});
}
// This field is just a scalar
return rootValue[fieldName];
};
const result = graphql(
resolver,
query,
null,
data // pass data as context since we have to access it all the time
);
// This is the non-normalized data, with only the fields we asked for in our query!
assert.deepEqual(result, {
result: [
{
title: 'Some Article',
author: {
name: 'Dan',
},
},
{
title: 'Other Article',
author: {
name: 'Dan',
},
},
],
});
Depending on the shape of your Redux store, you might need different code in the resolver.
Generating React PropTypes
If you follow the generally accepted style for writing React code, you probably put prop type declarations on all of your components. Since GraphQL results are deeply nested and sometimes complex, you might end up typing a lot of boilerplate if you want to be explicit about your props. What if you could just use the query you used to get the data as the validator for that prop? Now you can!
import { propType } from 'graphql-anywhere';
const CommentView = ({ comment }) => (
<div>
<p>{ comment.content }</p>
<p>Posted by { comment.postedBy.login } on {comment.createdAt }</p>
</div>
);
CommentView.commentFragment = gql`
fragment CommentView on Comment {
id
postedBy {
login
}
createdAt
content
}
`;
CommentView.propTypes = {
// Works with both queries and fragments!
comment: propType(CommentView.commentFragment).isRequired,
};
const goodData = {
id: 'asdf', postedBy: { login: 'sashko' },
createdAt: '2016-12-05T19:32:21+00:00', content: 'I love GraphQL!',
};
<CommentView comment={goodData} /> // OK
const badData = {
id: 'asdf',
createdAt: '2016-12-05T19:32:21+00:00', content: 'I love GraphQL!',
};
<CommentView comment={badData} /> // PropType error! postedBy missing.
So at this point we’ve seen that we can use graphql-anywhere for three things you need to do in almost every app: filtering, denormalization, and argument validation. Since you can use the same GraphQL query or fragment to do all of these things at once, that makes it a pretty convenient technology to work with.
Writing your own utilities
The execution process of a query using the graphql-anywhere library is similar to regular GraphQL, except there is no type information and it doesn’t support async data loading (since it’s designed for the client). Basically, the resolver is called once for every field on the query with the arguments provided, and the result from that field is passed into any children as the rootValue.
Because there is no schema involved, we can’t have separate resolvers for different types and fields. Instead, we get the fieldName as an argument, and we can pass down type information in an ad-hoc way through the root value. The first step to designing your graphql-anywhere utility is to ask: what should the root value contain? Since that’s the information passed down to the children, which powers the recursive nature of GraphQL, it’s the core consideration.
Want to work on tools like this? Contribute on GitHub or get a job working on Apollo!