Aaron’s Desk Chair Adventures

Code. Engineering Management. Fun.

Go API Versioning with Gorilla Mux

Code-Based Versioning

Recently I had a problem where I needed to implement API versioning in code. The versions would share the vast majority of routes/handlers and I found that I wanted to re-use the base routes and modifying them slightly between versions. I imagined using Gorilla mux router as the base with all the routes and then creating a new router for v1, v2, etc that would ‘inherit’ the base routes and only modify a few. That would maximize code-reuse as I’d only have to modify the latest route in one place for many versions to receive the benefit.

I searched around Google, Stack Overflow, and the Gorilla mux documentation about attaching a subrouter to a route and similar terms, but I struggled to find a working solution. I found a few useful posts and here’s the result:

package main

import (
        "fmt"
        "net/http"

        "github.com/gorilla/mux"
)

// AnotherHandlerLatest is the newest version of AnotherHandler
func AnotherHandlerLatest(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "hello from AnotherHandlerLatest.")
}

// ExampleHandlerLatest is the newest version of ExampleHandler
func ExampleHandlerLatest(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "hello from ExampleHandlerLatest.")
}

// ExampleHandlerV1 is a v1-compatible version of ExampleHandler
func ExampleHandlerV1(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello from ExampleHandlerv1.")
}

// AddV1Routes takes a router or subrouter and adds all the v1
// routes to it
func AddV1Routes(r *mux.Router) {
        r.HandleFunc("/example", ExampleHandlerV1)
        AddRoutes(r)
}

// AddV2Routes takes a router or subrouter and adds all the v2
// routes to it, note that these should probably match
// AddRoutes(r *muxRouter) alternatively you can do
// var AddV2Routes = AddRoutes
func AddV2Routes(r *mux.Router) {
        AddRoutes(r)
}

// AddRoutes takes a router or subrouter and adds all the latest
// routes to it
func AddRoutes(r *mux.Router) {
        r.HandleFunc("/example", ExampleHandlerLatest)
        r.HandleFunc("/example2", AnotherHandlerLatest)
}

func main() {
        router := mux.NewRouter()

        // latest
        AddRoutes(router)

        // v1
        AddV1Routes(router.PathPrefix("/v1").Subrouter())

        // v2
        AddV2Routes(router.PathPrefix("/v2").Subrouter())
}

The end result of this is that /example and /v2/example point at the same handler, but /v1/example points at a different handler. When a new version is created, I rename HandlerLatest to HandlerVn where n is the previous newest version. I add it to the AddVnRoutes, create an AddVn+1Routes and new version is made! I can easily deprecate older versions (and their associated handlers) as needed.