Skip to main content

Getting started with Go and React - Webservice, Routing and Status Log

Shenzhen, China

Creating an REST API backend in Go and connecting it to a React.js frontend.

I want to prototype a Go backend for a Weather Cam tool. The backend should hold all the information related to all cameras and serve them on different routes. The backend then needs to be connected to a React.js frontend that displays the JSON data that is being served as well as to allow to add / delete cameras.

Basic Setup

mkdir go_backend && cd go_backend
go mod init backend

Configuration of the Webservice

./src/api/main.go

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"net/http"
)

// App version
const version = "1.0.0"

type config struct {
	// App configuration type
	port int
	env  string
}

type AppStatus struct {
	// Type for the status check
	Status		string `json:"status"`
	Environment	string `json:"env"`
	Version		string `json:"version"`
}

func main() {
	// The app configuration is either given as a command line flag
	// or set to a default value defined below
	var cfg config
	flag.IntVar(&cfg.port, "port", 4000, "Server port to listen on")
	flag.StringVar(&cfg.env, "env", "development", "Application environment (development|production")
	flag.Parse()
	// Print config to console
	fmt.Println("Backend (version", version, ") is running on port", cfg.port, "in", cfg.env, "mode.")
	// Prepare a webservice handling the route `/status`
	http.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {
		// Add status variables for the status check
		currentStatus := AppStatus {
			Status: "Ready",
			Environment: cfg.env,
			Version: version,
		}
		// Convert to JSON
		js, err := json.MarshalIndent(currentStatus, "", "\t")
		if err != nil {
			fmt.Println(err)
		}
		// Write JSON header, http Status code and status variables
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		w.Write(js)
	})
	// Start the webservice on the defined app port
	err := http.ListenAndServe(fmt.Sprintf(":%d", cfg.port), nil)
	if err != nil {
		log.Println((err))
	}
}

Test run:

go run ./src/api/
Backend (version 1.0.0 ) is running on port 4000 in development mode.
curl localhost:4000/status

{
"status": "Ready",
"env": "development",
"version": "1.0.0"
}

Adding a Router

HttpRouter is a lightweight high performance HTTP request router (also called multiplexer or just mux for short) for Go.

In contrast to the default mux of Go's net/http package, this router supports variables in the routing pattern and matches against the request method. It also scales better.

The router is optimized for high performance and a small memory footprint. It scales well even with very long paths and a large number of routes. A compressing dynamic trie (radix tree) structure is used for efficient matching.

go get -u github.com/julienschmidt/httprouter
  1. Create an application struct that holds the app configuration and a logger instance. This allows us to access those from other parts of our application.
  2. Separate the HTTP GET request for the status route into a separate handler. Here we need the receiver to access the environment variable app.config.env and print to the app.logger.Println(err).

./src/api/statusHandler.go:

package main

import (
	"encoding/json"
	"net/http"
)

func (app *application) statusHandler(w http.ResponseWriter, r *http.Request) {
	// Add status variables for the status check
	currentStatus := AppStatus {
		Status: "ready",
		// get environment from app receiver in main
		Environment: app.config.env,
		// Version is declared as a global variable in main
		Version: version,
	}
	// Convert to JSON
	js, err := json.MarshalIndent(currentStatus, "", "\t")
	if err != nil {
		// get logger from app receiver in main
		app.logger.Println(err)
	}
	// Write JSON header, http Status code and publish status variables
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	w.Write(js)

}
  1. Let HttpRouter handle the routing and call the correct handler for each route.

./src/api/routes.go:

package main

import (
	"net/http"

	"github.com/julienschmidt/httprouter"
)

func (app *application) routes() *httprouter.Router {
	// Create new router
	router := httprouter.New()
	// let router handle status route with statusHandler
	router.HandlerFunc(http.MethodGet, "/status", app.statusHandler)

	return router
}

Main now looks like:

./src/api/main.go:

package main

import (
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"time"
)

// App version
const version = "1.0.0"

type config struct {
	// App configuration type
	port int
	env  string
}

type AppStatus struct {
	// Type for the status check
	Status		string `json:"status"`
	Environment	string `json:"env"`
	Version		string `json:"version"`
}


// Receiver type to share informations with other
// components. See `app` variable below
type application struct {
	config config
	logger *log.Logger
}

func main() {
	// The app configuration is either given as a command line flag
	// or set to a default value defined below
	var cfg config
	flag.IntVar(&cfg.port, "port", 4000, "Server port to listen on")
	flag.StringVar(&cfg.env, "env", "dev", "Application environment (dev|prod")
	flag.Parse()

	// Create logger that writes stdout to console
	logger := log.New(os.Stdout, "", log.Ldate|log.Ltime)

	// application used as an receiver to make config
	// variables and logger available in other parts
	// of the application
	app := &application {
		config: cfg,
		logger: logger,
	}

	srv := &http.Server {
		Addr: fmt.Sprintf(":%d", cfg.port),
		Handler: app.routes(),
		IdleTimeout: time.Minute,
		ReadTimeout: 10 * time.Second,
		WriteTimeout: 30 * time.Second,
	}

	// Print config to console using logger
	logger.Println("Backend (version", version, ") is starting on port", cfg.port, "in", cfg.env, "mode.")

	// Start the webservice on the defined app port
	err := srv.ListenAndServe()
	if err != nil {
		log.Println((err))
	}
}

Test run the application with:

go run ./src/api/
2021/10/12 13:30:05 Backend (version 1.0.0 ) is starting on port 4000 in dev mode.