Easy and readable React/GraphQL/Node Stack — Part 3

Zac Tolley
Scropt
11 min readApr 27, 2019

--

The first article in this series introduced the idea of a web development stack based on PostgreSQL, Apollo GraphQL, ReactJS, hooks and Typescript and how GraphQL is radically different from JSON. This stack is easy to use and very productive. The article then demonstrated how to create a basic GraphQL server. In part 2 the GraphQL server was enhanced to introduce nested resolvers and mutations.

Part 3 will move the focus onto the front end and create a foundation for the React JS/GraphQL part of the stack. Whilst building the front-end we will use auto generated React JS hooks and Typescript. The code for this part is available on Github.

So far we have put all the server code in a server folder, we can keep things neat by creating the client code in a client folder. The initial code and build scripts will be generated using Create React App (CRA). Create React App takes the pain out of configuring the build system for your React application so you can focus on the application itself. Some people are fans of CRA whilst others argue that they cannot configure it to suit their needs or would rather create their own minimal configuration. I am willing to sacrifice a little control to gain easy to maintain code that any React developer can pickup.

Kicking things off

From the top level folder of the project use Create React App to create your client application.

npx create-react-app client

The result of this should be a new folder sitting alongside the server folder named ‘client’. The client folder contains a starter React JS application.

At the start of this project we configured Yarn to use workspaces, so we need to do a make a few changes here and there to work with workspaces. Create React App created a ‘yarn.lock’ file in the client folder, we don’t need this and we also don’t want the node_modules folder it created so remove them both. In the top level package.json file add ‘client’ to workspaces and run yarn install from the top level to re-load the dependencies.

"workspaces": [
"server",
"client"
]

Dependencies

Now theres a number of dependencies that we are going to need in order to build our application, including:

From the client folder, add the following dependencies

yarn add apollo-boost formik graphql history react-apollo react-apollo-hooks react-router-dom typescript yupyarn add @types/jest @types/node @types/react @types/react-dom @types/react-router-dom

Then in the top level folder we’ll add the generator dependency. Why are you putting it at the top level? The generator will span client and server, using the schema from the server, the queries we’ll add the client and the resulting code will generate code that will sit in the client. The ‘-W’ option is to allow yarn to add dependency to a parent project, normally it only wants you to add them to packages within your project.

yarn add -W graphql graphql-tag @graphql-codegen/add @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-react-apollo

Development Server

To start we’ll set things up so that we can run the server and the React development server side by side and make sure they auto-reload on change.

In the server, edit the packge.json and ensure you have a script to run the server using nodemon and another to run it as a regular server.

# server/package.json"scripts": {
"dev": "nodemon --inspect -e js,json,graphql src/index.js",
"start": "node src/index"
}

You may need to add the nodemon dependency in the server to the server too with yarn add.

In the client we want to tell it to proxy requests it can’t handle onto the server so that GraphQL calls etc are sent to it. Edit the client package.json file and add this to the end

# client/package.json"proxy": "http://localhost:4000"

Finally, we will add a tool called ‘concurrently’ to allow us to create a script to start both the client and server at the same time from the top level.

yarn add -W concurrently
# package.json
"scripts": {
"server": "yarn --cwd server dev",
"client": "yarn --cwd client start",
"dev": "concurrently \"yarn server\" \"yarn client\""
}

Now from the command line you can try yarn dev. To stop the server use Ctrl-c.

Your web browser will start and open the React JS page after it builds. If you want to use the playground you still need to access it via port 4000 (not sure why) but you can tell it to send your queries to port 3000 and they will be proxied through to the server.

Typescript

Next we need to setup Typescript. We’ve already setup the dependency, we just need to configure the settings for the compiler and the auto generator tool to use. Typescript relies on a tsconfig file to set these things, this is a config I got from using Create React App documentation and then having to modify it to work with the auto generator tool and the code in this example

# tsconfig.json{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"strictNullChecks": false
},
"include": ["src"]
}

The jsx preserve setting is needed when compiling JSX with Typescript and Babel. The file sits in the top level folder, though it will only be used by the client, this is purely because of a quirk of Visual Studio Code which expects the config file to be at the top of your project. We also need to add a tsconfig.json file to the client folder, but we’ll tell that to just inherit from the top level and that the client source is in the src folder, otherwise it’ll use ‘../src’

# client/tsconfig.json{
"extends": "../tsconfig",
"include":["src"]
}

Routing and Homepage

Now let’s get the React application so that we can at least render a simple page of our own, via a router, ready for the todo list. Apply the code shown below then we will talk through what it does. Get rid of the existing App.js and it’s associated files, and also delete the index.js. While you are at it, remove serviceworkers.js, seriously who uses that? If you are running the dev server there are times it’ll break while we are setting things up. Stop it for now, restart it after we are done.

# client/src/index.tsximport React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router } from 'react-router-dom'
import App from './App'import './index.css'ReactDOM.render(
<Router>
<App />
</Router>,
document.getElementById('root')
)
# client/src/App.tsximport React from 'react'
import { Route, Switch } from 'react-router-dom'
import Home from './pages/Home'const App = () => (
<Switch>
<Route exact path="/" component={Home} />
</Switch>
)
export default App# client/src/pages/Home/index.tsximport React from 'react'const Home = () => <h1>Todos</h1>export default Home

We start in the index.tsx. In this application the purpose of the index.tsx is to bring together libraries that help us build the application, in this case we are using react router, and attach the application to the webpage. For now we are simply say ‘please load React Router and then execute our application within it. Soon we’ll add more helpers to link to Apollo GraphQL.

The App.tsx file in this application will be used to help route requests to the correct part of the application based on their url. React Router lets us do this very easily and provide flexibility for nested routes and custom layouts. For now we’ll just use it to show the home page and later to goto other pages like detail and edit.

And last, but not least, a React JS that will be our homepage. Note that all the files use the ‘.tsx’ extension, when writing React components that contain JSX code you must use a ‘.tsx’ extension.

If you start the server you should now get a simple page.

Getting data

In the React JS application we are going to use a combination of hooks and Apollo GraphQL client. The flow that we will use is to create a GraphQL query first, which we can develop in the playground, and then save that in our code. After that we’ll use a tool to auto-generate the Typescript type information associated with that query and also produce a hook we can use. Finally we’ll put the data on the screen.

Writing the GraphQL query

We already have a query we used earlier to request a list of todos and their associated project. We will save that query in a ‘.graphql’ file and give it a name.

# client/src/graphql/queries/TodoList.graphqlquery TodoList {
todos {
id
title
complete
project {
title
}
}
}

The reason we put the query into a folder of it’s own, away from the component, is that as the application grows you will discover that queries overlap, or you make a change using a mutation and need to understand what other queries may need to be re-run to update the cache.

The next step is to add the GraphQL code generator, we only need to do this once and it’s the last bit of glue configuration I promise.

# codegen.ymloverwrite: true
schema: 'server/src/graphql/schema.graphql'
documents: 'client/src/**/*.graphql'
generates:
client/src/graphql/index.tsx:
config:
withHooks: true
plugins:
- add: '/* eslint-disable /*'
- 'typescript-common'
- 'typescript-client'
- 'typescript-react-apollo'

This goes at the top of our project. You can use the codegen tool to automatically generate some of this but it’s quicker to just use this config. It tells the code generator tool where to look for files, where the schema is and that it needs output its work to ‘client/src/graphql/index.tsx’ and we want to generate hooks. The plugins need to be configured so it knows how to generate the code we want. There are more plugins for generating server code for use with MongoDB and a whole bunch of things. Note the line that uses the add plugin, this line uses a plugin to add some text to the generated file to disable linting. The generated code clashes with the rules used by Create React App 3 during its build process and cannot be disabled #6871.

The last part of this config is we want to tell our development setup to re-generate this code every time we make a change to a query so we can see our changes instantly.

