Building a basic HTTP server with Go
Tags: go, api, learning, backend
Published: 2024-06-26 21:10:47

Go or Go lang as it’s commonly referred to is a wannabe system’s programming language originally designed and developed at Google, it’s aim was to be the ultimate replacement for C/C++. Go incorporates modern programming language features like type inference and generics while presenting itself in an effortless and minimalist style, Go has borrowed concepts from Typescript, Javascript, and Python.

Go has been used to buy some awesome projects like the open-source Pocketbase server amongst others and in today’s post we will expo how to set a basic REST server with Go and as such we will cover the following talking points;

- Project Setup
- Create a basic Server with net/http
- Parsing request headers
- Parsing request query params
- Parsing request body

## Project Setup

The first thing to do is to ensure you have the Go compiler installed on your machine, if you don’t have it installed then you can download and install the Go executable and ensure that the GoPath is correctly configured. Now let’s set up a Go project, first navigate into your projects directory to create a Go project.
 
```bash
mkdir go_server && go mod init
```
This will create a go package for us, now we need to navigate into the newly created folder and create a new go file, `index.go`. This file will house the content of our server.

```bash
cd go_server
```

## Create a basic Server with net/http

Now we have our project all set up let's add the following code to our `index.go` file,

```go
package main

import (
  "fmt"
  "log"
  "net/http"
)

func sayHello(w http.ResponseWriter, req *htt.Reques) {
  fmt.FPrintf(w, "Hello World")
}

main () {
  http.handleFunc("/", sayHello);
  log.Println("Server listening on port", 8080);
  http.ListenAndServer(":8080", nil);
}

```

The code snippet above demonstrates how to set up a basic HTTP server with Go, we need to declare a package for the current Go module, and then we import a bunch of packages from the standard Go library, the `fmt` package is responsible for formatted printing and input/output operations. The `log` package in the Go standard library provides basic logging functionality. The net/http package in the Go standard library provides a powerful and comprehensive set of tools for building both HTTP servers and clients in Go. 

The `sayHello` function is a route handler function, it accepts two arguments, the first is the response object, while the second is the request object. We use the `FPrintf` function to write a message to the response body, this also effectively ends the request. Inside our main function, we register the `sayHello` function as a handler for the `/` route. Then we use the `Println` function from the `log` package to notify us when our server starts up. Then we use the `ListenAndServe` method on the `http` package to create a server on port `8080`.

The `ListenAndServe` method accepts two arguments, The first is a string that specifies the network address and port on which the server will listen for incoming HTTP requests. It's typically formatted as "hostname:port" or just ":port". The second argument is an optional interface that can be used to customize the overall request-handling behavior of the server. In most cases, you'll leave this argument as nil and rely on specific handler functions registered using http.HandleFunc or other routing mechanisms.

## Parsing request headers

There are times when you are interested in parsing the request and that is standard when building an API so in the next snippet we'll see how we can do just that;

```go
package main

// cont'd

var headers map[string]string

func sayHello(w http.ResponseWriter, req *http.Request) {
	
	for key, values := range req.Header {
		headers[key] = values[0]
	}
        log.Println(headers) // do h
	fmt.Fprint(w, "Hello, World!")
}

// cont'd

main () {
  headers = make(map[string]string)
  // cont'd
}
```

First, we create a new map object `header` to store the contents of the request headers, inside our `main` function we initialize the `headers` to be an empty map object. Inside the `sayHello` function we loop through the request headers and for each key on the request header we create a similar key on the `headers` map object and its value is the value of the key on the request header then we print the `headers` object to the console. 

## Parsing Request Query Parameters

To parse the request query parameters we just need to add a few extra lines of code to our handler function;

```go
// cont'd

func sayHello(w http.ResponseWriter, req *http.Request) {

	// cont'd

	// Get all query parameters as a map
	queryParams := req.URL.Query()

	// Access specific parameters
	name := queryParams.Get("name")

	log.Println(name)

	// cont'd
}

// cont'd
```

We use the `req.URL.Query()` method of the `http.Request` object to access the query string parameters as a `url.Values` map. Then `queryParams.Get("name")` retrieves the value for the "name" parameter as a string or an empty string if not present.

## Parsing request body

```go
package main

import (
  "encoding/json"
  "io"
  // cont'd
)

type Payload struct {
	Id string
}

// cont'd

func sayHello (w http.ResponeWriter, req http.Request) {
        // cont'd
      body, err := io.ReadAll(req.Body)

	if err != nil {
		log.Println("Error reading request body:", err)
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprintf(w, "Error reading request body")
		return
	}

	var payload Payload

	err = json.Unmarshal(body, &payload)

	if err != nil {
		log.Println("Error parsing JSON body:", err)
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprintf(w, "Invalid JSON format in request body")
		return
	}

	log.Println("payload", payload.Id)

       // cont'd

}

// cont'd
```

We have imported two new packages `encoding/json` and `io`. Next, we create a struct to serve as a type for the request body. Inside our handler function we call `io.ReadAll` and pass the `req.Body` as an argument to the `ReadAll` method we just called. 

This method returns two values we can destructre out, the request body and an error object. If there's an error we notify the user and end the request, otherwise we create a payload variable of type `Payload` and call `json.UnMarshall` and pass the `body` as the first argument to it and a pointer to the `payload` variable we just created. If there’s an error while trying to convert the body of the request we notify the user that their JSON is invalid then we end the request as a bad request, otherwise, we just log out the `Id` of the payload and continue with the rest of the code.