How to Build a Simple Web App with React, Graphql, and Go

Chris Chuck
14 min readJun 1, 2018

--

* I will probably rewrite this in the next coming months to use Go 1.11; I have a few more articles on the way first though!

Introduction

The main point I want to highlight in this article is how Go is a powerful backend language that I feel is unfortunately overshadowed by the popularity of Node.js. In order to entice you to give Go a try for your next project, I’m going to show you how to build a not-todo list using React, Graphql, and Go. The Github repo with all of the following code can be found here as well.

The Setup

For this, I’m going to assume you have Node, Go, and Dep already installed on your machine.

For the database, I’m going to use Mongo via MLab, but you can use any DB you’d like. If you want to follow this tutorial exactly, you can make an account and they’ll give you 500mb of free storage!

First, let’s create the root directory and the cd into it. So fire up your terminal and let’s get to work.

mkdir app
cd app

Now that that’s done, let’s go ahead and make client and api folders.

mkdir client
mkdir server

We just created our beginning file structure, now we can start building!

The Go Server

Initialization

First things first, setup your server folder like the following:

Now we need to set our GOPATH, so run the following command:

export GOPATH={path to app directory}/server

For example, I have my application folder(medium-go-app) in Documents so I ran the following:

export GOPATH=/Users/chrischuck/Documents/medium-go-app/server

Great, now that we’ve set that, we can setup our app for dependencies.

Run the following:

cd server/src/app
dep init

After doing this, your files/folder structure should look as follows:

A quick note on why we’re using dep over go get is because dep is primarily used for downloading dependancies while go get pulls the entire source code, as detailed here.

Simple REST server

Let’s start thing’s off with making a simple HTTP server. Luckily, this is easy since Go comes with an HTTP library out of the box. In your main.go file add the following:

package mainimport (
"log"
"net/http"
)
func main() {
http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("Hello Mars!")
}))
log.Println("Now server is running on port 3000")
http.ListenAndServe(":3000", nil)
}

Now if you run go run main.go you should have a running Go server that prints “Hello Mars!” on any request to localhost:3000

Database

Great, now lets add our database. Kill your server and run the following:

dep ensure -add github.com/mongodb/mongo-go-driver/mongo

This package is in alpha as of now, but it is the official Go driver for MongoDB, so please shower them with Github stars here. Their docs can also be found here.

Make an app/data folder and add a mongo.go file to it. We’re separating out our database resources so we can easily access it from other packages in the future. Thus, your file structure should now look like so:

Then in the mongo.go file, add the following:

package mongoimport (
"context"
"github.com/mongodb/mongo-go-driver/mongo"
)
var Client, err = mongo.Connect(context.Background(), "Insert your MongoDB URI here!", nil)

Notice how Client is capitalized, this tells go to export it. We’ll use this later.

Graphql

To make this a Graphql API, we need to change a few things.

Change your main.go to this:

package mainimport (
"log"
"net/http"
"github.com/graphql-go/graphql"
"github.com/graphql-go/handler"
"app/queries"
"app/mutations"
)
var schema, _ = graphql.NewSchema(graphql.SchemaConfig{
Query: queries.RootQuery,
Mutation: mutations.RootMutation,
})
func main() {
h := handler.New(&handler.Config{
Schema: &schema,
Pretty: true,
})
http.Handle("/graphql", disableCors(h)) log.Println("Now server is running on port 3000") http.ListenAndServe(":3000", nil)
}
func disableCors(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
w.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type, Content-Length, Accept-Encoding") if r.Method == "OPTIONS" {
w.Header().Set("Access-Control-Max-Age", "86400")
w.WriteHeader(http.StatusOK)
return
}
h.ServeHTTP(w, r) })
}

As you can see, we open up a single /graphql endpoint where we will send our requests. On top of this, we added the graphql-go/graphql and graphql-go/handler packages which we’ll have to add using dep in the next step. The app/queries and app/mutations packages we’ll make later. The disableCors function disables CORs and that code is from here.