# package.json
"scripts": {
"server": "yarn --cwd server dev",
"client": "yarn --cwd client start",
"dev": "concurrently \"yarn server\" \"yarn client\" \"yarn codegen:watch\"",
"codegen": "gql-gen --config codegen.yml",
"codegen:watch": "gql-gen --config codegen.yml --watch"
}

Close your server down ‘Ctrl-c’ and start it up again, codegen will create the required ‘index.ts’ file with the query compiled into it. This file needs to be added to your ‘.gitignore’ file as it’s compiled code and you never check in compiled code.

GraphQL and hooks

So now, finally, we can update our component to display something. Let’s go back to the Home component and tell it to list the todos.

import React, { Fragment } from 'react'
import { useTodoListQuery } from '../../graphql'
const Home = (): JSX.Element => {
const { data, error, loading } = useTodoListQuery()
if (loading) return <p>Loading...</p>
if (error) return <p>Error: {error.message}</p>
if (!data || !data.todos) return <p>No data</p>
const { todos } = data return (
<Fragment>
<h1>Todos</h1>
<ul>
{todos.map(({ title, id }) => (
<li key={id}>{title}</li>
))}
</ul>
</Fragment>
)}
export default Home

We import a hook auto generated for us, the code generator has a convention that that dictates the name created, in this case it turned the ‘TodoList’ query into useTodoListQuery. Then we create a function based component, adding a return type in just to make our code a little more strict, and if you decide to use a stricter linter such as the one by AirBnb then it’s good practice to do this. The hook generated uses react-apollo-hooks, this creates a hook that returns an object with 3 properties: data, error and loading.

If you haven’t used hooks they can feel strange, after all you have called a function that returns 3 properties, is it called once per render? How does the fact that when you first call it then loading will be true and data null but later data will be populated and loading will be false? In reality the function will be called and return the initial results then when the results change the Home component will be called again but this time when that function is called the data will be populated and it will know not to issue the query. It can feel a bit strange at first if you analyse it but after a while you just accept that it works and you write your render code like any other render code, you write code that just concentrates on rendering the values.

So we call the hook to get data then we check the state of the properties we get back to decide how to render the output. If the component it called to render and loading is true, then we render a message to tell the user to wait. If the component is called to render and error contain a message, we display that. If the component is called and is no longer loading, there are no errors and data is populated with Todo data, we display it. While writing the map function for the data you may notice that tools such as Visual Studio Code will provide autocomplete for available properties, and when you hover your mouse over a property will give you type data for it. Compare this to using a Query component or HOC’s, it’s much cleaner and simpler and it gets better when we get to mutations later on.

There one small step we need to follow before we can give this component a try. The hook code created is a react-apollo-hook, this in turn uses the Apollo GraphQL client. These 2 libraries require the developer to create an instance of the Apollo GraphQL client and setup a Context to store it and the related data and cache associated with it. The top level React index.tsx file is the place to setup integrations.

# client/src/index.tsx
import ApolloClient from 'apollo-boost'import React from 'react'
import { ApolloProvider } from 'react-apollo-hooks'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router } from 'react-router-dom'
import App from './App'
import './index.css'
const client = new ApolloClient({
uri: '/graphql',
})
ReactDOM.render(
<Router>
<ApolloProvider client={client}>
<App />
</ApolloProvider>
</Router>,
document.getElementById('root')
)

Now you can restart your server and if your browser doesn’t fire up, goto http://localhost:3000/ and…

Summary

Go back and look through the code. Yes this is a very simple example so of course the code is simple but this stack scales easily without getting much more complex unlike applications using Redux. Compare this example to the same if you used Redux and/or JSON. Having to define the GraphQL query may seem like an extra step, but this approach reduces the backend code needed and also reduces the need to transform data.

All the code for this section is available on GitHub, go look there if yours isn’t working to figure out why. The code in GitHub is slightly different to that discussed, it brings in Bootstrap to set the fonts and stuff and uses its container class. This is easy for you to do, edit the index.html that comes with the code that CRA created, located in the public folder, and add a link to get CSS from Bootstrap.

The final part of this series will allow the user to add a new todo item and edit existing ones.

--

--