Go Web Development 101

3 months to be productive

74% write API services

45% write Web apps

*Go Developer Survey 2020 Results

Hello Gopher


                        package main
    
                        import "fmt"
                        
                        func main() {
                          fmt.Println("Hello Gopher!")
                        }
                    
Run Code

                        go build hellogopher.go // 1. Compile code
                        ./hellogopher           // 2. Run binary
                        

                            go run hellogopher.go   // Compile code + run binary
                        

Development

JetBrains GoLand
Visual Studio Code
Vim Go

5 Facts about Go

  1. static type system
  2. garbage collection
  3. no inheritance
  4. built-in concurrency
  5. native execution
    Linux, Win, z/OS, 386, amd64, ARM, wasm, ...

Variables, Slices, Loops


                // Variable
                var kitty string = "Kitty"
                bella := "Bella"

                // Array (fixed length)
                namesArray := [3]string{kitty, bella, "Cleo"}

                // Slice (variable length)
                namesSlice := make([]string, 2)
                namesSlice[0] = kitty

                // Loop
                for i, name := range namesSlice {
                  fmt.Println("Hello " + name + "!")
                }
         

Struct


              type Cat struct {
                Name string
              }
              
              func main() {
                c := Cat{Name: "Kitty"}
                fmt.Println("Hello " + c.Name + "!")
              }
           

Errors


                      // Error as return value
                      func (c Cat) feed(food string) error {
                        if c.Name == "Kitty" && food != "Steak Tartare" {
                            return errors.New("Won't eat!")
                        }
                        return nil
                      }

                      func main() {
                        c := Cat{Name: "Kitty"}

                        // Handle error
                        err := c.feed("Caviar")
                        if err != nil {
                            fmt.Printf("%v won't eat it.", c.Name)
                        }
                      }
                  

The Cats App

Cats App
API

Set up Cats App


                  # Create directory
                  mkdir cats
                  cd cats
              

                    # Enable dependency tracking
                    go mod init goday.com/cats
              

                # Create go file
                touch main.go
              

Cat API simple


            func catAPIHandler(w http.ResponseWriter, r *http.Request) {
              fmt.Fprintf(w, "Meow!")
              w.WriteHeader(http.StatusOK)
            }
            
            func main() {
              http.HandleFunc("/api/cats", catAPIHandler)
              http.ListenAndServe(":8080", nil)
            }
          

Cat API with JSON


            type Cat struct {
              Name string
            }
            
            func catAPIHandler(w http.ResponseWriter, r *http.Request) {
              cats := make([]Cat, 1)
              cats[0] = Cat{Name: "Ginger"}
              json.NewEncoder(w).Encode(c)
            }
                        
            func main() {
              http.HandleFunc("/api/cats", catAPIHandler)
              http.ListenAndServe(":8080", nil)
            }
          

Cat API with JSON


            # Query cat API
            curl -s http://localhost:8080/api/cats | jq 
            [
              {
                "Name": "Ginger"
              }
            ]
          

Cat API with JSON


            type Cat struct {
              Name string `json:"name"`
            }
            
            func catAPIHandler(w http.ResponseWriter, r *http.Request) {
              cats := make([]Cat, 1)
              cats[0] = Cat{Name: "Ginger"}
              json.NewEncoder(w).Encode(cats)
            }
                        
            func main() {
              http.HandleFunc("/api/cats", catAPIHandler)
              http.ListenAndServe(":8080", nil)
            }
          

Test Cat API cat_api_test.go


          func TestCatAPIHandler(t *testing.T) {
            // 1. Create test request
            req, _ := http.NewRequest("GET", "/api/cats", nil)
          
            // 2. Create recorder (which satisfies http.ResponseWriter)
            recorder := httptest.NewRecorder()
          
            // 3. Invoke handler
            catAPIHandler(recorder, req)

            // 4. Check the response
            if recorder.Code != http.StatusOK {
              t.Errorf("Wrong status: got %v expected %v", recorder.Code, http.StatusOK)
            }
          }
          

Run Test


            # Run tests
            go test -v ./...
            === RUN   TestCatAPIHandler
            --- PASS: TestCatAPIHandler (0.00s)
            PASS
            coverage: 50.0% of statements
            ok      devopscon.com/cats      0.127s  coverage: 50.0% of statements
          

Build with Makefile


            .DEFAULT_GOAL := build
            BIN_FILE=cats

            build:
              go build -o dist/"${BIN_FILE}"

            test:
              go test -v ./...

            run:
              ./"${BIN_FILE}"

            clean:
              go clean
            

Demo

Cats App
Web

Cats Handler basic template setup

  
            func indexHandler(w http.ResponseWriter, r *http.Request) {
              var tpl = template.Must(template.ParseFiles("index.html"))
              tpl.Execute(w, nil)
            }

            func main() {
              http.HandleFunc("/", indexHandler)
              http.ListenAndServe(":8080", nil)
            }
          

Cats Handler with multiplexer

  
            func indexHandler(w http.ResponseWriter, r *http.Request) {
              var tpl = template.Must(template.ParseFiles("index.html"))
              tpl.Execute(w, nil)
            }

            func main() {
              mux := http.NewServeMux()
              mux.HandleFunc("/", indexHandler)

              http.ListenAndServe(":8080", mux)
            }
          

Serve files from Filesystem


            func main() {
              mux := http.NewServeMux()
              mux.HandleFunc("/", indexHandler)

              // Serve files
              fs := http.FileServer(http.Dir("assets"))
              mux.Handle("/assets/", http.StripPrefix("/assets/", fs))            

              http.ListenAndServe(":8080", mux)
            }
          

Serve files embedded in binary


            //go:embed assets
            var assets embed.FS

            func main() {
              mux := http.NewServeMux()
              mux.HandleFunc("/", indexHandler)

              // Serve files
              mux.Handle("/assets/", http.FileServer(http.FS(assets)))

              http.ListenAndServe(":8080", mux)
            }
          

Handler and HandleFunc

Cats Handler template with data

main.go
  
                type Cat struct {
                  Name string
                }

                func indexHandler(w ResponseWriter, r *Request) {
                  // Create cat slice
                  cat := make([]Cat, 1)
                
                  // Add cat ginger
                  cat[0] = Cat{Name: "Ginger"}
                
                  // Render template
                  tpl.Execute(w, cat)
                }
              
index.html
  
                <body>
                  <h1>Cats App</h1>
                  {{ range. }}
                    <h2>{{ .Name }}</h2>
                  {{ end }}
                </body>                
              

Cats Handler with Cats API

Query Cats API

                GET https://api.thecatapi.com/v1/breeds?limit=5
              

             [
                {
                   "id": "abys",
                   "name": "Abyssinian",
                   "image": {
                      "url": "https://cdn2.thecatapi.com/images/0XYvRd7oD.jpg"
                   }
                },
                {
                   "id": "aege",
                   "name": "Aegean",
                   "image": {
                      "url": "https://cdn2.thecatapi.com/images/ozEvzdVM-.jpg"
                   }
                },
                ...
             ]
              
Map JSON
  
                type Cat struct {
                  ID    string `json:"id"`
                  Name  string `json:"name"`
                  Image struct {
                    URL string `json:"url"`
                  } `json:"image"`
                }            
              

Cats Handler with Cats API

  
            func indexHandler(w http.ResponseWriter, r *http.Request) {
              resp, err := http.Get("https://api.thecatapi.com/v1/breeds?limit=5")
              if err != nil {
                http.Error(w, "Cats API error", http.StatusInternalServerError)
                return
              }
            
              // Create cat slice
              cat := make([]Cat, 5)

              // Read and parse body
              defer resp.Body.Close()            
              body, _ := ioutil.ReadAll(resp.Body)
              json.Unmarshal(body, &cat)
            
              tpl.Execute(w, cat)
            }
           

Middleware

  • cross cutting functionality for all requests
    (e.g. logging, authentication)
  • create a chain of handlers
    router => middleware handler => application handler
  • satisfy the interface http.Handler

Middleware to log requests

  
            func loggingMiddleware(next http.Handler) http.Handler {
              return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                log.Printf("%v requested URL %v", r.Host, r.URL)
                next.ServeHTTP(w, r)
              })
            }

            func main() {
              mux := http.NewServeMux()
              mux.HandleFunc("/", indexHandler)
            
              http.ListenAndServe(":8080", loggingMiddleware(mux))
            }
           

Demo

Basics
 

  • Dev Setup
  • Variables, Slices, Loops
  • Struct
  • Errors

Cats App
API

  • Handler (with JSON)
  • Http Test
  • Build

Cats App
Web

  • Multiplexer (Router)
  • File Server
  • Template
  • Middleware

Jan Stamer