Building a basic HTTP server with Go

Kinanee Samson - Jun 26 - - Dev Community

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.

mkdir go_server && go mod init
Enter fullscreen mode Exit fullscreen mode

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.

cd go_server
Enter fullscreen mode Exit fullscreen mode

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,

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);
}

Enter fullscreen mode Exit fullscreen mode

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;

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
}
Enter fullscreen mode Exit fullscreen mode

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;

// 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
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode

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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .