Building a Single-Page App with React and Golang

October 10th, 2020

Outline

  • part 1: tech stack & base framework
  • installing dev env & tools
  • creating folder structure
  • creating "walking skeleton"
  • part 2: connecting database access

  • part 3: adding users + authentication

  • part 4: designing & building the UI frontend

  • part 5: modeling schema & building out the graphQL endpoints

  • part 6: connecting UI frontend to the backend

  • part 7: embedding assets & serving frontend in production

Hello friends!

This is a mult-part series on creating a single-page application using React & Golang. The goal for the series is to create a minimal blogging CMS that can be used for static site generators like Jekyll, Hugo, or Gatsby.

React will be used for the frontend (what users see) while Golang will be used for the API (which the frontend consumes).

Prerequisites

Before we get started, we'll need to make sure our development environment is correctly set up.

We will need:

  • NodeJS 1.14+ & Yarn 1.22+
  • Golang 1.15+

For the backend, we will need to install some tools:

  • sqlc
  • gqlgen

For the frontend, we will need:

  • npx
  • create-react-app

This guide also assumes decent knowledge of both Typescript, React & Golang.

What are we building?

The goal is a minimal blogging CMS that can be used to write & manage blog posts (stored as markdown files) that can be used by a static site generator.

It will also need some kind of authentication system so that only authorized users can actually manage the content.

The API will be built using GraphQL (specifically the gqlgen tool) while the frontend will consume the API using Apollo.

We will also be using Typescript alongside React.

We will also be using:

  • CSS-in-JSS (through styled-components)
  • GraphQL codegen (to generate Typescript types for graphql queries/mutations)

Why React?

Why Typescript?

Why Golang?

Setting up the project

We will be using a mono-repo approach to storing our code, meaning both the frontend and backend code will live in the same directory (and git repository).

Let's create some directorys as well as the base create-react-app project.

First we will need to create a root directory, which I have chosen to call blog-cms.

mkdir blog-cms && cd blog-cms

Now let's create the create-react-app project.

npx create-react-app frontend --template typescript

We will be using absolute imports over relative imports in our React project.

In order to set that up, we need to edit the frontend/tsconfig.json file

We will be adding baseUrl: "src" to the compilerOptions which allows us to import based on the absolute path of a file within the src directory, rather than the relative path of whatever file we are in.

The tsconfig.json should now look like this:

{
  "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": "react",
    "baseUrl": "src"
  },
  "include": [
    "src"
  ]
}

Let's also set up eslint and prettier:

In frontend/.eslintrc.json

{
  "env": {
    "browser": true,
    "es6": true
  },
  "globals": {
    "Atomics": "readonly",
    "SharedArrayBuffer": "readonly"
  },
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "ecmaVersion": 2018,
    "sourceType": "module"
  },
  "plugins": ["react", "@typescript-eslint", "prettier"],
  "extends": [
    "plugin:react/recommended",
    "airbnb",
    "plugin:prettier/recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended"
  ],
  "rules": {
    "prettier/prettier": "error",
    "no-restricted-syntax": ["error", "LabeledStatement", "WithStatement"],
    "@typescript-eslint/explicit-function-return-type": "off",
    "@typescript-eslint/no-explicit-any": "off",
    "@typescript-eslint/no-unused-vars": "off",
    "react/jsx-filename-extension": [2, { "extensions": [".js", ".jsx", ".ts", ".tsx"] }],
    "no-case-declarations": "off",
    "react/prop-types": 0,
    "react/jsx-props-no-spreading": "off",
    "no-param-reassign": "off",
    "import/extensions": [
      "error",
      "ignorePackages",
      {
        "js": "never",
        "mjs": "never",
        "jsx": "never",
        "ts": "never",
        "tsx": "never"
      }
    ],
  },
  "settings": {
    "import/resolver": {
      "node": {
        "paths": ["src"],
        "extensions": [".js", ".jsx", ".ts", ".tsx"]
      }
    }
  }
}

This tells eslint to use the airbnb rules as well as the prettier eslint rules.

Under rules, we disable some of the options that are not useful for us.

Next, let's configure prettier:

Add the following to frontend/.prettierrc.js

module.exports = {
  semi: true,
  trailingComma: "all",
  singleQuote: true,
  printWidth: 120,
  tabWidth: 2,
  bracketSpacing: true,
};

Some of the interesting options:

  • trailingComma: "all": add a trailing comma in places where it is optional
  • tabWidth: 2: two spaces for tabs