75% of graduated CNCF projects use Go (Golang). Notable cloud projects that use Go include Kubernetes, Docker, Helm, Prometheus and consul. Some of these projects expose Go to the surface. kubectl
allows you to use Go templates. Helm uses Go templating also in its yaml files. As a result, I've been looking into Go. So let's build a server in Go (Golang). This is like the "hello world" of backend development.
Getting started
First, create a module.
go mod init mudassir.dev
Now, let's return Hello World!
from our endpoint.
// File: main.go
package main
import (
"net/http"
"log"
"fmt"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
Start the server using go run main.go
. Running curl -X GET localhost:8080
will return Hello World!
. That was quite simple to do.
Couple of things are going on here. We're importing the net/http
package. We're then registering an endpoint with a handler function. This handler function is an anonymous function. We then start the server on port 8080
.
However, we're returning a simple string. Could we have a JSON response instead?
Adding JSON
Let's update our import to include the ecoding/json
package.
import (
"net/http"
"log"
"fmt"
"encoding/json"
)
Let's create a struct to hold data.
var greeting struct {
Message string `json:"message"`
}
greeting.Message = "Hello World!"
All together, our code should look like this.
package main
import (
"fmt"
"log"
"net/http"
"encoding/json"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
var greeting struct {
Message string `json:"message"`
}
greeting.Message = "Hello World!"
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(greeting)
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
Start the server using go run main.go
. Now run curl -X GET localhost:8080
. We will get the following JSON response.
{
"message": "Hello World!"
}
Now let's refactor a little. We can extract the anonymous function. This also improves readability.
// File: main.go
package main
import (
"fmt"
"log"
"net/http"
"encoding/json"
)
func greetingHandler(w http.ResponseWriter, r *http.Request) {
var greeting struct {
Message: string `json:"message"`
}
greeting.Message = "Hello World!"
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(test)
}
func main() {
http.HandleFunc("/", greetingHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
What next?
Here's some things you can do next for fun.
- Use the
flag
package to listen on a different port - Use an environment variable to listen on a different port
- Unit tests
- Add a database
- Use the repository pattern
- Document your API using OpenAPI
- Use gRPC instead of REST
- Event driven architecture using Apache Kafka
- Containerise using Docker
- Add some logs (now read them using fluentd)