blog: Minimal three tier Golang app

with TypeScript frontend, REST, a .txt as “data store”, no external dependencies, all compiling to a single binary. The overwhelming result:

It alive

But it represents a starting point for a three tier application. Any real world example would use an actual database and maybe a better routing scheme. You need Go, tsc (npm install -g typescript), Make and a browser. Compile and start with make, then open http://localhost:8000 and hit the button.

$ cat Makefile
all: hello.js hello

hello.js: hello.ts
        which tsc || sudo npm install -g typescript
        tsc hello.ts

hello:
        go run hello.go


$ cat hello.ts
interface Answer {
    Current: Number
}

async function getCurrent() {
    let answer = await fetch("/current")
      .then(res => res.json())
      .then(res => res as Answer);
    let counter = document.getElementById('current');
    if (counter != null)
      counter.innerText = answer.Current.toString();
}


$ cat go.mod
module hello

go 1.16


$ cat hello.go
package main

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

//go:embed hello.js
var js string

var html string = `
<html>
<head><title></title>
<script>` + js + `
</script>
</head>
<body>
<h1 id="current"></h1>
<button onclick="getCurrent()">Load Current</button>
</body>
</html>
`

const storefile = "myfancydatastore.txt"

type Answer struct {
	Current int
}

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 main() {
	writeToStore(Answer{Current: 0})
	mux := http.NewServeMux()
	mux.HandleFunc("/", serveIndex)
	mux.HandleFunc("/current", handler)
	fmt.Println("Starting server on localhost:8000")
	http.ListenAndServe("localhost:8000", mux)
}

Posted in programming
2022-12-12 23:03 UTC