What is API Versioning?
Hey Everyone, How are you doing, Today I am back with one of the most important topics called API Versioning.
Let's discuss the different techniques we have to version an API with implementation in Golang.
What is API Versioning:
API versioning is a crucial aspect of API design and development, ensuring that changes in your API do not disrupt existing clients.
This guide explores four common methods for API versioning
URI Versioning:
URI versioning involves embedding the version number directly in the URI path. This method is straightforward to understand for clients.
Version 1:
GET /api/v1/products
Version 2:
GET /api/v2/products
Let's check the implementation for this:
package main
import (
"fmt"
"log"
"net/http"
)
// Handler for version 1 of the API
func getV1Data(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, `{"version":"v1", "data":"This is v1 data"}`)
}
// Handler for version 2 of the API
func getV2Data(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, `{"version":"v2", "data":"This is v2 data"}`)
}
// Main handler to route to the correct version
func mainHandler(w http.ResponseWriter, r *http.Request) {
switch {
case r.URL.Path == "/api/v1/data":
getV1Data(w, r)
case r.URL.Path == "/api/v2/data":
getV2Data(w, r)
default:
http.NotFound(w, r)
}
}
func main() {
http.HandleFunc("/", mainHandler)
log.Println("Starting server on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Test it with the following curls
# Request to version 1 of the API
curl http://localhost:8080/api/v1/data
# Request to version 2 of the API
curl http://localhost:8080/api/v2/data
Header Versioning:
Header versioning involves specifying the API version in the HTTP headers. This method keeps the URI clean and focuses on headers for version control.
Version 1:
GET /api/products
Headers:
API-Version: v1
Version 2:
GET /api/products
Headers:
API-Version: v2
Let's check the implementation for this:
package main
import (
"fmt"
"log"
"net/http"
)
// Handler for version 1 of the API
func getV1Data(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, `{"version":"v1", "data":"This is v1 data"}`)
}
// Handler for version 2 of the API
func getV2Data(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, `{"version":"v2", "data":"This is v2 data"}`)
}
// Main handler to route to the correct version based on headers
func mainHandler(w http.ResponseWriter, r *http.Request) {
apiVersion := r.Header.Get("API-Version")
switch apiVersion {
case "v1":
getV1Data(w, r)
case "v2":
getV2Data(w, r)
default:
http.Error(w, "Unsupported API version", http.StatusBadRequest)
}
}
func main() {
http.HandleFunc("/api/data", mainHandler)
log.Println("Starting server on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Test it with the following curls
# Request to version 1 of the API
curl -H "API-Version: v1" http://localhost:8080/api/data
# Request to version 2 of the API
curl -H "API-Version: v2" http://localhost:8080/api/data
# Request with an unsupported version
curl -H "API-Version: v3" http://localhost:8080/api/data
Query Parameter Versioning:
In query parameter versioning, the version number is included as a query parameter in the request URL. This method is flexible and easy to implement.
Version 1:
GET /api/products?version=v1
Version 2:
GET /api/products?version=v2
Let's check the implementation for this:
package main
import (
"fmt"
"log"
"net/http"
)
// Handler for version 1 of the API
func getV1Data(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, `{"version":"v1", "data":"This is v1 data"}`)
}
// Handler for version 2 of the API
func getV2Data(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, `{"version":"v2", "data":"This is v2 data"}`)
}
// Main handler to route to the correct version based on query parameter
func mainHandler(w http.ResponseWriter, r *http.Request) {
queryParams := r.URL.Query()
apiVersion := queryParams.Get("version")
switch apiVersion {
case "v1":
getV1Data(w, r)
case "v2":
getV2Data(w, r)
default:
http.Error(w, "Unsupported API version", http.StatusBadRequest)
}
}
func main() {
http.HandleFunc("/api/data", mainHandler)
log.Println("Starting server on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Test it with the following curls:
# Request to version 1 of the API
curl http://localhost:8080/api/data?version=v1
# Request to version 2 of the API
curl http://localhost:8080/api/data?version=v2
# Request with an unsupported version
curl http://localhost:8080/api/data?version=v3
Media Type Versioning:
Media-type versioning, also known as content negotiation, uses the Accept header to specify the version. This method is flexible and integrates well with REST principles.
Version 1:
GET /api/products
Headers:
Accept: application/vnd.example.v1+json
Version 2:
GET /api/products
Headers:
Accept: application/vnd.example.v2+json
Let's check the implementation for this:
package main
import (
"fmt"
"log"
"net/http"
"strings"
)
// Handler for version 1 of the API
func getV1Data(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/vnd.example.v1+json")
fmt.Fprintln(w, `{"version":"v1", "data":"This is v1 data"}`)
}
// Handler for version 2 of the API
func getV2Data(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/vnd.example.v2+json")
fmt.Fprintln(w, `{"version":"v2", "data":"This is v2 data"}`)
}
// Main handler to route to the correct version based on Accept header
func mainHandler(w http.ResponseWriter, r *http.Request) {
acceptHeader := r.Header.Get("Accept")
if strings.Contains(acceptHeader, "application/vnd.example.v1+json") {
getV1Data(w, r)
} else if strings.Contains(acceptHeader, "application/vnd.example.v2+json") {
getV2Data(w, r)
} else {
http.Error(w, "Unsupported API version", http.StatusUnsupportedMediaType)
}
}
func main() {
http.HandleFunc("/api/data", mainHandler)
log.Println("Starting server on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Test it with the following curls:
# Request version 1 of the API
curl -H "Accept: application/vnd.example.v1+json" http://localhost:8080/api/data
# Request version 2 of the API
curl -H "Accept: application/vnd.example.v2+json" http://localhost:8080/api/data
# Request with an unsupported version
curl -H "Accept: application/vnd.example.v3+json" http://localhost:8080/api/data
These are the common API Versioning Techniques.
That's it for Today, see you again with yet another Good topic until then bye bye
Durga Chikkala signing off...