How to Use GraphQL With React

What do we need when we want to create a simple web app in 2022? We need a client side application, a server and some kind of database. Quite simple, right? Nowadays, these layers are essential to provide a marvelous experience to all the people who will become users of our app. What's the app actually? I will go as classic as possible. Yes, it will be a To Do app!

In this article, I assume that you (the reader) have some background knowledge about NodeJS, React, and Typescript. The goal of this blog is to show how we can kick off GraphQL using these technologies without deep-diving into them.

To keep it simple we only focus on the App and Server communication which is created using GraphQL instead of REST. GraphQL is able to send data through HTTP too, but it is not mandatory. As you’ve already figured it out we will leave the classical database part out and in our case, this is an in-memory solution. Just note that it will reset on every server rerun. Let’s begin.

Side note: You can jump over all the explanations and run the finished app that can be found at the end of the article.

What is GraphQL?

Your first thought must be “What is GraphQL?”. GraphQL is a query language for APIs which provides a complete and understandable description of the data. It has the ability to give the clients the power to ask for all the data they want and nothing more.

I was not telling the full story when I stated that it is used instead of REST. However, in my implementation this is true but not in many other cases. For real, GraphQL is a new layer in your architecture that can hide many data sources like REST APIs, MongoDb or PostgreSQL connections, etc. behind a single point of truth: one endpoint. The client can get all the data it needs in a single request.

GraphQL APIs are organized in terms of types and fields, not endpoints. So it uses types to ensure clients only ask for what’s possible and they provide clear and helpful errors. Apps can use types to avoid writing parsing code manually.

Okay, now you get the idea. We will use a GraphQL library for the server and for the app. It’s called Apollo.

The Server

This backend is easy and can be implemented in one single index.js file. To start the project just create a directory with your preferred name and run the following commands:

mkdir into-graphql-server cd ./into-graphql-server npm init -y npm i --save apollo-server graphql nanoid

Type definitions and database

To begin with, we have to define what kind of data-, query- and mutation types can be provided by the server. The data type is clear. But the other two will need some explanation. GraphQL calls any request that asks data from the backend a query. And mutations are used as the rest of the operations from the CRUD, these are the requests that create, update or delete resources.

const typeDefs = gql` enum TodoItemStatus { IN_PROGRESS DONE } type TodoItem { id: String value: String status: TodoItemStatus } type Query { items: [TodoItem] } type Mutation { createTodoItem(value: String!, status: TodoItemStatus!): TodoItem updateTodoItem(id: String!, status: TodoItemStatus!): TodoItem deleteTodoItem(id: String!): TodoItem } `

For example, our database is just a javascript array with values. It is built-in and knows CRUD out-of-the-box.

const todoItems = [ { id: nanoid(), value: 'Test todo item 1', status: 'IN_PROGRESS', }, { id: nanoid(), value: 'Test todo item 2', status: 'DONE', }, ]

Resolvers

Type definitions tell us and the IDE information but they don't exactly explain how they should work. This is why resolvers exist. They include how to query and mutate the data and where the data can be found.

const resolvers = { Query: { items: () => todoItems, }, Mutation: { createTodoItem: (parent, { value, status }) => { const newTodoItem = { id: nanoid(), value: value, status: status, } todoItems.push(newTodoItem) return newTodoItem }, updateTodoItem: (parent, { id, status }) => { const updatedTodoItem = todoItems.find((item) => item.id === id) updatedTodoItem.status = status return updatedTodoItem }, deleteTodoItem: (parent, { id }) => { const deletedTodoItemIndex = todoItems.findIndex((item) => item.id === id) const deletedTodoItemCopy = { ...todoItems[deletedTodoItemIndex] } todoItems.splice(deletedTodoItemIndex, 1) return deletedTodoItemCopy }, }, }

Setup and start

Last but not least, we have everything to boot the server up with some configuration and a method call.

const server = new ApolloServer({ typeDefs, resolvers, csrfPrevention: true, cache: 'bounded', }) server.listen().then(({ url }) => { console.log(`🚀 Server ready at ${url}`) })

The App

To finish our application let’s talk about the other side of the HTTP, the React application that we will create to be the UI to show to-do list items, and it lets us interact with them. With these few commands to run you’ve got a base skeleton of the app:

npx create-react-app into-graphql-app --template typescript cd ./into-graphql-app npm i --save apollo-client graphql

Type definitions

