Skip to content

API Versioning ​

The versioning subpackage provides semver versioning for your APIs.

All best practice conventions are implemented, including the suggestions written in the api-guidelines document.

  • The version is retrieved from the "Accept" and/or "Accept-Version" request header(s).
  • If a version matched, the server responds with a custom "X-Api-Version" header with the normalized version inside.
  • If a version was not found (missing version header or unimplemented version) the server responds with status code of 501 and plain text of "version not found" by default.
  • If a specific version of the resource is deprecated by the server, the client receives the custom "X-Api-Warn", "X-Api-Deprecation-Date" and "X-Api-Deprecation-Info" headers, the request is handled as expected.

Introduction ​

Internally, the version validation is done by the regex-free and fast github.com/blang/semver/v4 third-party package.

Valid version ranges are:

  • "<1.0.0"
  • "<=1.0.0"
  • ">1.0.0"
  • ">=1.0.0"
  • "1.0.0", "=1.0.0", "==1.0.0"
  • "!1.0.0", "!=1.0.0"

A Range can consist of multiple ranges separated by space: Ranges can be linked by logical AND:

  • ">1.0.0 <2.0.0" would match between both ranges, so "1.1.1" and "1.8.7" but not "1.0.0" or "2.0.0"
  • ">1.0.0 <3.0.0 !2.0.3-beta.2" would match every version between 1.0.0 and 3.0.0 except 2.0.3-beta.2

Ranges can also be linked by logical OR:

  • "<2.0.0 || >=3.0.0" would match "1.x.x" and "3.x.x" but not "2.x.x"

AND has a higher precedence than OR. It's not possible to use brackets.

Ranges can be combined by both AND and OR

  • >1.0.0 <2.0.0 || >3.0.0 !4.2.1 would match 1.2.3, 1.9.9, 3.1.1, but not 4.2.1, 2.1.1.

Getting Started ​

Import the versioning package.

go
import (
    // [...]

    "github.com/kataras/iris/v12"
    "github.com/kataras/iris/v12/versioning"
)

Using the versioning.NewGroup(version string) *versioning.Group function you can create a group of routes to register the application's routes based on version requested by clients. The versioning.Group completes the iris.Party interface.

Example Code:

go
package main

import (
    "github.com/kataras/iris/v12"
    "github.com/kataras/iris/v12/versioning"
)

func main() {
    app := iris.New()

    app.OnErrorCode(iris.StatusNotFound, func(ctx iris.Context) {
        ctx.WriteString(`Root not found handler.
        This will be applied everywhere except the /api/* requests.`)
    })

    api := app.Party("/api")
    // Optional, set version aliases (literal strings).
    // We use `UseRouter` instead of `Use`
    // to handle HTTP errors per version, but it's up to you.
    api.UseRouter(versioning.Aliases(versioning.AliasMap{
        // If no version provided by the client, default it to the "1.0.0".
        versioning.Empty: "1.0.0",
        // If a "latest" version is provided by the client,
        // set the version to be compared to "3.0.0".
        "latest": "3.0.0",
    }))

    // |----------------|
    // | The fun begins |
    // |----------------|

    // Create a new Group, which is a compatible Party,
    // based on version constraints.
    v1 := versioning.NewGroup(api, ">=1.0.0 <2.0.0")

    // Optionally, set custom view engine and path
    // for templates based on the version.
    v1.RegisterView(iris.HTML("./v1", ".html"))

    // Optionally, set custom error handler(s) based on the version.
    // Keep in mind that if you do this, you will
    // have to register error handlers
    // for the rest of the parties as well.
    v1.OnErrorCode(iris.StatusNotFound, testError("v1"))

    // Register resources based on the version.
    v1.Get("/", testHandler("v1"))
    v1.Get("/render", testView)

    // Do the same for version 2 and version 3,
    // for the sake of the example.
    v2 := versioning.NewGroup(api, ">=2.0.0 <3.0.0")
    v2.RegisterView(iris.HTML("./v2", ".html"))
    v2.OnErrorCode(iris.StatusNotFound, testError("v2"))
    v2.Get("/", testHandler("v2"))
    v2.Get("/render", testView)

    v3 := versioning.NewGroup(api, ">=3.0.0 <4.0.0")
    v3.RegisterView(iris.HTML("./v3", ".html"))
    v3.OnErrorCode(iris.StatusNotFound, testError("v3"))
    v3.Get("/", testHandler("v3"))
    v3.Get("/render", testView)

    app.Listen(":8080")
}

func testHandler(v string) iris.Handler {
    return func(ctx iris.Context) {
        ctx.JSON(iris.Map{
            "version": v,
            "message": "Hello, world!",
        })
    }
}

func testError(v string) iris.Handler {
    return func(ctx iris.Context) {
        ctx.Writef("not found: %s", v)
    }
}

func testView(ctx iris.Context) {
    ctx.View("index.html")
}

Version Aliases ​

Optional, set version aliases (literal strings).

go
api.UseRouter(versioning.Aliases(versioning.AliasMap{
    // If no version provided by the client, default it to the "1.0.0".
    versioning.Empty: "1.0.0",
    // If a "latest" version is provided by the client,
    // set the version to be compared to "3.0.0".
    "latest": "3.0.0",
}))

We use UseRouter instead of Use to handle HTTP errors per version, but it's up to you.

Get Current Version ​

The version is extracted through the versioning.GetVersion function. You can use it anywhere, even if you don't use the versioning feature. By default GetVersion will try to read from:

  • Accept header, e.g. Accept: "application/json; version=1.0.0"
  • Accept-Version header, e.g. Accept-Version: "1.0.0"

You can customize it by setting a version based on the request context:

go
api.Use(func(ctx *context.Context) {
    if version := ctx.URLParam("version"); version != "" {
        versioning.SetVersion(ctx, version)
    }

    ctx.Next()
})

Or by using the FromQuery(version, default) helper:

go
api.Use(versioning.FromQuery("version", "1.0.0"))

Deprecation ​

To mark an API version as deprecated use the Deprecated method.

go
v1.Deprecated(versioning.DefaultDeprecationOptions)

This will make the route handlers to send some extra headers to the client.

  • "X-API-Warn": options.WarnMessage
  • "X-API-Deprecation-Date": context.FormatTime(ctx, options.DeprecationDate))
  • "X-API-Deprecation-Info": options.DeprecationInfo

Built with excellence by Hellenic Development, delivering enterprise-grade solutions.