You don’t need a fancy framework to use GraphQL with React

All you need is a good naming strategy

React offers a great language to declaratively describe User Interfaces (web or otherwise) and GraphQL offers a great language to declaratively describe data requirements. React gives us composability with its components and GraphQL gives us composability with its fragments. These two similarities allow for a great match between React and GraphQL (a match made in Facebook).

In this article, I’ll walk you through a simple example to connect a blog app written in React with a GraphQL API without using any frameworks like Relay or Apollo.

Those frameworks exist for very good reasons. Here are a few generic things such frameworks try to do (not a complete list):

  • Performance. For example, batching multiple network requests into one and displaying a big list of records without overwhelming the app memory through the use of pagination.
  • Efficiency. For example, asking servers for only the new data required and then merging new data with old data, or asking servers for only the data used in the visible window and asking for the rest of the data after that.
  • Dealing with failure. For example, employing a standard error handling for failing requests and a standard retry strategy for them.
  • Responsiveness. For example, showing expected data changes optimistically while waiting on a confirmation from the server. Then possibly performing a rollback on those optimistic changes if the server fails to persist the data.
  • Cache management. For example, deciding what to cache, where to cache it, and when to expire something in the cache.

Obviously, if you need all of these features and strengths, you need to either use a framework or reinvent a wheel. If you think you don’t need these features, you’re probably not thinking far enough into the future.

But sometimes using a framework is not an option. Sometimes a framework has a lot more features than what you’re after and sometimes introducing a framework means massive changes to existing structures. Frameworks usually decrease the flexibility you have with your code. This last point can be both a blessing and a curse.

This article is not meant to convince you that frameworks are bad (or good). It simply shows that using GraphQL with React is simpler than one might think. If you’re using REST APIs with React today without a framework, switching to a GraphQL API is a no-brainer.

Of course if you’re not sold on GraphQL yet, then you need to read a few different articles first.

The rest of this article assumes that you’re comfortable with GraphQL query language and React.js itself.

We’ll start with a ready React app which you can clone from Github:

git clone https://github.com/jscomplete/react-blog-example
cd react-blog-example
npm install
npm run nodemon
npm run webpack

You’ll need to run nodemon and webpack in two separate terminals as they both watch files for changes.

On http://localhost:8080/ you should now see a simple blog app with some in-memory mock data:

You can add new articles too. They will be saved to memory as well.

The code for this React app is simple but I’ve made a few design decisions to expose how to work with many different GraphQL queries and mutations.

You have an initial list of articles on the left which does include all article details. You get a single article’s complete information when you click on it. Also, an article object expects an author property which itself is an object. When we create a new article, we’ll have to create (or find) an author first then create an article for that author.

You can see the in-memory API inside src/api.js which is the file that we’re going to convert to work with GraphQL.

I made the in-memory API async already (which is always a good thing to do when faking APIs). The React app is already wired to work with this async API. All of React’s state is managed in the top-level App component.

What we’re going to do next is completely remove the in-memory API and replace it with GraphQL requests that are built to match the React components’ hierarchy.

Creating seed data in a GraphQL API

To build a GraphQL server schema that matches the data used by this React application, we’ll use GraphFront.com, which is a free GraphQL backend as a service where we can define data models and get a ready-to-use GraphQL API for them. This will save us some time as this article is not about creating a GraphQL API but rather using one.

Disclaimer: GraphFront.com is an in-house project at jsComplete. The UI is a React app that uses a GraphQL API without any frameworks. It’s all open-source.

Once you’re on the GraphFront dashboard, define the following models and fields (see images below for reference):

  • An Author has the following fields: firstName as String, lastName as String, and website as a String.
  • An Article has the following fields: title as String, date as Date, body as String, authorId as a relation field on Author.
Creating an Author model
Creating an Article model

GraphFront gives you a GraphQL endpoint and an API Key. Use them to insert some seed data into the GraphFront cloud-hosted database.

Navigate to your GraphQL endpoint and use the following mutation to insert a few authors:

mutation CreateAuthor($apiKey: String!, $authorInput: AuthorInput!) {
createAuthor(apiKey: $apiKey, input: $authorInput) {
id
}
}

Here is an example variables object to use with this mutation:

{
"apiKey": "YOUR_API_KEY_HERE",
"authorInput": {
"firstName": "The React",
"lastName": "Team",
"website": "ttps://twitter.com/reactjs"
}
}

The mutation will return the authorId value as created in the database. Create a few authors and make a record of their ids.

Here’s the mutation you can use to create an article:

mutation CreateArticle($apiKey: String!, $articleInput: ArticleInput!) {
createArticle(apiKey: $apiKey, input: $articleInput) {
id
}
}

And here’s an example variables object for it:

