I created a web server with Go language and deployed it Google Cloud Run inside a Docker container.

You can try here the bingo sheet generator app .

Why Go instead Python for web server

Most of my programming experience is from Python. This is the reason I started the web app project with FastAPI framework.

As the name suggest, FastAPI is fast. Have to admit, I am not sure if it the core code is written in C or other high performance language.

In general, Go language is be much faster programming language than Python. This made me interested giving Go a try in a real case.

Go web server folder structure

app/
    main.go
    public/
        index.html
Dockerfile
docker-init.sh

Example of Go web server code

The app returns user a simple one page HTML file with Javascript functionality. So, there were not much requirements for the Go web server.

Here is an example of main.go file:

package main

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

func printHandler(w http.ResponseWriter, r *http.Request){
	fmt.Fprintf(w, "Hello world!")
}

func apiHandler(w http.ResponseWriter, r *http.Request){
	type Profile struct {
		country string
		cities []string
	}
	
	
	profile := Profile{"Finland", []string{"Helsinki", "Tampere", "Turku"}}
	response, err := json.Marshal(profile)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}	
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(200)
	w.Write(response)
}

func main() {

    fileServerHandler := http.FileServer(http.Dir("./app/public"))
    
    http.Handle("/", fileServerHandler)
    http.HandleFunc("/hello", printHandler)
	http.HandleFunc("/api", apiHandler)

    fmt.Printf("Starting server at port 1000\n")
    if err := http.ListenAndServe(":1000", nil); err != nil {
        log.Fatal(err)
    }
}

This creates three endpoints:

  • / returns HTML files from public folder
  • /hello prints Hello world!
  • /api prints {"country":"Finland","cities":["Helsinki", "Tampere", "Turku"]}

UI for the app with Go web server

The Go app serves anything in the public folder. If you go to http://example.com/, it returns file from public/index.html.

UI can be implemented by any framework. Or you can use plain HTML and Javascript. I injected React elements to the main element in index.html.

All app logic is in Javascript. Thus, the app does not need to contact the Go web server after the first load.

Dockerfile for Go web server

This is the Docker configuration for Dockerfile:

FROM golang:1.19

SHELL ["/bin/bash", "-c"]

WORKDIR /workdir
CMD mkdir bin

COPY ./docker-init.sh ./docker-init.sh
COPY ./app ./app

RUN go mod init mycompany/mymodule
RUN go mod tidy
RUN go get -d ./...

EXPOSE 1000

ENTRYPOINT ["/workdir/docker-init.sh"]

Dockerfile will execute docker-init.sh to use different commands for local development and Cloud Run deployment. go run is suitable for development environment to instantly apply to changes. go build on the other hand creates an executable file suitable for production environment Google Cloud.

#!/bin/bash

echo $ENV_NAME

if [ $ENV_NAME = "local" ]; then  
    echo "Docker entrypoint: Is local"
    go run /workdir/app/main.go
else
    echo "Docker entrypoint: Not local"
    GOOS=linux go build -o ./bin/linux_app ./app/main.go
    ./bin/linux_app
fi

Conclusion of the Go web server

Go web server would work just fine without Docker. I just find Docker easy for any development project to make all environments similar. Also, Google Cloud Run supports Docker containers out of the box.

In this scenario FastAPI vs Go server did not make any practical difference. Google Cloud cold start caused much more delay.

I have tried Go instead of Python also for basic scripting tasks. Conclusion there was that Python is much simpler with all high-level libraries. I recommend Go only if performance is really a major hurdle.

Cloud Run was easy and cheap choice to host small app with infrequent visits.

Extending the frontend part would not require any changes to the Go web server. You can add any HTML and Javascript files, and the server keeps working.

Build an API with the Go server would require some more learning.