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.