{"id":16828079,"url":"https://github.com/kataras/versioning","last_synced_at":"2025-07-03T11:03:27.561Z","repository":{"id":39876232,"uuid":"226252742","full_name":"kataras/versioning","owner":"kataras","description":":new: API Versioning for Go","archived":false,"fork":false,"pushed_at":"2024-05-24T18:01:19.000Z","size":35,"stargazers_count":50,"open_issues_count":2,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-02T03:23:15.563Z","etag":null,"topics":["api-versioning","go","golang","versioning","web-development"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kataras.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"kataras"}},"created_at":"2019-12-06T05:30:13.000Z","updated_at":"2025-02-08T16:12:47.000Z","dependencies_parsed_at":"2024-10-27T12:00:51.695Z","dependency_job_id":"59944a0a-893b-4e17-bf64-bff29e24f394","html_url":"https://github.com/kataras/versioning","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/kataras/versioning","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kataras%2Fversioning","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kataras%2Fversioning/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kataras%2Fversioning/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kataras%2Fversioning/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kataras","download_url":"https://codeload.github.com/kataras/versioning/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kataras%2Fversioning/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263314105,"owners_count":23447291,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api-versioning","go","golang","versioning","web-development"],"created_at":"2024-10-13T11:24:34.787Z","updated_at":"2025-07-03T11:03:27.536Z","avatar_url":"https://github.com/kataras.png","language":"Go","readme":"# API Versioning (Go)\r\n\r\n[![build status](https://img.shields.io/github/actions/workflow/status/kataras/versioning/ci.yml?style=for-the-badge)](https://github.com/kataras/versioning/actions) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=for-the-badge)](https://goreportcard.com/report/github.com/kataras/versioning) [![godocs](https://img.shields.io/badge/go-%20docs-488AC7.svg?style=for-the-badge)](https://godoc.org/github.com/kataras/versioning) [![donate on PayPal](https://img.shields.io/badge/support-PayPal-blue.svg?style=for-the-badge)](https://www.paypal.me/kataras)\r\n\r\n[Semver](https://semver.org/) versioning for your APIs. It implements all the suggestions written at [api-guidelines](https://github.com/byrondover/api-guidelines/blob/master/Guidelines.md#versioning) and more.\r\n\r\nThe version comparison is done by the [go-version](https://github.com/hashicorp/go-version) package. It supports matching over patterns like `\"\u003e= 1.0, \u003c 3\"` and e.t.c.\r\n\r\n## Getting started\r\n\r\nThe only requirement is the [Go Programming Language](https://golang.org/dl).\r\n\r\n```sh\r\n$ go get github.com/kataras/versioning\r\n```\r\n\r\n## Features\r\n\r\n- Per route version matching, an `http.Handler` with \"switch\" cases via [versioning.Map](https://github.com/kataras/versioning/blob/master/versioning.go#L33) for version =\u003e handler\r\n- Per group versioned routes and deprecation API\r\n- Version matching like \"\u003e= 1.0, \u003c 2.0\" or just \"2.0.1\" and e.t.c.\r\n- Version not found handler (can be customized by simply adding the `versioning.NotFound`: customNotMatchVersionHandler on the Map)\r\n- Version is retrieved from the \"Accept\" and \"Accept-Version\" headers (can be customized through request's context key)\r\n- Respond with \"X-API-Version\" header, if version found.\r\n- Deprecation options with customizable \"X-API-Warn\", \"X-API-Deprecation-Date\", \"X-API-Deprecation-Info\" headers via `Deprecated` wrapper.\r\n\r\n## Compare Versions\r\n\r\n```go\r\n// If reports whether the \"version\" is a valid match to the \"is\".\r\n// The \"is\" can be a version constraint like \"\u003e= 1, \u003c 3\".\r\nIf(version string, is string) bool\r\n```\r\n\r\n```go\r\n// Match reports whether the current version matches the \"expectedVersion\".\r\nMatch(r *http.Request, expectedVersion string) bool\r\n```\r\n\r\nExample\r\n\r\n```go\r\nrouter.HandleFunc(\"/api/user\", func(w http.ResponseWriter, r *http.Request) {\r\n    if versioning.Match(r, \"\u003e= 2.2.3\") {\r\n        // [logic for \u003e= 2.2.3 version of your handler goes here]\r\n        return\r\n    }\r\n})\r\n```\r\n\r\n## Determining The Current Version\r\n\r\nCurrent request version is retrieved by `versioning.GetVersion(r *http.Request)`.\r\n\r\nBy default the `GetVersion` will try to read from:\r\n- `Accept` header, i.e `Accept: \"application/json; version=1.0\"`\r\n- `Accept-Version` header, i.e `Accept-Version: \"1.0\"`\r\n\r\n```go\r\nfunc handler(w http.ResponseWriter, r *http.Request){\r\n    currentVersion := versioning.GetVersion(r)\r\n}\r\n```\r\n\r\nYou can also **set a custom version** to a handler trough a middleware by setting a request context's value.\r\nFor example:\r\n```go\r\nimport (\r\n    \"context\"\r\n    \"net/http\"\r\n\r\n    \"github.com/kataras/versioning\"\r\n)\r\n\r\nfunc urlParamVersion(next http.Handler) http.Handler {\r\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){\r\n        version := r.URL.Query().Get(\"v\") // ?v=2.3.5\r\n        if version == \"\" {\r\n            // set a default version, e.g. 1.0\r\n            version = \"1.0\"\r\n        }\r\n        r = r.WithContext(versioning.WithVersion(r.Context(), version))\r\n        next.ServeHTTP(w, r)\r\n    })\r\n}\r\n```\r\n\r\n## Map Versions to Handlers\r\n\r\nThe `versioning.NewMatcher(versioning.Map) http.Handler` creates a single handler which decides what handler need to be executed based on the requested version.\r\n\r\n```go\r\n// middleware for all versions.\r\nfunc myMiddleware(next http.Handler) http.Handler {\r\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){\r\n        // [...]\r\n        next.ServeHTTP(w, r)\r\n    })\r\n}\r\n\r\nfunc myCustomVersionNotFound(w http.ResponseWriter, r *http.Request) {\r\n    w.WriteHeader(404)\r\n    fmt.Fprintf(w, \"%s version not found\", versioning.GetVersion(r))\r\n}\r\n\r\nrouter := http.NewServeMux()\r\nrouter.Handle(\"/\", myMiddleware(versioning.NewMatcher(versioning.Map{\r\n    // v1Handler is a handler of yuors that will be executed only on version 1.\r\n    \"1.0\":               v1Handler, \r\n    \"\u003e= 2, \u003c 3\":         v2Handler,\r\n    versioning.NotFound: http.HandlerFunc(myCustomNotVersionFound),\r\n})))\r\n```\r\n\r\n### Deprecation\r\n\r\nUsing the `versioning.Deprecated(handler http.Handler, options versioning.DeprecationOptions) http.Handler` function you can mark a specific handler version as deprecated.\r\n\r\n```go\r\nv1Handler = versioning.Deprecated(v1Handler, versioning.DeprecationOptions{\r\n    // if empty defaults to: \"WARNING! You are using a deprecated version of this API.\"\r\n    WarnMessage string\r\n    DeprecationDate time.Time\r\n    DeprecationInfo string\r\n})\r\n\r\nrouter.Handle(\"/\", versioning.NewMatcher(versioning.Map{\r\n    \"1.0\": v1Handler,\r\n    // [...]\r\n}))\r\n```\r\n\r\nThis will make the handler to send these headers to the client:\r\n\r\n- `\"X-API-Warn\": options.WarnMessage`\r\n- `\"X-API-Deprecation-Date\": options.DeprecationDate`\r\n- `\"X-API-Deprecation-Info\": options.DeprecationInfo`\r\n\r\n\u003e versioning.DefaultDeprecationOptions can be passed instead if you don't care about Date and Info.\r\n\r\n## Grouping Routes By Version\r\n\r\nGrouping routes by version is possible as well.\r\n\r\nUsing the `versioning.NewGroup(version string) *versioning.Group` function you can create a group to register your versioned routes.\r\nThe `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`.\r\n\r\n```go\r\nrouter := http.NewServeMux()\r\n\r\n// version 1.\r\nusersAPIV1 := versioning.NewGroup(\"\u003e= 1, \u003c 2\")\r\nusersAPIV1.HandleFunc(\"/api/users\", func(w http.ResponseWriter, r *http.Request) {\r\n    if r.Method != http.MethodGet {\r\n        http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)\r\n        return\r\n    }\r\n\r\n    w.Write([]byte(\"v1 resource: /api/users handler\"))\r\n})\r\nusersAPIV1.HandleFunc(\"/api/users/new\", func(w http.ResponseWriter, r *http.Request) {\r\n    if r.Method != http.MethodPost {\r\n        http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)\r\n        return\r\n    }\r\n\r\n    w.Write([]byte(\"v1 resource: /api/users/new post handler\"))\r\n})\r\n\r\n// version 2.\r\nusersAPIV2 := versioning.NewGroup(\"\u003e= 2, \u003c 3\")\r\nusersAPIV2.HandleFunc(\"/api/users\", func(w http.ResponseWriter, r *http.Request) {\r\n    if r.Method != http.MethodPost {\r\n        http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)\r\n        return\r\n    }\r\n\r\n    w.Write([]byte(\"v2 resource: /api/users handler\"))\r\n})\r\nusersAPIV2.HandleFunc(\"/api/users\", func(w http.ResponseWriter, r *http.Request) {\r\n    if r.Method != http.MethodPost {\r\n        http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)\r\n        return\r\n    }\r\n\r\n    w.Write([]byte(\"v2 resource: /api/users post handler\"))\r\n})\r\n\r\nversioning.RegisterGroups(router, versioning.NotFoundHandler, usersAPIV1, usersAPIV2)\r\n```\r\n\r\n\u003e 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.\r\n\r\n### Deprecation for Group\r\n\r\nJust call the `Group#Deprecated(versioning.DeprecationOptions)` on the group you want to notify your API consumers that this specific version is deprecated.\r\n\r\n```go\r\nuserAPIV1 := versioning.NewGroup(\"1.0\").Deprecated(versioning.DefaultDeprecationOptions)\r\n```\r\n\r\nFor a more detailed technical documentation you can head over to our [godocs](https://godoc.org/github.com/kataras/versioning). And for executable code you can always visit the [_examples](_examples) repository's subdirectory.\r\n\r\n## License\r\n\r\nkataras/versioning is free and open-source software licensed under the [MIT License](https://tldrlegal.com/license/mit-license).\r\n","funding_links":["https://github.com/sponsors/kataras","https://www.paypal.me/kataras"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkataras%2Fversioning","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkataras%2Fversioning","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkataras%2Fversioning/lists"}