logo

Lucas Katayama/Basic API with Go

Created Thu, 26 Dec 2019 16:42:34 -0300 Modified Thu, 26 Dec 2019 16:42:34 -0300
659 Words

This post explain a little bit how to write a basic API with Go WITHOUT any framework.

Basic Structure

To write some Web API with Go, we use something like describe in Simple HTTP File Server post. But instead of using the File Handler, we are going to write the API handlers.

Like this:

package main

import (
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        // do something
    })
    http.ListenAndServe(":5000", nil)
}

Request/Response Handler

We are basically saying to handle (http.HandleFunc) request to path / with a function and it need to be with the signature:

func(w http.ResponseWriter, r *http.Request) {
    // do something
}

That is what all languages have when dealing with http request:

  • response (http.ResponseWriter)
  • request (http.Request)

Handling Requests

Methods

To handle the different types of methods (e.g. GET, POST), we use the variable r *http.Request and access the field Method. We can switch between all methods or handle only one

func(w http.ResponseWriter, r *http.Request) {
    if r.Method == http.MethodGet {
        // do something
    }
}

func(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
        case http.MethodGet:
        // do something
        case http.MethodPost:
        // do something
        default:
        // handle other
    }
}

URL Params

Query

To retrieve a Query param:

func(w http.ResponseWriter, r *http.Request) {
    fmt.Println(r.URL.Query().Get("param"))
}

Path

To retrierve a path param:

id := strings.TrimPrefix(r.URL.Path, "/path/")

The problem with that is to process subresources, Like /book/123/author. That end up with a more complex code which I am not to approach here. For that, use a web framework with a router.

Request Body

text/plain

To read request body

import "io/ioutil"

...

func(w http.ResponseWriter, r *http.Request) {
    bodyBytes, err :=  ioutil.ReadAll(r.Body)
    if err != nil {
        // handle read errors
    }
    fmt.Println(string(bodyBytes))
}

application/json

Let’s set a data structure and map JSON fields with golang struct tags and declare a value for it

type Request struct {
    Name string `json:"name"`
    Age  int    `json:"my_age"`
}

Now we read it and UnMarshal it

func(w http.ResponseWriter, r *http.Request) {
    bodyBytes, err :=  ioutil.ReadAll(r.Body)
    if err != nil {
        // handle read errors
    }
    var req Request
    err := json.Unmarshal(bodyBytes, &req)
    if err != nil {
        // handle unmarshal errors
    }
    fmt.Println(req)
}

Writing Responses

Status Code

func(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusBadRequest)
}

Content Type

func(w http.ResponseWriter, r *http.Request) {
    w.Header().Add("Content-Type", "application/json")
}

Response Body

text/plain

To write something to the response, we’re going to use the fmt package as follows:

func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "something to format: %s", "a value")
}

application/json

Let’s set a data structure and map JSON fields with golang struct tags and declare a value for it

type Response struct {
    Name         string `json:"name"`
    Age          int    `json:"my_age"`
    AnotherField string 
}

var response = Response{
    Name:         "John Doe",
    Age:          25,
    AnotherField: "some string",
}

Now we write to response writer.

We need to change the content type, transform the struct to a array of bytes and write the string to response:

func(w http.ResponseWriter, r *http.Request) {
    // changing content type
    w.Header().Add("Content-Type", "application/json")

    // transforming to a array of bytes
    bytes, err := json.Marshall(response)
    if err != nil {
        // handle marshalling errors
    }

    // writing
    fmt.Fprint(w, string(bytes))
}

Then we got the reponse

{"name":"John Doe","my_age":25,"AnotherField":"some string"}

To pretty print that JSON, just change json.Marshal to json.MarshalIndent:

bytes, _ := json.MarshalIndent(response, "", "  ")

The third parameter identify what to use as indentation, which is 2 spaces in this example.

{
  "name": "John Doe",
  "my_age": 25,
  "AnotherField": "some string"
}

Wrapping Up

That is not even close to what we can do with Golang to write a API. The problem here is to write and write, over and over again the same thing.

Here comes the web frameworks. They provide a bunch of code structure to easy write an API, like routing, body parsers, error handling, static file serving, etc.

They are all alike, so choose one that fits better for you. From now on, I am going to choose one: Echo.