If you're in to web development, you've probably heard someone talking about how Golang is the language for modern backend development. It was built by a team of people who really know their stuff, at a company that reeaally knows it's stuff.
Besides the nice and simple syntax, one of it's main advantages is the standard library that is fully equipped with modules that are production ready, and extremely well designed ( there's also a number of great third party modules, but more about some of those in another article) .
One of the finest modules from the Go standard library is definitely net/http
, which makes sense, because you really can't do web without the http protocol.
It has a production ready web server, and an extremely easy to use http client for sending requests. Let's see how easy it is to run a web server in Go:
package main
import (
"fmt"
"net/http"
)
const portNumber = ":8080"
func HomePageHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("We're live !!!"))
}
func main() {
http.HandleFunc("/", HomePageHandler)
fmt.Printf("Starting application on port %v\n", portNumber)
http.ListenAndServe(portNumber, nil)
}
Pretty self explanatory, right? I've even added a few more lines of code than necessary, just to make things more obvious. The HandleFunc
method attaches a route to a handler, and each handler function must accept an http request ( a pointer to it actually) , and a response writer which is in charge of sending the response back via the net to the client.
Another way of writing the handler would have been :
func HomePageHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "We're live!!!")
}
It really depends on style. Fprintf
takes any type that implements the io.Writer
interface ( the http.Responsewriter
is a marvelous example of this, but so is os.Stdout
) as a first argument, and a string as a second argument. The string is what we're displaying, and the io.Writer is where we're displaying it.
If we're going to call w.Write
, we know that as an implementation of the Write
function of the io.Writer
interface, we need to give it a slice of bytes, not a string. Luckily converting a string to a slice of bytes is a slice of cake.
Let's take a look at this *http.Request thingamajig:
package main
import (
"fmt"
"net/http"
)
const portNumber = ":8080"
func HomePageHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello %v, you are at %v. You have sent some query params too: %v.The method you used is %v", r.UserAgent(), r.URL.Path, r.URL.Query(), r.Method)
}
func main() {
http.HandleFunc("/hello", HomePageHandler)
fmt.Printf("Starting application on port %v\n", portNumber)
http.ListenAndServe(portNumber, nil)
}
I've updated the code so that it is clear that the request struct has everything we need for handling an http request, just like we would with any cool web framework like Flask or Spring Boot. Except, this is not a framework, it's the standard library http module. Ok, I will admit that using a framework like Gin Gonic
or Gorilla
will speed you up, and keep you from writing a lot of boilerplate code, but for a stadard library http module, this is pretty practical.
Now, what about sending http requests to a remote server. Has anyone written a cool http client for Go, something in the line of the massively successful requests library in Python?
Actually, the original Go creators decided not to give anyone a chance to do that, because the net/http module contains such a simple http client , that there really wasn't any need for a third party library.
Here's a call to github's API:
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
)
func main() {
req, err := http.Get("https://api.github.com/users")
if err != nil {
log.Fatal(err)
}
defer req.Body.Close()
data, err := ioutil.ReadAll(req.Body)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))
}
Probably the most complicated code here is all of this error checking, but when considering try/catch blocks in other languages, it's not even that bad.
I'm calling defer req.Body.Close()
here because it's good practice to close a request body, same as you would close a file once you've finished reading from it.
Since the request body is an implementation of the io.ReadCloser
interface (what's with all these io module interfaces, right? ) , we can't just print that , we need to call ioutil.Readall
which will take the request body and return a byte slice. As we know, turning a byte slice into a string (and vice versa) is not difficult.
Finally, since a large part of backend development today is concerned with REST APIs, I guess it wouldn't be bad to implement a basic GET endpoint that returns some JSON so that the output we get at least remotely resembles something you would see in real life:
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
const portNumber = ":8080"
type User struct {
Name string `json:"username"`
Age int `json:"age"`
Gender string `json:"gender"`
}
func UsersHandler(w http.ResponseWriter, r *http.Request) {
users := []User{
{
Name: "Chris",
Age: 22,
Gender: "Male",
},
{
Name: "Annie",
Age: 23,
Gender: "Female",
},
{
Name: "Jane",
Age: 25,
Gender: "Female",
},
}
usersJson, err := json.Marshal(users)
if err != nil {
log.Fatal(err)
}
w.Write(usersJson)
}
func main() {
http.HandleFunc("/users", UsersHandler)
fmt.Printf("Starting application on port %v\n", portNumber)
http.ListenAndServe(portNumber, nil)
}
Alright mister, you've got a lot of explaining to do! Wtf are these weird back ticks next to your struct fields? and why are all the fields in the struct capitalized?
Good questions!
The weird back ticks are an awesome feature of Golang. In this case we are using them to describe what we want the keys of our fields to be called when we serialize them to JSON. So Name becomes username. Now, we could have left it as a POGS ( Plain Old Go Struct ? ) , but then the JSON field would be name with a capitalized N, which really isn't the convention when displaying data in JSON format. But, then I guess we could have lowercased the Name field , and that would solve it right? ( forget for a moment that I actually changed name to username, it would have worked however you named it)
No, it wouldn't . If you're coming from Java, you know all about encapsulation. private String name
you would proudly type, and the whole world would know that name is not to be touched unless it has a setter. Well, Go doesn't like verbosity. In fact, Go developers had the audacity to call Java a stuttery language.
Person person = new Person()
.. well, it does sound a bit like stuttering.
Go does respect encapsulation though. Kind of, not as explicitly as Java, but it definitely doesn't rely on the whole we're all grownups rambling of Python developers. Because Go developers know that grownups are quite irresponsible, so it's better not to rely on them.
How does Go take care of encapsulation? Easy, if it's capitalized it's not encapsulated ( I almost spelled encapitalized) . So Name as a field is available to the public, while name would not be. Besides exporting or restricting the use of functions and struct fields to different packages, it is extremely useful for filtering what fields to send to the client, and which not to. Besides for JSON, the back ticks are used for many other purposes, such as in ORMs like gorm
, but that is definitely going to be an article of it's own.
Hope you enjoyed this article, thanks for reading!