{
"apiKey": "YOUR_API_KEY_HERE",
"articleInput": {
"title": "React 16.0 is released",
"date": "10/10/2017",
"authorId": "USE_AUTHOR_ID_FROM_PREVIOUS_MUTATION",
"body": "Lorem ipsum dolor sit amet...."
}
}

Create a few articles. The React application will eventually work with the data you seed here. You can see the data in the GraphFront dashboard when you’re done:

Analyzing React components for data requirements

The first step is to understand the React application components hierarchy.

We start with an App component that renders an ArticleList component. The ArticleList component represents the left section of the app and it renders many ArticleRow components.

The App component also renders an Article component to represent the currently selected article with all of its information. The Article component uses an Author component to represent the author information.

There is also the NewArticleForm to create a new article. This component will use the same mutation we used above to insert an article. We’ll do that last.

Here’s the tree of the React application components as we see it so far:

App
ArticleList
ArticleRow
Article
Author
NewArticleForm

When we map this application to a GraphQL API, this tree of components translates as follows:

  • The root component (App in our case) will manage the many queries/mutations needed by the whole application.
  • Every child of the root component will translate into a query or a mutation.
  • Every child after that will translate into a fragment.

To keep things simple, we’ll construct one GraphQL document with many operations for the different data requirements of the whole application. Every component will basically contribute to that GraphQL document with its own partial data requirement. When we are ready to ask the server for data, we’ll pick one of the available operations in that GraphQL document. The server will only execute the operation we name.

Note: It’s possible (and easy) to construct several GraphQL documents that each represent a single requirement, but it requires a little bit more code. I did not go that route to keep things simple here.

We’ll put every component’s contribution to the main GraphQL document in a GraphQL static property on the component. Every parent component will include the contributions of its children. This will make the GraphQL property on the top-level component a complete and ready-to-use GraphQL document.

The App component uses two data elements:

  1. The list of articles which is an array
  2. A currentArticle which is on object

The App component does not know anything else about the structure of the data.

These two elements translate to two queries. We’ll make those queries use fragments from App’s children component as follows:

// In src/components/App.js
App.GraphQL = `
query GetArticleList($apiKey: String!) {
viewer(apiKey: $apiKey) {
data: articles {
...ArticleListFragment
}
}
}
  query GetArticle($apiKey: String!, $articleId: String!) {
viewer(apiKey: $apiKey) {
data: findArticle(id: $articleId) {
...ArticleFragment
}
}
}
  ${ArticleList.GraphQL}
${Article.GraphQL}
`;

Note a few things about the above queries.

  • To keep things simple, we used the same query structure, passed all queries an apiKey variable (which is required by GraphFront), and made all queries return a data property as their answer. This will allow us to use the same code to handle all response objects.
  • We’ve used two top fields from the GraphFront API, articles and findArcticle(id: String!). Those fields map to our need to fetch an array of articles and a single article object. GraphFront requires wrapping all queries in a viewer object that passes the apiKey.
  • The App component does not know anything beyond the type of the two data elements it uses. The details of what data to fetch in the list of articles are managed by the ArticleList component and the details of what data to fetch for the single current article are managed by he Article component. That’s why we used fragments for these details.
  • The fragments used in the queries above are for the direct children of the App component. This will be always the case. A component can only use the data requirements of its children components.
  • We’ve included the GraphQL static properties of all the App’s children components inside App.GraphQL. We will do this in every parent component.

Let’s now figure out the requirements of the App component’s children, ArticleList and Article. From this node inward, we will be defining fragments on certain types. A component could have multiple fragments based on the way it uses the data. It will also include all the fragments used by all its children. (Note, this is also a duplication that can be avoided with a bit more code, but to keep the example simple we’ll pass on that).

The ArticleList component has only one fragment. It’s on the Article object. Inside the ArticleList, we know that while rendering a list of ArticleRow components, we need an id for each article. Everything else is determined by the ArticleRow component:

// In src/components/ArticleList.js
ArticleList.GraphQL = `
fragment ArticleListFragment on Article {
id
...ArticleRowFragment
}
  ${ArticleRow.GraphQL}
`;

The ArticleRow fragment is simple. It does not have any children and requires an id, title, data on each article:

// In src/components/ArticleRow.js 
ArticleRow.GraphQL = `
fragment ArticleRowFragment on Article {
id
title
date
}
`;

The Article component requires the following properties on every article: title, date, body, and author which is an object. The properties of the author object are determined by the Author component (which is a child of the Article component):

// In src/components/Article.js
Article.GraphQL = `
fragment ArticleFragment on Article {
title
date
body
author {
...AuthorFragment
}
}
  ${Author.GraphQL}
`;

The Author component is simple. It does not have any children and it requires firstName, lastName, website properties on an Author type:

// In src/components/Author.js
Author.GraphQL = `
fragment AuthorFragment on Author {
firstName
lastName
website
}
`;

