How to add values to an HTTP query string in Go

Here’s how to add key/value pairs to an http.Reqest’s query string in Go: package main import ( "fmt" "net/http" ) func main() { // create a request req, _ := http.NewRequest(http.MethodGet, "http://www.example.com/", nil) // get the current query string from the http.Request values := req.URL.Query() // add or set values values.Set("myKey1", "myVal1") // overwrite! values.Add("myKey1", "myVal2") // append! // add values back to the request req.URL.RawQuery = values.Encode() // query string: mykey1=myVal1&myKey1=myVal2 // print the url fmt.Println(req.URL.String()) } The above program will output: ...

2025-08-26

Echo Router Parameters

I regularly have to look up how to create an Echo router context with (a) path parameters and (b) query parameters, so I’m making this post as a cheat sheet for my future self. Path parameters Example: /some/random/path/:id // create your context ctx := echo.New().NewContext(request, response) // add your path param ctx.SetParamNames("id") ctx.SetParamValues("foo") Query parameters Example: /some/random/path?key1=value1&key2=value2 // create your request request := httptest.NewRequest(http.MethodPost, "/some/random/path", nil) // get existing query params query := request.URL.Query() // add or set new params query.Add("key1", "value1") query.Set("key2", "value2") // encode the query params back to the request request.URL.RawQuery = query.Encode() // create your context with the request ctx := echo.New().NewContext(request, response)

2025-06-06

Go Backoff Algorithms

I ran across a blog post by Josh Bleecher Snyder today which has some beautiful backoff algorithms in Go. Capturing them here in case his blog ever goes offline. Algorithm 1 func do(ctx context.Context) error { const ( maxAttempts = 10 baseDelay = 1 * time.Second maxDelay = 60 * time.Second ) delay := baseDelay for attempt := range maxAttempts { err := request(ctx) if err == nil { return nil } delay *= 2 delay = min(delay, maxDelay) jitter := multiplyDuration(delay, rand.Float64()*0.5-0.25) // ±25% sleepTime := delay + jitter select { case <-ctx.Done(): return ctx.Err() case <-time.After(sleepTime): } } return fmt.Errorf("failed after %d attempts", maxAttempts) } func multiplyDuration(d time.Duration, mul float64) time.Duration { return time.Duration(float64(d) * mul) } Algorithm 2 I really like this one. It’s very easy to read. ...

2025-06-04

Playing around with trees in Golang

I was playing around with implementing Depth First Search (DFS) and Breadth First Search (BFS) in Go today. I made a tree which looked like this… 0 / \ 1 2 / \ / \ 3 4 5 6 / \ 7 8 I then made some Go code to walk the tree using DFS and BFS: package main import "fmt" // Node is a very basic struct which represents // a node in the tree. type Node struct { Value int Left *Node Right *Node } // dfs() walks the tree (starting at the provided root node) // in a depth first fashion. It uses a stack. func dfs(node *Node) { // base case if node == nil { return } fmt.Println("DFS visiting:", node.Value) if node.Left != nil { dfs(node.Left) } if node.Right != nil { dfs(node.Right) } } // bfs() walks the tree (starting at the provided root node) // in a breadth first fashion. It uses a queue. func bfs(node *Node) { // base case if node == nil { return } queue := []*Node{node} depth := 0 for len(queue) >= 1 { for _, node := range queue { queue = queue[1:] fmt.Printf("BFS visiting: %d (depth: %d)\n", node.Value, depth) if node.Left != nil { queue = append(queue, node.Left) } if node.Right != nil { queue = append(queue, node.Right) } } depth += 1 } } func main() { // define our tree (see above diagram) tree := &Node{ Value: 0, Left: &Node{ Value: 1, Left: &Node{ Value: 3, }, Right: &Node{ Value: 4, }, }, Right: &Node{ Value: 2, Left: &Node{ Value: 5, }, Right: &Node{ Value: 6, Left: &Node{ Value: 7, }, Right: &Node{ Value: 8, }, }, }, } // call our funcs dfs(tree) bfs(tree) } Running the code outputs: ...

2025-05-20

Enums in Go Part Deux - Marshaling and Unmarshaling!

My blog post from yesterday made me think… how can you marshal and unmarshal enums in Go correctly? So I modified internal/coffee/coffee.go and added a MarshalText() and UnmarshalText() methods: package coffee import ( "fmt" "strings" ) //go:generate stringer -linecomment -type=Coffee type Coffee int const ( Drip Coffee = iota // drip coffee Latte // latte Breve // breve Cappuccino // cappuccino ) func (c Coffee) MarshalText() ([]byte, error) { return []byte(c.String()), nil } func (c *Coffee) UnmarshalText(text []byte) error { want := string(text) for i := 0; i < len(_Coffee_index)-1; i++ { name := _Coffee_name[_Coffee_index[i]:_Coffee_index[i+1]] if strings.EqualFold(name, want) { *c = Coffee(i) return nil } } return fmt.Errorf("invalid Coffee %q", want) } I then modified main.go to add a new type named Drink which expected a Coffee in its field: ...

2025-05-15

Enums in Go

I was asked yesterday how you implement enums in Go. I didn’t know, so I spent some time this morning learning how to do it. It turns out this is ridiculously easy to do and Go has the stringer tool which makes it super simple. (stringer codegens the code which prints your enum as a string.) Example I created a repo with the following structure: go.mod main.go internal/coffee/coffee.go internal/coffee/coffee_string.go internal/coffee/coffee.go looks like this: ...

2025-05-14

Go Test Parallelism

This post documents some testing I did around whether unit tests in Golang are run in parallel. TLDR: Tests in a given package run serially, unless t.Parallel() is specified. To test this, I created two files in a directory named gotest: $ ls one_test.go two_test.go // one_test.go package main import ( "fmt" "testing" "time" ) func TestOne(t *testing.T) { fmt.Println("1 starts:", time.Now().String()) time.Sleep(5 * time.Second) fmt.Println("1 ends:", time.Now().String()) } // two_test.go package main import ( "fmt" "testing" "time" ) func TestTwo(t *testing.T) { fmt.Println("2 starts:", time.Now().String()) fmt.Println("2 ends:", time.Now().String()) } When I ran the tests, you can see that TestOne blocked TestTwo from running for five seconds: ...

2024-10-18

Go sub-slice gotchas

Thanks to Julia Evan’s latest post, I learned that creating new slices by sub-slicing an existing slice has an important caveat: They can sometimes use the same backing array! 😬 This is important to understand if you mutate the sub-slice. Take the below example, wherein we accidentally mutate s1! package main import ( "fmt" ) func main() { s1 := []int{0, 1, 2, 3, 4, 5} // len == 6, capacity == 6 s2 := s1[1:5] // len == 4, capacity == 5 // Modifies both s1 and s2, because they share the same backing array. s2[0] = 10 // Modifies both s1 and s2, because the length of s2 after adding the new // element does not exceed the capacity of the s2 slice. s2 = append(s2, 20) // Modifies only s2, because adding this element increases the length of s2 // beyond the capacity of the s2 slice. This allocats a new backing array, // copies all elements to it, and then appends 30 to it. So s1 does not get // modified. s2 = append(s2, 30) fmt.Println("s1", s1) fmt.Println("s2", s2) } Its output is: ...

2024-08-11

Configuring a Git pre-push hook to run unit tests

A coworker turned me onto this lovely technique the other day. You can use a git pre-push hook to run all of your Golang unit tests before pushing. To do this, make a the following file: $YOUR_REPO/.git/hooks/pre-push The file must be executable. The file’s contents should be: #!/bin/sh if ! go test ./... ; then echo echo "Rejecting commit. Unit tests failed." echo exit 1 fi Easy peasy.

2024-06-26

Running a subset of Go tests

It is often useful to run a subset of the tests in a Go project. You might do this because you only want to see test results for one package or to run tests faster. For these examples, assume your project is a Go module named examplemodule. It has the following structure: examplemodule |_ go.mod |_ go.sum |_ internal |_ foo | |_ foo.go | |_ foo_test.go |_ bar |_ bar.go |_ bar_test.go Here’s the most useful techniques I use to do this: ...

2024-03-07