Query Parameter API Versioning — Complete Guide
In this tutorial, you'll learn about Query Parameter Versioning. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Query parameter versioning specifies the API version through a query string parameter like ?version=2 or ?api_version=v2 in the URL.
What You'll Learn
By the end of this lesson, you will implement query parameter versioning, understand its Caching implications, and know when it is appropriate.
Why It Matters
Query parameter versioning is the easiest Strategy to implement and does not require URL path changes, making it attractive for simple APIs.
Real-World Use
Some APIs use ?v=2 as a query parameter for versioning, especially when retrofitting versioning onto existing unversioned endpoints.
Query Parameter Versioning Flow
flowchart LR
Request["GET /users?version=2"] --> Parse[Parse ?version=]
Parse -->|version=1| V1[V1 Handler]
Parse -->|version=2| V2[V2 Handler]
Parse -->|missing| Default[Default Version]
Implementation
# query_version.py
from typing import Any, Dict, Optional
class QueryVersionHandler:
def __init__(self, param_name: str = "version", default: int = 1):
self.param_name = param_name
self.default = default
self.handlers: Dict[int, callable] = {}
def add_handler(self, version: int, handler: callable):
self.handlers[version] = handler
def handle(self, query_params: Dict[str, str], **kwargs) -> Dict:
version_str = query_params.get(self.param_name)
version = self.default
if version_str:
try:
version = int(version_str)
except (ValueError, TypeError):
return {"error": f"Invalid version: {version_str}"}
handler = self.handlers.get(version)
if not handler:
return {"error": f"Version {version} not supported",
"supported": list(self.handlers.keys())}
return handler(**kwargs)
handler = QueryVersionHandler(param_name="version", default=1)
def v1_users():
return {"users": [{"id": 1, "name": "Alice"}], "version": "v1"}
def v2_users():
return {"data": [{"id": 1, "name": "Alice", "role": "admin"}],
"meta": {"version": "v2"}}
handler.add_handler(1, v1_users)
handler.add_handler(2, v2_users)
tests = [{"version": "2"}, {"version": "1"}, {}, {"version": "invalid"}, {"version": "3"}]
for params in tests:
result = handler.handle(params)
print(f"query: {params} -> {result.get('meta', result).get('version', result.get('error', '?'))}")
Expected output:
query: {'version': '2'} -> v2
query: {'version': '1'} -> v1
query: {} -> v1
query: {'version': 'invalid'} -> Invalid version: invalid
query: {'version': '3'} -> Version 3 not supported
Caching Implications
Query parameter versioning has significant caching implications:
- CDNs may cache the unversioned URL first, serving wrong version to subsequent clients
- Query parameters are often ignored in cache keys by default
- Solution: include version parameter in cache key configuration
# query_cache_issue.py
from typing import Dict, Optional
class CacheKeyBuilder:
def __init__(self):
self.version_param = "version"
def build(self, path: str, query: Dict) -> str:
version = query.get(self.version_param, "1")
return f"{path}:v{version}"
builder = CacheKeyBuilder()
urls = [
("/api/users", {"version": "1"}),
("/api/users", {"version": "2"}),
("/api/users", {}),
]
for path, query in urls:
key = builder.build(path, query)
print(f"Cache key: {key}")
Expected output:
Cache key: /api/users:v1
Cache key: /api/users:v2
Cache key: /api/users:v1
Common Mistakes
1. Cache Poisoning
Without version in cache keys, one version's response can be served to clients requesting another version. Always include version in cache keys.
2. Not Handling Missing Parameter
Always default to a version when the parameter is missing. Document which version is the default.
3. Conflating with Resource Parameters
Using ?version=2 alongside ?page=2&limit=10 can be confusing. Use a distinct parameter name.
4. Allowing Override via Body
Clients should not be able to override the version in POST bodies. Version should come from the URL/query only.
5. No Validation of Version Values
Accept only valid version numbers. Return 400 for invalid values rather than silently defaulting.
Practice Questions
1. How does query parameter versioning work?
The version is specified as a query string parameter: GET /users?version=2.
2. What is the main caching problem with this approach?
CDNs and proxies may not include query parameters in cache keys, potentially serving wrong versions.
3. When is query parameter versioning acceptable?
For internal APIs, development environments, or when retrofitting versioning onto existing unversioned endpoints.
4. What is the difference between ?version=2 and /v2/users?
URI versioning has unique URLs for each version (good for caching). Query versioning shares the same URL (problematic for caching).
Challenge
Implement query parameter versioning for a blog API where v1 returns posts with author name, v2 adds category and tags, and v3 adds pagination metadata.
FAQ
Mini Project: Query Version Router
# query_router.py
from typing import Dict, Optional
class QueryVersionRouter:
def __init__(self, param: str = "version", default: int = 1):
self.param = param
self.default = default
self.versions = {}
def register(self, version: int, handler):
self.versions[version] = handler
def route(self, query: Dict) -> Dict:
try:
v = int(query.get(self.param, self.default))
except (TypeError, ValueError):
return {"status": 400, "error": "Invalid version"}
if v not in self.versions:
return {"status": 400, "error": f"Version {v} not supported",
"supported": list(self.versions.keys())}
return {"status": 200, "data": self.versions[v](), "version": f"v{v}"}
r = QueryVersionRouter()
r.register(1, lambda: {"msg": "v1 response"})
r.register(2, lambda: {"msg": "v2 response", "extra": True})
print(r.route({"version": "2"}))
print(r.route({"version": "1"}))
print(r.route({}))
Expected output:
{'status': 200, 'data': {'msg': 'v2 response', 'extra': True}, 'version': 'v2'}
{'status': 200, 'data': {'msg': 'v1 response'}, 'version': 'v1'}
{'status': 200, 'data': {'msg': 'v1 response'}, 'version': 'v1'}
What's Next
You understand query parameter versioning. Next, learn about content negotiation, then explore media type versioning.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro