Date:

Share:

REST Servers in Go: Part 2 – using a router package

Related Articles

This is the second post in a series on writing REST servers on Go. The following is a list of posts in the series:

Where did we stop in Part 1

Part 1 ended with a version of our Go server, in which we improved the JSON rendering to an assistant function, making specific route handlers quite concise.

The problem we have left is the path routing logic, which is scattered across several places.

This is a problem encountered by all people who write HTTP servers independently. Unless the server is very minimal compared to the routes it has (for example, specialized servers have only one or two routes that they handle), the text and difficulty in organizing the router code is something that experienced programmers notice very quickly.

Advanced routing

The first instinct is to simplify routing away, perhaps with a set of functions or data type with methods. There are many interesting ad-hoc ways to do this, and many powerful and well-used third-party router packages in the Go ecosystem that do it for you. I highly recommend reviewing This post is by Ben Hoyt, Where it compares and contrasts with several approaches (ad-hoc and third-party) for a simple set of tracks.

Let’s take another look at our server’s REST API for a concrete example:

POST   /task/              :  create a task, returns ID
GET    /task/<taskid>      :  returns a single task by ID
GET    /task/              :  returns all tasks
DELETE /task/<taskid>      :  delete a task by ID
GET    /tag/<tagname>      :  returns list of tasks with this tag
GET    /due/<yy>/<mm>/<dd> :  returns list of tasks due by this date

There are a few things we can do to make routing more ergonomic:

  1. Add a way to set up individual therapists for different methods in the same track. for example message To /task/ Need to go to one therapist, Get / Task / Into another etc..
  2. Add a way to get “deeper” matches; For example we should be able to say that
    /task/ Going to one therapist, while / task / That numeric ID goes in after.
  3. While we’re at it, the borrower should simply extract the numeric identifier from it
    / task / And transfer it to the therapist in a convenient way.

Writing a custom router in Go is very simple, due to the possible nature of HTTP handlers. For this series of posts, I will resist the temptation. Instead, let’s see how this is handled by one of the most popular third party routers – Gorilla / Mox.

Task server with gorilla / mux

Gorilla / Mox Is one of the oldest of the popular Go HTTP routers; as per The documents, The name mux stands for “HTTP request multiplier” (the same meaning it has in the standard directory).

Since this is a package with a narrow and focused purpose, its use is quite simple. A version of our task server that uses Gorilla / Mox For routing is Available here. This is how the routes are defined:

router := mux.NewRouter()
router.StrictSlash(true)
server := NewTaskServer()

router.HandleFunc("/task/", server.createTaskHandler).Methods("POST")
router.HandleFunc("/task/", server.getAllTasksHandler).Methods("GET")
router.HandleFunc("/task/", server.deleteAllTasksHandler).Methods("DELETE")
router.HandleFunc("/task/id:[0-9]+/", server.getTaskHandler).Methods("GET")
router.HandleFunc("/task/id:[0-9]+/", server.deleteTaskHandler).Methods("DELETE")
router.HandleFunc("/tag/tag/", server.tagHandler).Methods("GET")
router.HandleFunc("/due/year:[0-9]+/month:[0-9]+/day:[0-9]+/", server.dueHandler).Methods("GET")

Notice how these definitions immediately relate to points (1) and (2) in the “Ergonomics Wish List” described above. By clicking on a Methods Calling the route, we can easily direct different methods in the same path to different therapists. Matching patterns (using standard expression syntax) in the path allows us to easily distinguish between them /task/ and / task / In setting the route at the top level.

To see how point (3) is treated, let’s look at getTaskHandler:

func (ts *taskServer) getTaskHandler(w http.ResponseWriter, req *http.Request) 
  log.Printf("handling get task at %sn", req.URL.Path)

  // Here and elsewhere, not checking error of Atoi because the router only
  // matches the [0-9]+ regex.
  id, _ := strconv.Atoi(mux.Vars(req)["id"])
  ts.Lock()
  task, err := ts.store.GetTask(id)
  ts.Unlock()

  if err != nil 
    http.Error(w, err.Error(), http.StatusNotFound)
    return
  

  renderJSON(w, task)

In the route definition, the route / task / id:[0-9]+ / Defines the regular expression for path analysis, but also assigns the identifying part to “id”. This “variable” can be accessed by calling mux.Vars On request.

Comparison of approaches

Here’s the path to reading code that you need to go through to figure out how Get
/ task /
The route is handled on our original server:

Whereas this is the path when used Gorilla / Mox:

Path handles HTTP path with gorilla / mux router

In addition to having fewer hoops to jump through, it is also much less code to read. IMHO it’s very good from a code reading standpoint. Path settings using Gorilla / Mox They are short and simple, and do not include much charm; It’s pretty clear how it works. Another advantage is that we can now easily see all the routes at a glance in one place. In fact, the route configuration code now looks very similar to the informal definition of our REST API.

I like to use packages like Gorilla / Mox, Because they are precision tools. They do one thing and they do it well, and they do not “stick” the whole program in a way that makes it difficult for them to remove or replace them later. If you test the
The server code for this part, Note that the parts are using Gorilla / Mox Limited to relatively few lines of code. If we find a fatal limitation in this package later in the project lifecycle, replacing it with another router (or a hand-rolled version) should be fairly straightforward.


Source

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Popular Articles