lewisdale.dev/src/blog/posts/2024/5/learning-go-day-7.md
Lewis Dale 1b1c3e6636
All checks were successful
Build and copy to prod / build-and-copy (push) Successful in 2m9s
Day 7: Project setup
2024-05-05 08:41:32 +01:00

3.2 KiB

title date tags excerpt
Learning Go: Day Seven 2024-05-07T08:00:00.0Z
learning
go
Kicking off the project by starting to build out an HTTP server

So, yesterday I decided I was going to build an uptime monitor and status page. To recap, here's the list of things I need to figure out how to do:

  • Run a web server
  • Render web pages with some dynamic content
  • Store & read data from/to a database
  • Send requets to a server & handle the responses
  • Do all of the above on a periodic schedule

I've decided to call the project Oopsie, because that's what I'll say when things go red, and have setup the repo on my git server. Like on day one, I initialised my Go module:

go mod init lewisdale.dev/oopsie

And then created my main.go file.

Creating a web server and handling my first request

Once again, Go's standard library comes to the rescue, as there are libraries for dealing with HTTP requests built right into the runtime. The net/http package has functions for creating a server, listening for requests, and even serving static assets, which is really useful.

My first server, and request handler, is the ever-original "Hello, world":

package main

import (
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Hello, World!"))
	})
	http.ListenAndServe(":8000", nil)
}

It's fairly straightforward. The http.HandleFunc call registers a handler on the root URL, and http.ResponseWriter.Write outputs a string, which is converted to a slice of bytes. Then I start the actual server on port 8000 with http.ListenAndServe. Truly, riveting stuff.

Pattern-matching handlers

The pattern syntax for HandleFunc is something called ServeMux, which I don't fully understand but fortunately the syntax is well-documented on the package docs. It's not too dissimilar to most other URL pattern-matching libraries1. Generally the syntax is:

  1. / - match the root
  2. /something - explictly match a URL
  3. /something-else/ - matching any URL where the path starts with /something-else/
  4. /route/{param} - match a URL that starts with /route/ and then has an arbitrary parameter in the second block of the path

You can also prefix the paths with either the request method, host name, or both. For example:

http.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("This was a GET request!"))
	})
http.HandleFunc("POST /", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("This was a POST request!"))
	})

So then if I perform a POST request:

curl -X POST http://localhost:8000
> This was a POST request!%  

And then just a GET request:

curl http://localhost:8000
This was a GET request!%  

That's a pretty powerful pattern-matcher to have straight out of the box. The only thing I'm less keen on is the w.Write syntax, but that's extremely minor. It'll be interesting to see how I get on.


  1. I still prefer Express-style pattern matching because inertia ↩︎