Skip to main content

Overview

The version middleware provides API versioning support through headers, query parameters, or URL path prefixes. It helps manage multiple API versions and handles deprecation warnings.

Installation

import "github.com/go-mizu/mizu/middlewares/version"

Quick Start

app := mizu.New()

app.Use(version.New(version.Options{
    DefaultVersion: "v1",
}))

app.Get("/users", func(c *mizu.Ctx) error {
    v := version.Get(c)
    // Handle based on version
    return c.JSON(200, users)
})

Configuration

OptionTypeDefaultDescription
DefaultVersionstring-Default version when not specified
Headerstring"Accept-Version"Header name for version
QueryParamstring"version"Query parameter name
PathPrefixboolfalseExtract version from URL path
Supported[]string-List of supported versions
Deprecated[]string-List of deprecated versions
ErrorHandlerfunc(*mizu.Ctx, string) error-Custom error handler

Examples

Version from Header

app.Use(version.New(version.Options{
    DefaultVersion: "v1",
    Header:         "Accept-Version",
}))

app.Get("/api/users", func(c *mizu.Ctx) error {
    switch version.Get(c) {
    case "v2":
        return c.JSON(200, usersV2)
    default:
        return c.JSON(200, usersV1)
    }
})
Client request:
GET /api/users HTTP/1.1
Accept-Version: v2

Version from Query Parameter

app.Use(version.New(version.Options{
    DefaultVersion: "v1",
    QueryParam:     "api_version",
}))
Request: GET /users?api_version=v2

Version from URL Path

app.Use(version.New(version.Options{
    PathPrefix: true,
}))

// GET /v1/users → version "v1"
// GET /v2/users → version "v2"

Supported Versions Only

app.Use(version.New(version.Options{
    DefaultVersion: "v2",
    Supported:      []string{"v1", "v2", "v3"},
    ErrorHandler: func(c *mizu.Ctx, v string) error {
        return c.JSON(400, map[string]string{
            "error": "Unsupported API version: " + v,
            "supported": "v1, v2, v3",
        })
    },
}))

Deprecation Warnings

app.Use(version.New(version.Options{
    DefaultVersion: "v3",
    Supported:      []string{"v1", "v2", "v3"},
    Deprecated:     []string{"v1"},
}))

// Requests with v1 will include:
// Deprecation: true
// Sunset: See documentation for migration guide

Using Helper Functions

// Version from header only
app.Use(version.FromHeader("X-API-Version"))

// Version from path only
app.Use(version.FromPath())

// Version from query only
app.Use(version.FromQuery("v"))

Version-Specific Routes

app.Use(version.New(version.Options{
    PathPrefix: true,
}))

app.Get("/v1/users", usersV1Handler)
app.Get("/v2/users", usersV2Handler)
app.Get("/v3/users", usersV3Handler)

Content Negotiation

app.Get("/users", func(c *mizu.Ctx) error {
    v := version.Get(c)

    switch v {
    case "v3":
        return c.JSON(200, map[string]any{
            "data":  users,
            "meta":  metadata,
            "links": links,
        })
    case "v2":
        return c.JSON(200, map[string]any{
            "users": users,
            "total": len(users),
        })
    default:
        return c.JSON(200, users)
    }
})

Feature Flags by Version

app.Get("/users", func(c *mizu.Ctx) error {
    v := version.Get(c)

    users := getUsers()

    // New field only in v2+
    if v >= "v2" {
        for i := range users {
            users[i].Avatar = getAvatar(users[i].ID)
        }
    }

    return c.JSON(200, users)
})

API Reference

func New(opts Options) mizu.Middleware
func FromHeader(header string) mizu.Middleware
func FromPath() mizu.Middleware
func FromQuery(param string) mizu.Middleware
func GetVersion(c *mizu.Ctx) string
func Get(c *mizu.Ctx) string  // Alias for GetVersion

Version Detection Order

When multiple sources are configured:
  1. Header (e.g., Accept-Version: v2)
  2. Query parameter (e.g., ?version=v2)
  3. Path prefix (e.g., /v2/users)
  4. Default version

Response Headers

For deprecated versions:
Deprecation: true
Sunset: See documentation for migration guide

Technical Details

Architecture

The version middleware uses a context-based approach to store and retrieve version information:
  1. Version Detection: The middleware examines multiple sources in a specific priority order
  2. Context Storage: Detected version is stored in the request context using a private context key
  3. Validation: Optional validation against supported/deprecated version lists
  4. Header Injection: Automatic deprecation headers for deprecated versions

Implementation Details

Version Detection Priority:
  1. HTTP Header (configurable, default: Accept-Version)
  2. Query Parameter (configurable, default: version)
  3. Path Prefix (optional, pattern: /v{number}/...)
  4. Default Version (fallback)
Version String Validation:
  • Pattern: v or V followed by digits and optional dots
  • Examples: v1, v2, V1, v1.0, v1.2.3
  • Invalid: api, empty string, v, va
Context Management:
  • Uses a private contextKey{} struct to prevent collisions
  • Version stored as string in request context
  • Retrieved via GetVersion(c) or Get(c) helper functions
Performance Optimizations:
  • Supported and deprecated versions stored in maps for O(1) lookup
  • Single pass through version sources with early exit
  • No regex matching for version strings (character-by-character validation)

Response Headers

For deprecated versions, the middleware automatically adds:
  • Deprecation: true - Indicates the version is deprecated
  • Sunset: See documentation for migration guide - Migration information

Best Practices

  • Always set a default version
  • Document supported versions
  • Add deprecation warnings before removal
  • Use semantic versioning (v1, v2, v3)
  • Maintain backward compatibility within major versions
  • Provide migration guides for deprecated versions

Testing

The middleware includes comprehensive test coverage for all features:
Test CaseDescriptionExpected Behavior
TestNew/from headerVersion from Accept-Version headerExtracts version “v2” from header
TestNew/from queryVersion from query parameterExtracts version “v3” from ?version=v3
TestNew/default versionNo version specifiedUses default version “v1”
TestNew_PathPrefix/v1Version from path prefix /v1Extracts version “v1” from URL path
TestNew_PathPrefix/v2Version from path prefix /v2Extracts version “v2” from URL path
TestNew_Supported/supported versionRequest with supported versionReturns 200 OK
TestNew_Supported/unsupported versionRequest with unsupported versionReturns 400 Bad Request
TestNew_DeprecatedRequest with deprecated versionSets Deprecation header to “true”
TestFromHeaderCustom header name (X-API-Version)Extracts version “2.0” from custom header
TestFromPathPath with semantic version (v1.2)Extracts version “v1.2” from path
TestFromQueryCustom query param (api_version)Extracts version “3.0” from custom param
TestGetGetVersion vs Get functionBoth functions return same value
TestIsVersionString/v1Valid version string “v1”Returns true
TestIsVersionString/v2Valid version string “v2”Returns true
TestIsVersionString/V1Valid uppercase “V1”Returns true
TestIsVersionString/v1.0Valid with dot “v1.0”Returns true
TestIsVersionString/v1.2.3Valid semantic “v1.2.3”Returns true
TestIsVersionString/apiInvalid string “api”Returns false
TestIsVersionString/emptyEmpty stringReturns false
TestIsVersionString/vJust “v” characterReturns false
TestIsVersionString/vaInvalid “va”Returns false