Writing

Why Go?

Jun 16, 2024 2 min

When Go 1.22 dropped, I was deep in TypeScript. I had bookmarked plenty of Go content but never got around to it. Eventually, I picked it up, and it stuck. If you’re still on the fence, Go’s own Why Go? page makes a solid case.

I’ve written Go on and off since then. Every time I go back to JavaScript, I’m reminded of how chaotic that ecosystem can be. Still, I’ve built a solid foundation with Go.

A few resources helped me along the way:

Let’s build a simple API server in Go. No frameworks, just the standard library.

Writing a simple API in Go

Install Go from golang.org/dl, then verify your installation:

$ go version

Initialize a new module:

go mod init github.com/ebarthur/golang/api

You should see a go.mod file. Think of it as the Go version of package.json.

Create a main.go file:

package main

import (
	"log"
)

func main() {
	server := NewAPIServer(":4032")
	err := server.Run()
	if err != nil {
		log.Fatal(err)
	}
}

Now create an api.go file:

package main

import (
	"log"
	"net/http"
)

type APIServer struct {
	addr string
}

func NewAPIServer(addr string) *APIServer {
	return &APIServer{
		addr: addr,
	}
}

func (s *APIServer) Run() error {
	router := http.NewServeMux()

	router.HandleFunc("GET /users/{UserID}", func(w http.ResponseWriter, r *http.Request) {
		userID := r.PathValue("UserID")
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(userID))
	})

	server := http.Server{
		Addr:    s.addr,
		Handler: RequestLoggerMiddleware(router),
	}

	log.Printf("Server is listening at %s", s.addr)
	return server.ListenAndServe()
}

Middleware

Add a logging middleware:

func RequestLoggerMiddleware(next http.Handler) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		log.Printf("METHOD: @%s\nPath: %s", r.Method, r.URL.Path)
		next.ServeHTTP(w, r)
	}
}

Add an auth middleware:

func RequireAuthMiddleWare(next http.Handler) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		token := r.Header.Get("Authorization")
		if token != "Bearer token" {
			http.Error(w, "Unauthorized", http.StatusUnauthorized)
			return
		}
		next.ServeHTTP(w, r)
	}
}

To chain them together:

server := http.Server{
	Addr:    s.addr,
	Handler: RequireAuthMiddleWare(RequestLoggerMiddleware(router)),
}

Run & Test

Start the server:

go run .

Test the endpoint:

$ curl http://localhost:4032/users/12

Include the auth token:

$ curl -H "Authorization: Bearer token" http://localhost:4032/users/12

The middleware checked the request header and verified the token, so we were allowed through. That was fun.

Maybe next time, we’ll connect a database, add more routes, and organize the project a bit more.