blog: Minimal three tier Golang app v2

Building on Minimal three tier Golang app, I made a three tier app with a Go frontend (as Web Assembly), a Go backend and a .txt as exemplary “data store”. Still no external dependencies, all compiling to a single binary. You need Go, Make and a browser. Compile and start with make, then open http://localhost:8000 and hit the button.

$ cat Makefile
all: hellofront hello

hellofront: hellofront.go
        GOOS=js GOARCH=wasm go build hellofront.go structures.go

hello:
        go run hello.go structures.go


$ cat structures.go
package main

type Answer struct {
        Current int
}


$ cat hellofront.go
package main

import (
        "encoding/json"
        "fmt"
        "net/http"
        "syscall/js"
)

var current js.Value

func main() {
        done := make(chan struct{}, 0)
        current = js.Global().Get("document").Call("getElementById", "current")
        js.Global().Set("GetCurrent", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
                go GetCurrent()
                return nil
        }))
        <-done
}

func GetCurrent() {
        resp, err := http.Get("/current")
        if err != nil {
                fmt.Println(err)
        }
        var res Answer
        err = json.NewDecoder(resp.Body).Decode(&res)
        if err != nil {
                fmt.Println(err)
        }
        current.Set("innerText", res.Current)
}


$ cat hello.go
package main

import (
	_ "embed"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
)

//go:embed hellofront
var wasm []byte

// copied from $(go env GOROOT)/misc/wasm/wasm_exec.js
//go:embed wasm_exec.js
var wasm_exec string

var html = `<html>
<head>
<script>` + wasm_exec + `</script>
<script>
const go = new Go();
var module = WebAssembly.instantiateStreaming(fetch("hellofront"), go.importObject).then((result) => {
	go.run(result.instance);
});
</script>
</head>
<body>
<h1 id="current"></h1>
<button onclick="GetCurrent()">Load Current</button>
</body>
</html>
`

const storefile = "myfancydatastore.txt"

func readFromStore() Answer {
	b, err := ioutil.ReadFile(storefile)
	if err != nil {
		// do something
	}
	var a Answer
	err = json.Unmarshal(b, &a)
	if err != nil {
		// do something
	}
	return a
}

func writeToStore(a Answer) {
	b, err := json.Marshal(a)
	if err != nil {
		// do something
	}
	ioutil.WriteFile(storefile, b, 0644)
}

func serveIndex(w http.ResponseWriter, r *http.Request) {
	if r.Method == http.MethodGet {
		w.Header().Set("content-type", "text/html; charset=utf-8")
		w.Write([]byte(html))
	}
}

func handler(w http.ResponseWriter, r *http.Request) {
	if r.Method == http.MethodGet {
		w.Header().Set("content-type", "application/json")
		a := readFromStore()
		b, err := json.Marshal(a)
		if err != nil {
			// do something
		}
		w.Write(b)
		a.Current = a.Current + 1
		writeToStore(a)
	}
}

func loadWASM(w http.ResponseWriter, r *http.Request) {
	if r.Method == http.MethodGet {
		w.Write(wasm)
	}
}

func main() {
	writeToStore(Answer{Current: 0})
	mux := http.NewServeMux()
	mux.HandleFunc("/", serveIndex)
	mux.HandleFunc("/current", handler)
	mux.HandleFunc("/hellofront", loadWASM)
	fmt.Println("Starting server on localhost:8000")
	http.ListenAndServe("localhost:8000", mux)
}
Posted in programming
2023-03-03 14:41 UTC