Semver versioning for your APIs. It implements all the suggestions written at api-guidelines and more.
The version comparison is done by the go-version package. It supports matching over patterns like ">= 1.0, < 3"
and e.t.c.
The only requirement is the Go Programming Language.
$ go get github.com/kataras/versioning
- Per route version matching, an
http.Handler
with "switch" cases via versioning.Map for version => handler - Per group versioned routes and deprecation API
- Version matching like ">= 1.0, < 2.0" or just "2.0.1" and e.t.c.
- Version not found handler (can be customized by simply adding the
versioning.NotFound
: customNotMatchVersionHandler on the Map) - Version is retrieved from the "Accept" and "Accept-Version" headers (can be customized through request's context key)
- Respond with "X-API-Version" header, if version found.
- Deprecation options with customizable "X-API-Warn", "X-API-Deprecation-Date", "X-API-Deprecation-Info" headers via
Deprecated
wrapper.
// If reports whether the "version" is a valid match to the "is".
// The "is" can be a version constraint like ">= 1, < 3".
If(version string, is string) bool
// Match reports whether the current version matches the "expectedVersion".
Match(r *http.Request, expectedVersion string) bool
Example
router.HandleFunc("/api/user", func(w http.ResponseWriter, r *http.Request) {
if versioning.Match(r, ">= 2.2.3") {
// [logic for >= 2.2.3 version of your handler goes here]
return
}
})
Current request version is retrieved by versioning.GetVersion(r *http.Request)
.
By default the GetVersion
will try to read from:
Accept
header, i.eAccept: "application/json; version=1.0"
Accept-Version
header, i.eAccept-Version: "1.0"
func handler(w http.ResponseWriter, r *http.Request){
currentVersion := versioning.GetVersion(r)
}
You can also set a custom version to a handler trough a middleware by setting a request context's value. For example:
import (
"context"
"net/http"
"github.com/kataras/versioning"
)
func urlParamVersion(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
version := r.URL.Query().Get("v") // ?v=2.3.5
if version == "" {
// set a default version, e.g. 1.0
version = "1.0"
}
r = r.WithContext(versioning.WithVersion(r.Context(), version))
next.ServeHTTP(w, r)
})
}
The versioning.NewMatcher(versioning.Map) http.Handler
creates a single handler which decides what handler need to be executed based on the requested version.
// middleware for all versions.
func myMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
// [...]
next.ServeHTTP(w, r)
})
}
func myCustomVersionNotFound(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(404)
fmt.Fprintf(w, "%s version not found", versioning.GetVersion(r))
}
router := http.NewServeMux()
router.Handle("/", myMiddleware(versioning.NewMatcher(versioning.Map{
// v1Handler is a handler of yuors that will be executed only on version 1.
"1.0": v1Handler,
">= 2, < 3": v2Handler,
versioning.NotFound: http.HandlerFunc(myCustomNotVersionFound),
})))
Using the versioning.Deprecated(handler http.Handler, options versioning.DeprecationOptions) http.Handler
function you can mark a specific handler version as deprecated.
v1Handler = versioning.Deprecated(v1Handler, versioning.DeprecationOptions{
// if empty defaults to: "WARNING! You are using a deprecated version of this API."
WarnMessage string
DeprecationDate time.Time
DeprecationInfo string
})
router.Handle("/", versioning.NewMatcher(versioning.Map{
"1.0": v1Handler,
// [...]
}))
This will make the handler to send these headers to the client:
"X-API-Warn": options.WarnMessage
"X-API-Deprecation-Date": options.DeprecationDate
"X-API-Deprecation-Info": options.DeprecationInfo
versioning.DefaultDeprecationOptions can be passed instead if you don't care about Date and Info.
Grouping routes by version is possible as well.
Using the versioning.NewGroup(version string) *versioning.Group
function you can create a group to register your versioned routes.
The versioning.RegisterGroups(r *http.ServeMux, versionNotFoundHandler http.Handler, groups ...*versioning.Group)
must be called in the end in order to register the routes to a specific StdMux
.
router := http.NewServeMux()
// version 1.
usersAPIV1 := versioning.NewGroup(">= 1, < 2")
usersAPIV1.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}
w.Write([]byte("v1 resource: /api/users handler"))
})
usersAPIV1.HandleFunc("/api/users/new", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}
w.Write([]byte("v1 resource: /api/users/new post handler"))
})
// version 2.
usersAPIV2 := versioning.NewGroup(">= 2, < 3")
usersAPIV2.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}
w.Write([]byte("v2 resource: /api/users handler"))
})
usersAPIV2.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}
w.Write([]byte("v2 resource: /api/users post handler"))
})
versioning.RegisterGroups(router, versioning.NotFoundHandler, usersAPIV1, usersAPIV2)
A middleware can be registered, using the methods we learnt above, i.e by using the
versioning.Match
in order to detect what code/handler you want to be executed when "x" or no version is requested.
Just call the Group#Deprecated(versioning.DeprecationOptions)
on the group you want to notify your API consumers that this specific version is deprecated.
userAPIV1 := versioning.NewGroup("1.0").Deprecated(versioning.DefaultDeprecationOptions)
For a more detailed technical documentation you can head over to our godocs. And for executable code you can always visit the _examples repository's subdirectory.
kataras/versioning is free and open-source software licensed under the MIT License.