The slippery slope
Source: Dev.to
Introduction
A recent LinkedIn post discussed using query parameters to request alternate representations of a resource. The idea is to project only the needed fields, e.g.:
GET /users/1
Response
HTTP/1.1 200 OK
Content-Type: application/json
{"username": "Name", "displayName": "Display Name"}
If the client only needs displayName, the request can be written as:
GET /users/1?fields=displayName
Response
HTTP/1.1 200 OK
Content-Type: application/json
{"displayName": "Display Name"}
At first glance this looks reasonable, but it raises questions about the proper use of the query component.
RFC 3986 Overview
RFC 3986 defines the generic syntax of a URI:
scheme ":" hier-part [ "?" query ] [ "#" fragment ]
- Scheme – protocol identifier (e.g.,
http,ftp). Case‑insensitive. - Authority – host, optional port, and optional userinfo (e.g.,
//www.example.com:80). - Path – hierarchical sequence of segments identifying a resource.
- Query – non‑hierarchical data, typically
key=valuepairs, introduced by?. - Fragment – secondary resource identifier (e.g., a page anchor), introduced by
#; processed by the user agent, not the server.
The specification treats the query string as an opaque set of key‑value pairs. It does not prescribe any semantics for parameters such as fields, sort, or filter.
Query Parameters as a Mini‑DSL
Using query parameters as instructions (e.g., fields, sort, filter) effectively creates a domain‑specific language (DSL) on top of a limited set of HTTP verbs. Examples:
GET /users/1?fields=displayName
GET /users/?sort=
GET /users/?filter=
GET /users?id=1&fields=displayName
Here id is a search parameter, while fields is a projection instruction. Over time, such conventions can become de‑facto standards, forcing API designers to maintain additional documentation and treat these keywords as reserved words.
Why This Is Problematic
- Opacity of the URI – RFC 3986 treats everything after
?as opaque; interpreting it as a DSL adds hidden contracts. - Coupling of verbs and instructions – The limited set of HTTP methods becomes overloaded with extra semantics.
- Maintenance burden – Every new instruction (e.g.,
fields,sort) requires documentation and client awareness.
Content Negotiation as an Alternative
Projection is fundamentally a representation concern. HTTP already provides a mechanism for clients to request specific representations: content negotiation.
GET /users/1
Accept: application/json; fields=displayName
The Accept header can carry media‑type parameters, similar to:
GET /users/1
Accept: application/json; q=0.9; charset=UTF-8
These parameters are open‑ended, allowing servers to interpret them as needed.
RFC 6906 – Profile Parameter
RFC 6906 introduces a more structured way to request representation variations:
GET /users/1
Accept: application/json; profile="http://www.example.com/profiles/user-summary"
Alternatively, a custom media type can be defined:
GET /users/1
Accept: application/vnd.user.displayname+json
Practical Considerations
-
Links – When creating resources, it is simpler to convey a URL that already includes the desired projection:
Location: /users/1?fields=displayNameUsing additional headers to convey projection instructions would complicate the response.
-
Caching – Caches are not inherently aware of custom query‑parameter semantics. Proper caching requires explicit
Vary:headers, and behavior can differ across implementations, leading to cache misses or stale data.
Conclusion
The prevalence of query‑parameter shortcuts does not automatically make them an architectural best practice. While they are pragmatic, they introduce hidden contracts and can erode the clarity and interoperability that REST aims to provide. Remember:
“Architecture erodes not through wrong decisions, but through forgotten reasons.”
Explicitly documenting such shortcuts as conventions, not standards, helps preserve the intent and prevents a slippery slope away from established HTTP semantics.