This may be familiar from the server. We have written some type definitions for the GraphQL that are actually needed here too. It can be shared via some library or monorepo but as I mentioned I chose an easy-to-create but more-difficult-to-maintain way. I copied those types.

const typeDefs = gql`` // Same as the Server

Apollo Client

Similarly to the Apollo server, the client has to be configured the following way:

const client = new ApolloClient({ uri: 'http://localhost:4000', cache: new InMemoryCache(), typeDefs, })

Provider

Because this is a React app we should let React know about the Apollo Client that was configured above through the help of a provider.

root.render( <React.StrictMode> <ApolloProvider client={client}> <App /> </ApolloProvider> </React.StrictMode> )

The CRUD

And here is the fun part. We’ve arrived at where we are able to run queries and mutations (and subscriptions but we skip those this time) against that single endpoint. We will use React hooks from the Apollo Client library called useQuery for the queries and useMutation for the mutations. These hooks take two parameters, one GraphQL query, or mutation and one configuration object. UseQuery gives back an object containing the data or error and a loading boolean value. At the same time, useMutation gives back an array that contains the mutation function that we need to call with parameters, and a similar object to useQuery's returned value as the second value of the array. Let me show you how:

Create

const [createTodoItem] = useMutation<TodoItem>( gql` mutation CreateTodoItem($value: String!, $status: TodoItemStatus!) { createTodoItem(value: $value, status: $status) { id value status } } `, { refetchQueries: ['GetTodoItems'], } )
await createTodoItem({ variables: { value: trim(value), status: TodoItemStatus.IN_PROGRESS } })

Read

const { loading, error, data } = useQuery<{ items: TodoItem[] }>(gql` query GetTodoItems { items { id value status } } `)

Update

const [updateTodoItem] = useMutation<TodoItem>( gql` mutation UpdateTodoItem($id: String!, $status: TodoItemStatus!) { updateTodoItem(id: $id, status: $status) { id value status } } `, { refetchQueries: ['GetTodoItems'], } )
await updateTodoItem({ variables: { id: item.id, status: item.status === TodoItemStatus.DONE ? TodoItemStatus.IN_PROGRESS : TodoItemStatus.DONE }, })

Delete

const [deleteTodoItem] = useMutation<TodoItem>( gql` mutation DeleteTodoItem($id: String!) { deleteTodoItem(id: $id) { id value status } } `, { refetchQueries: ['GetTodoItems'], } )
await deleteTodoItem({ variables: { id: item.id } })

You may notice that configurational object with one property in mutations. RefetchQueries is an array of objects referencing queries (DocumentNode objects parsed with the gql function) or an array of names as strings of queries you've previously executed. By including this, it lets your app refetching certain queries after a particular mutation. GraphQL does not do refetching by default.

Experiences

GraphQL is different in many aspects of the way we use REST APIs. For example, the use of only one endpoint would seem strange instead of many, and that we have type definitions instead of a swagger document.

Requests are specified in a specific language in contrast to REST’s query parameters, path variables, and JSON objects in their body. And we have not talked about the responses yet. Responses via HTTP using GraphQL always gives back HTTP 200 status code (and sometimes not but this is not GraphQL's fault). These other status codes may indicate other problems with the server or the communication channel.

What if GraphQL gives an error? It sends an error property within a JSON object that clearly explains what exception occurred. So it doesn’t rely on HTTP, and it is not as verbose in that way but in every system, it always works the same. Which is good, to be honest.

Conclusion

At the end of the day it doesn't matter if your system will use GraphQL or REST APIs. They can serve the same purpose. Both have advantages and disadvantages that can help you vote for one or the other. However, I can tell that it was quite easy to pick up GraphQL to start a new project with it. Of course the Apollo libraries were handy and they boosted the implementation. Let's be honest, when was the last time we created something purely using the feature set of the languages?

GraphQL is different from REST, and it can be unusual for those who have never used it before. But every tech stack has its own rules that we can learn and understand. If we are able to abstract its rule set from REST’s then GraphQL could be a pretty great tool in our arsenal.

Finally*

*References

https://github.com/gabemiller/into-graphql-app

https://github.com/gabemiller/into-graphql-server

Blog Posts

View more

Graceful termination of Nginx in K8s

Graceful termination of Nginx in K8s

React Native vs. React Web: What's the Difference and When to Use Them

React Native vs. React Web: What's the Difference and When to Use Them