This completes all the data required by all the components.

The result of everything we did so far will be that the App component has a complete GraphQL document with two operations and four fragments:

App.GraphQL is now a complete GraphQL document

Let’s now start changing the API code that we have which currently works with the in-memory data. Just delete everything in src/api.js and add the following function instead:

import axios from 'axios';
const GraphQLEndPoint = 'YOUR_GRAPHFRONT_API_ENDPOINT';
const GraphQLApiKey = 'YOUR_API_KEY';
import App from './components/App';
export const sendOperation = (operationName, variables={}) => {
return axios.post(GraphQLEndPoint, {
query: App.GraphQL,
variables: {
...variables,
apiKey: GraphQLApiKey,
},
operationName,
}).then(resp => {
const GraphQLData = resp.data.data;
    return GraphQLData.viewer.data;
});
};

You’ll need to replace the YOUR_* values with the values you have in the GraphFront dashboard.

The sendOperation function is simple. It sends an Ajax request to your GraphQL endpoint with the App.GraphQL document, an operationName, and any variables needed by that operation. It always sends the apiKey because that’s a GraphFront requirement. Once we have the data back from the server, we make the promise return just the actual data object that we aliased in our queries above.

That’s all we need. We have the complete GraphQL document we defined our queries operation. We just need to invoke them at the right time.

In the App component, we need to replace the api.* methods that we used before with the in-memory API with sendOperation calls.

// In src/components/App.js

// In the componentDidMount function
// Replace:
api.getArticleList()
// With:
api.sendOperation('GetArticleList')

// In the setCurrentArticle function
// Replace:
api.getArticle(articleId)
// With:
api.sendOperation('GetArticle', { articleId })

We’re just picking operations here and passing them to GraphQL along with any variables they need.

When you refresh the React application now, it will completely read all of its data from GraphQL. Every component in the application has full responsibility of the data it requires. We do not over-fetch or under-fetch any data.

Adding Mutations

Adding mutations is no different when it comes to using the GraphQL document. However, the process of preparing data for a mutation is mostly an imperative one.

Since we need to first find or create an author and then create an article associated with that author, we need two mutation operations. GraphFront has a findOrCreate[Model] mutation that we can use for the author. I’ll just use it to find an author based on all of their properties (usually, you want to use this with something unique about the authors, like their emails):

mutation FindOrCreateAuthor(
$apiKey: String!,
$input: AuthorOptionalInput!
) {
mutationData: findOrCreateAuthor(
apiKey: $apiKey,
input: $input,
findFields: ["firstName", "lastName", "website"],
) {
id
}
}

The above is the exact API for the GraphFront mutation (which you can find in GraphQL’s generated documentation). When we supply the input properties for this mutation, it will either find the author object and return its id or create a new author object and returns its id.

The article mutation is simpler:

mutation CreateArticle(
$apiKey: String!,
$input: ArticleInput!
) {
mutationData: createArticle(
apiKey: $apiKey,
input: $input
) {
id
...ArticleFragment
}
}

No need for a find operation first here, but we need to pass an authorId to its input object. Note also how it uses the Article fragment to figure out what data to ask for after the creation of an article.

Both of these mutations need to be added to the App.GraphQL property.

Note also how we’re always aliasing the returned mutation data as mutationData. This allows us to modify our sendOperation once to account for both query data and mutations data.

// Change sendOperation in src/api.js
// The new data modifier handler function's body:
const GraphQLData = resp.data.data;
if (GraphQLData.viewer) {
return GraphQLData.viewer.data;
}
return GraphQLData.mutationData;

Ideally, sendOperation should be refactored into sendQueryOperation and sendMutationOpreation with some shared code, but we’ll keep it simple here.

To use the two mutations in the React application, we need to change the addArticle function on the App component:

// Replace addArticle in src/components/App.js
addArticle = (userInput) => {
const { author: authorInput, ...articleInput } = userInput;
api.sendOperation('FindOrCreateAuthor', {
input: authorInput
}).then(author => {
articleInput.authorId = author.id;
articleInput.date = new Date();
return api.sendOperation('CreateArticle', {
input: articleInput
});
}).then(newArticle => {
this.setState((prevState) => ({
data: {
articles: [...prevState.data.articles, newArticle],
currentArticle: newArticle,
},
newArticleForm: false,
}));
});
};
  • We first separate authorInput from articleInput since we are now storing authors first.
  • We then send the FindOrCreateAuthor operation with just the author input. This will return an object that has an id attribute.
  • We use the id attribute as the authorId input for the next operation. We add the current date on that input.
  • We send the CreateArticle operation with the prepared input. This operation will return the new article object which we then just need to make React aware of.

That’s all. The application now reads from and writes to a GraphQL API and all the data requirements are clearly documented where they belong!

I hope that was helpful. Thanks for reading.

samerbuna.com