To get graphql-go/graphql and graphql-go/handler we need to run the following:

dep ensure

Notice how we didn’t have to explicitly add either graphql-go/graphql or graphql-go/handler? We just had to dep ensure to make sure we had everything.

In order to prevent our main.go file from becoming a monolithic mess, we’re going to break apart our Graphql components so we can work on the individually and easily. So to do this, make a mutations, a queries, and a types folder in src/app.

After running dep ensure and making your folders, you should have the following:

Types

Let’s define our types first so Graphql will know what the schema of our data will be. So, in the types folder make a notTodo.go file.

In that file, paste the following code:

package typesimport (
"github.com/graphql-go/graphql"
)
var NotTodo = graphql.NewObject(graphql.ObjectConfig {
Name: "NotTodo",
Fields: graphql.Fields{
"name": &graphql.Field{
Type: graphql.String,
},
"description": &graphql.Field{
Type: graphql.String,
},
},
})

This models our not-todo object, it has a name of the chore we’re not going to do and a description of it. These two fields are explicitly defined as strings as well.

Queries

Let’s start off with the queries because those are required by graphql-go.

In Graphql, you use queries to retrieve data. What’s special about Graphql queries is that you specify the exact fields you want to get and Graphql will send you that, nothing more, nothing less.

In the app/queries folder, make a queries.go file and a fields folder.

Paste the following into the queries.go file:

package queriesimport (
"github.com/graphql-go/graphql"
fields "app/queries/fields"
)
var RootQuery = graphql.NewObject(graphql.ObjectConfig{
Name: "RootQuery",
Fields: graphql.Fields{
"getNotTodos": fields.GetNotTodos,
},
})

Our root query is done! now we just need to complete the individual GetNotTodos query. In the fields folder, create a getNotTodos.go file and add this code:

package queriesimport (
"context"
"github.com/graphql-go/graphql"
"github.com/mongodb/mongo-go-driver/bson"

"app/data"
types "app/types"
)
type todoStruct struct {
NAME string `json:"name"`
DESCRIPTION string `json:"description"`
}
var GetNotTodos = &graphql.Field {
Type: graphql.NewList(types.NotTodo),
Description: "Get all not todos",
Resolve: func(params graphql.ResolveParams) (interface{}, error) {

notTodoCollection := mongo.Client.Database("medium-app").Collection("Not_Todos")
todos, err := notTodoCollection.Find(context.Background(), nil)
if err != nil { panic(err) }
var todosList []todoStruct for todos.Next(context.Background()) {
doc := bson.NewDocument()
err := todos.Decode(doc)
if err != nil { panic(err) }
keys, err := doc.Keys(false)
if err != nil { panic(err) }
// convert BSON to struct
todo := todoStruct{}
for _, key := range keys {
keyString := key.String()
elm, err := doc.Lookup(keyString)
if err != nil { panic(err) }
switch (keyString) {
case "name":
todo.NAME = elm.Value().StringValue()
case "description":
todo.DESCRIPTION = elm.Value().StringValue()
default:
}
}
todosList = append(todosList, todo)
}

return todosList, nil
},
}

As you can see, we have a lot going on here. First we need to get all of the documents from Mongo and convert them to a BSON type in order to get the parameters we require. From here, it’s just mapping the values to a struct, adding them to a slice, and returning it.

Mutations

Lastly, we can make our mutations so we can create new not-todos.

Mutations are used to, well, mutate data. They’re very similar in profile to queries but think of them as reserved for only changing data. You can, technically, change data with queries, but that’s bad design.

In the app/mutations folder, make a mutations.go file and a fields folder.

Paste the following into the mutations.go file:

package mutationsimport (
"github.com/graphql-go/graphql"
fields "app/mutations/fields"
)
var RootMutation = graphql.NewObject(graphql.ObjectConfig{
Name: "RootMutation",
Fields: graphql.Fields{
"createNotTodo": fields.CreateNotTodo,
},
})

The root mutation is done, we just have to fill out the fields. So make a new file called createNotTodo.go in your fields folder and add the following:

package mutationsimport (
"github.com/graphql-go/graphql"
"context"
"app/data"
types "app/types"
)
type todoStruct struct {
NAME string `json:"name"`
DESCRIPTION string `json:"description"`
}
var CreateNotTodo = &graphql.Field {
Type: types.NotTodo,
Description: "Create a not Todo",
Args: graphql.FieldConfigArgument{
"name": &graphql.ArgumentConfig{
Type: graphql.String,
},
"description": &graphql.ArgumentConfig{
Type: graphql.String,
},
},

Resolve: func(params graphql.ResolveParams) (interface{}, error) {

// get our params
name, _ := params.Args["name"].(string)
description, _ := params.Args["description"].(string)
notTodoCollection := mongo.Client.Database("medium-app").Collection("Not_Todos") _, err := notTodoCollection.InsertOne(context.Background(), map[string]string{"name": name, "description": description }) if err != nil { panic(err) } return todoStruct{name, description}, nil
},
}

We’re done! We just built out our Go/Graphql backend. Time for the front end. Just run go run main.go so we can have our server ready when the time comes.

The React App

Initialization

Let’s start by initializing our package.json with the following:

cd ../../../client
npm init -f

Now we need our dependencies, let’s start with the dev dependencies.

npm install --save-dev @babel/core @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators @babel/plugin-syntax-dynamic-import @babel/preset-env @babel/preset-react @babel/polyfill "babel-loader@^8.0.0-beta" html-webpack-plugin webpack webpack-cli webpack-dev-server

On a side note, if you need the list of Babel 7 packages, they can be found here.

And now the regular dependencies.

npm install --save apollo-boost graphql react react-apollo react-dom react-loadable react-router-dom

As you can see we’re using Apollo as our Graphql client and React Router v4 for routing (Async routing included later!).

Lastly, edit the scripts portion of your package.json to the following:

"scripts": {
"dev": "webpack-dev-server --mode development",
"build": "webpack --mode production"
}

File Structure

Copy the folders(except node_modules) and files you see here. Your package.json should already be made and you can go ahead and leave src/components and src/routes empty, we’ll get to those later.

Babel

Next on the list is setting up our .babelrc file. Open up that file and paste the following:

{
"presets": ["@babel/preset-env", "@babel/preset-react"],
"plugins": [
"@babel/plugin-proposal-class-properties",
["@babel/plugin-proposal-decorators", { "legacy": true }],
"@babel/plugin-syntax-dynamic-import"
]
}

Webpack

This is the final setup step before we get to writing actual React code. We’re almost there!

In your webpack.config.js, paste the following:

const HtmlWebPackPlugin = require('html-webpack-plugin');const htmlWebpackPlugin = new HtmlWebPackPlugin({
template: './src/index.html',
filename: './index.html'
});
module.exports = {
entry: [
'@babel/polyfill',
'./src/index.js',
],
output: {
filename: 'app.js',
path: __dirname + '/dist'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.css$/,
use: [
{
loader: "style-loader"
},
{
loader: "css-loader",
options: {
modules: true,
importLoaders: 1,
localIdentName: "[name]_[local]_[hash:base64]",
sourceMap: true,
minimize: true
}
}
]
}
]
},
devServer: {
historyApiFallback: true,
},
plugins: [htmlWebpackPlugin]
};

React

In src/index.html, paste the following:

<!DOCTYPE html>
<html>
<body>
<div id="app" />
</body>
</html>

And in your src/index.js paste the following as well:

import React from "react";
import ReactDOM from "react-dom";
ReactDOM.render(
<div> Hello World! </div> ,
document.getElementById('app')
);

Now we have a basic react app!

if you run npm run dev in your terminal and go to localhost:8080 in your browser and you should see your Hello World React app!

Routing

Now let’s use React Router to add routing to our app. Right off the bat, we’re going to implement async routing because with React Router, it’s just that easy. React Router builds off of React Loadable, which we already added to our dependencies earlier.

First, let’s make a file in the src/components folder calling loading.js ; this React component is going to be rendered when the route you’ve switched to hasn’t loaded yet. We could make it a super cool loading screen, but for the sake of simplicity, we’re just going to render a div that says “loading.”

So in your src/components/loading.js paste the following:

import React from 'react'const Loading = () => <div>loading</div>export default Loading

Now we can start building the actual routes!

Let’s start off with the landing and 404 routes to get our feet wet, then we’ll make the cool stuff after.

In the src/routes folder create a new folder called home and add an index.js file and a home.js file to your new folder.

Again, like the loading component, we’re just going to display a simple component but this time, with the words “home.” So, in the home.js file add the following:

import React from 'react'const Home = () => (<div>Home!</div>)export default Home

Great, now that our home component is setup, we can wrap it in a loadable component so React Router can asynchronously load it. But for separation of concerns, we’re going to do this in the index.js file.

So in the index.js file, put the following:

import React from 'react'
import Loadable from 'react-loadable'
import Loading from '../../components/loading'const LoadableComponent = Loadable({
loader: () => import('./home'),
loading: Loading,
})
const LoadableHome = () => <LoadableComponent />export default LoadableHome

As you can see, this is where React Loadable and our loading component come in. We pass in the homecomponent that we just created and our loading component to React Loadable and then just export it.

Now let’s build our 404 route, it’s the exact same process. If you’re new to React, try doing this on your own and checking back afterwards.

In yoursrc/routes folder create a new folder called notFound and add an index.js file and a notFound.js file to your new folder.

The notFound.js file should look like the following:

import React from 'react'const NotFound = () => <div>404 Not Found :( </div>export default NotFound

And the index.js file should be as follows:

import React from 'react'
import Loadable from 'react-loadable'
import Loading from '../../components/loading'const LoadableComponent = Loadable({
loader: () => import('./notFound'),
loading: Loading,
})
const LoadableNotFound = () => <LoadableComponent />export default LoadableNotFound

Now that we have our routes done, we need to build our app router. Create an index.js file in src/routes and add the following:

import React from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import Home from './home'
import NotFound from './notFound'
class AppRouter extends React.Component {
render() {
return (
<Router>
<Switch>
<Route exact path='/' component={Home} />
<Route exact component={NotFound} />
</Switch>
</Router>
)
}
}
export default AppRouter;

As you can see, we simply imported our routes and passed them as the component prop to React Router’s Route component. Notice the path prop in the Home route as well, when you go to localhost:8080/ you’ll be greeting with out landing page that we created earlier. But see how the NotFound route, doesn’t have a path prop? This is because it’s our wildcard route, any route, localhost:8080/not-home for example, will default to this route. Make sure this route is always last in your list of routes as well.

And now the final step in adding routes to our app.

change your src/index.js to the following:

import React from "react";
import ReactDOM from "react-dom";
import AppRouter from './routes'ReactDOM.render(
<AppRouter />,
document.getElementById('app')
);

Awesome, now we have routing on our app. If you go to localhost:8080 you’ll be greeted with our landing and if you go to any other route, you’ll see a 404 page.

Apollo

Apollo is a Graphql client that makes using Graphql in your React app a breeze. There are other clients as well, such as Relay from Facebook, but I just find Apollo to be easier to use and more flexible.

All we need to do to setup Apollo is change your src/index.js file to the following:

import React from "react";
import ReactDOM from "react-dom";
import ApolloClient from 'apollo-boost';
import { ApolloProvider } from 'react-apollo';
import AppRouter from './routes'const client = new ApolloClient({
uri: 'http://localhost:3000/graphql'
});
ReactDOM.render(
<ApolloProvider client={client}>
<AppRouter />
</ApolloProvider>,
document.getElementById('app')
);

Simple, right? That’s it for adding Apollo! Now let’s start communicating with our api.

Change your routes/home/home.js file to the following:

import React from 'react'
import { gql } from 'apollo-boost';
import { graphql, compose } from 'react-apollo';
// example of a graphql query
const query = gql`
query GetNotTodos{
getNotTodos {
name
description
}
}
`
// example of a graphql mutation
const mutation = gql`
mutation CreateNotTodo($name: String, $description: String) {
createNotTodo(name: $name, description: $description) {
name
description
}
}
`
@compose(
graphql(query),
graphql(mutation)
)
class Home extends React.Component {
render() {
console.log(this.props)
return (
<div>
Home still!
</div>
)
}
}
export default Home

What did we do here? First, we changed the Home component to a regular component from a functional one. Next is where the magic happens. We wrapped our component with the graphql wrappers. What this means is that our query ran automatically (Yes, I’m not lying! Check the console.log in data.getNotTodos) and our mutation function is passed down as a prop.

Before we can create a not-todo, let’s display them first.

Change your Home component to the following:

class Home extends React.Component {
render() {
const { getNotTodos = [] } = this.props.data
return (
<div style={{display: 'flex', justifyContent:'center', marginTop: '10%'}} >
<table style={{border:'1px solid black'}}>
<tr>
<th style={{border:'1px solid black'}}>name</th>
<th style={{border:'1px solid black'}}>description</th>
</tr>
{
getNotTodos.map(notTodo => (
<tr>
<td style={{border:'1px solid black'}}>{notTodo.name}</td>
<td style={{border:'1px solid black'}}>{notTodo.description}</td>
</tr>
))
}
</table>
</div>
)
}
}

Great, now we have a simple table to display the name of what we’re not going to do and a description of it as well.

Now we just need to be able to add a not-todo.

Again, change the Home component to this:

class Home extends React.Component {
constructor(props) {
super(props)

this.state = {
name: '',
description: ''
}
}
onChange = event => {
this.setState({ [event.target.name]: event.target.value})
}
save = () => {
this.props.mutate({
variables: {
name: this.state.name,
description: this.state.description
},
refetchQueries: ['GetNotTodos']
})
}
render() {
const { getNotTodos = [] } = this.props.data
return (
<div style={{display: 'flex', justifyContent:'center', marginTop: '10%'}} >
<div style={{display: 'flex', flexDirection:'column'}}>
<h3 style={{margin: '0px'}}>Add something to not do!</h3>
<input placeholder='Name' name='name' onChange={this.onChange} />
<input placeholder='Description' name='description' onChange={this.onChange} />
<button onClick={this.save}>Save</button>
</div>
<table style={{border:'1px solid black'}}>
<tr>
<th style={{border:'1px solid black'}}>name</th>
<th style={{border:'1px solid black'}}>description</th
</tr>
{
getNotTodos.map(notTodo => (
<tr>
<td style={{border:'1px solid black'}}>{notTodo.name}</td>
<td style={{border:'1px solid black'}}>{notTodo.description}</td>
</tr>
))
}
</table>
</div>
)
}
}

What did we just do? Well first we gave our app state in the constructor and hooked up the inputs so they save data in this.state. From here, we created the save function, which takes the mutation we created earlier, and passes it the name and description fields. On top of this, we tell it get refetch the GetNotTodos query once the mutation is complete.

This means, when you add a not-todo, it updates our list immediately!

Before Saving
After Saving

Conclusion

Thank you for reading my very first Medium article and that’s it for creating a web app with React, Graphql, and Go. Hopefully you enjoyed making a web app with me and hopefully you’ll give Go a try on your own soon!

If you have any comments, fixes, or suggestions, please comment them below!

--

--