DEV Community

Mario Carrion
Mario Carrion

Posted on • Originally published at mariocarrion.com on

Building Microservices in Go: REST APIs - HTTP Handlers

When building Microservices the de facto way to do it is to use both HTTP, as the protocol, and REST, as the way to represent resources. HTTP is literally the foundation of the World Wide Web and it is supported by multiple programmings in one way or another, so using HTTP is a no brainer because it could be used by any Frontend and Backend code with few to no external dependencies.

REST is an acronym for REpresentational State Transfer coined by Roy Fielding and introduced in his doctoral dissertation, it is an architectural style that defines guidelines when building web services, it's not a standard but it does use standards like HTTP (and its verbs to represent actions) and payload/message formats (like JSON or XML), to mention a few.

This is the first of a series of posts covering steps for building REST APIs in Go.

Disclaimer: This post includes Amazon affiliate links. If you click on one of them and you make a purchase I'll earn a commission. Please notice your final price is not affected at all by using those links.


Introduction to HTTP Handlers

Go includes all the necessary building blocks for implementing Webservices in its standard library, including things like HTTP Servers, different multiplexers and way to define HTTP handlers, as well as way to render content in different encodings like JSON or XML, something like the following works for creating an HTTP server:

package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Path: %s!", r.URL.Path[1:])
    })

    http.ListenAndServe(":8080", nil)
}
Enter fullscreen mode Exit fullscreen mode

Accessing it using curl:

 curl -X GET http://localhost:8080/resources
Path: resources!
Enter fullscreen mode Exit fullscreen mode

Using the standard library to define our HTTP Resources definitely works, however one of the biggest difficulties when using the standard library is the need to add extra code to build handlers that happen to have the same base path or require variables as pattern matching.

For example, assuming we need to define resources for /resources, /resources/{id} and /resources/{id}/values using the standard library will require us to define code to identify /resources/{id} and /resources/{id}/values after handling /resources.

This is better explained when requesting something like this:

 curl -X GET http://localhost:8080/resources/1234/values
Path: resources/1234/values!
Enter fullscreen mode Exit fullscreen mode

In this case our implemented handler needs to determine those arguments from the path itself.

For cases like this I recommend using a third party package called github.com/gorilla/mux.

Using github.com/gorilla/mux

github.com/gorilla/mux is a powerful URL router and dispatcher, it lets us implement handlers meant to be matched by different options, including paths, schemes or query values, to mention a few. To solve the problem we had before with the handler requiring us to define code to determine the other resources we could write something like:

package main

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

    "github.com/gorilla/mux"
)

func main() {
    router := mux.NewRouter()
    router.HandleFunc("/resources", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "/resources")
    })
    router.HandleFunc("/resources/{id:[0-9]+}", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "/resources/{id:[0-9]+}: %s!", mux.Vars(r)["id"])
    })
    router.HandleFunc("/resources/{id:[0-9]+}/values", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "/resources/{id:[0-9]+}/values: %s", mux.Vars(r)["id"])
    })

    srv := &http.Server{
        Handler:      router,
        Addr:         ":8080",
        WriteTimeout: 15 * time.Second,
        ReadTimeout:  15 * time.Second,
    }

    log.Fatal(srv.ListenAndServe())
}
Enter fullscreen mode Exit fullscreen mode

Which then we can request using curl like we did before but this time the three different resources and clearly handled independently:

 curl http://localhost:8080/resources
/resources

 curl http://localhost:8080/resources/123
/resources/{id:[0-9]+}: 123!

 curl http://localhost:8080/resources/456/values
/resources/{id:[0-9]+}/values: 456
Enter fullscreen mode Exit fullscreen mode

Implementing resources

The code used for this post is available on Github.

I previously mentioned Hexagonal Architecture during the Domain Driven Design and Project Layout post, using that as base we can define a new type TaskHandler that defines all the different nouns meant to represent our resources:

func (t *TaskHandler) Register(r *mux.Router) {
    r.HandleFunc("/tasks", t.create).Methods(http.MethodPost)
    r.HandleFunc(fmt.Sprintf("/tasks/{taskId:%s}", uuidRegEx), t.task).Methods(http.MethodGet)
    r.HandleFunc(fmt.Sprintf("/tasks/{taskId:%s}", uuidRegEx), t.update).Methods(http.MethodPut)
}
Enter fullscreen mode Exit fullscreen mode

The naming of those resources usually is defined as collections, in our case /tasks, and depending on the needs we could define subcollections to represent collections associated to an entity belonging to our parent collection, so an example of this would be a hypothetical resource called /tasks/{taskId}/categories.

In the context of our "To Do Microservice" domain we are defining three methods that map exactly to three different HTTP verbs and three different actions defined in our TaskService Application Service, those HTTP verbs usually map to CRUD operations:

  • POST -> Create
  • GET -> Read
  • PUT -> Update
  • DELETE -> Delete

Therefore our type defined above is mapped like:

  • POST /tasks -> (*TaskHandler).create used to create tasks,
  • GET /tasks/{id} -> (*TaskHandler).task used to retrieve tasks by id, and
  • PUT /tasks/{id} -> (*TaskHandler).update used to update tasks by id
  • We didn't implement DELETE /tasks/{id} this time, but in a future commit we will see it appear.

Implementing the required resources also requires us to define concrete types for handling requests and responses, the way I like to recommend implementing those is to define types with the following naming <MethodName><ResourceName><Request|Response>, for example the type representing the payload to create a Task via create would be:

type CreateTasksRequest struct {
    Description string `json:"description"`
}
Enter fullscreen mode Exit fullscreen mode

And the response type response from task would be:

type GetTasksResponse struct {
    Task Task `json:"task"`
}
Enter fullscreen mode Exit fullscreen mode

Although a bit repetitive defining concrete types for requests and responses allows us to define more rules to those values when defining our OpenAPI document, which will be discussed in future posts.

Parting words

With this post we kick off the series describing the steps for building REST APIs, building HTTP-based webservices is relatively simple when using the standard library, however things could become difficult when trying to deal with nested or parametrized resources, in those cases using a third party package like github.com/gorilla/mux makes sense.

Because we are using Domain Driven Design together with the Hexagonal Architecture we can explicitly indicate types representing the resources we are trying to model, mapping HTTP verbs to concrete methods and defining specific types for requests and responses lets us pave the
way for changes we will need to add documentation via OpenAPI 3.

Recommended Reading

If you're looking to sink your teeth into more REST and Web Programming I recommend the following books:

Top comments (1)

Collapse
 
xvbnm48 profile image
M Fariz Wisnu prananda

thansk for sharing!