I'm planning a new API and was curious about what kind of requests I should accept.
The most common Content-Types I've seen for popular modern REST APIs include:
application/x-www-form-urlencoded
application/json
Shout-out to application/graphql
too, but this article will focus on the two above :)
Of course, when just starting out it's easy to accept both and forget about it. For example...
I've done this many times in the past. However, I wanted to set out and discover the true pros and cons of each. I wanted to understand why someone would choose one over the other.
Initial bias
Before I started my search, I wanted to keep my biases in check. What do I currently believe and what am I probably predisposed to want to validate?
application/json
is beginner-friendly...mostly
axios
and superagent
, two of the more popular npm HTTP libraries, work with JSON bodies by default. To send as application/x-www-form-urlencoded
, you have to add additional configuration options.
For example, here's how you might do it with axios. Leveraging a querystring encoding library is necessary:
import axios from 'axios';
import qs from 'qs';
axios({
method: 'POST',
url: '/resource',
data: qs.stringify({
foo: 'bar',
}),
});
It's not really a significant amount of boilerplate, but it's something I've run into in the past.
On the other hand, tools like Postman are much easier to work with when using application/x-www-form-urlencoded
:
Whereas application/json
doesn't allow toggling nor describing individual keys:
URL encoding is used by some of the best APIs out there
I commonly see Stripe referred to as the gold standard as far as API quality. Guess what encoding they use?
The Stripe API is organized around REST. Our API has predictable resource-oriented URLs, accepts form-encoded request bodies, returns JSON-encoded responses, and uses standard HTTP response codes, authentication, and verbs
-- Stripe Docs
And it's not just them. Twilio, the market leader for SMS, is on the same page:
Creating or updating a resource involves performing an HTTP PUT or HTTP POST to a resource URI. In the PUT or POST, you represent the properties of the object you wish to update as form urlencoded key/value pairs. Don't worry, this is already the way browsers encode POSTs by default. But be sure to set the HTTP Content-Type header to "application/x-www-form-urlencoded" for your requests if you are writing your own client.
-- Twilio Docs
These folks have got to know what they're doing, right?
Okay, so some positive and negative biases in regards to both approaches. At this point, I started Googling around. My main search terms:
application/json vs application/x-www-form-urlencoded
-
json vs form
differences
performance
best practices
What I learned
Preflight OPTIONS requests are always sent with JSON
Essentially, there's a whitelist of request properties and values. As long as your HTTP requests stay within this whitelist, no preflight requests are sent.
The application/json
Content-Type isn't on the list, so using this for requests will result in preflight requests. You can read more about this below:
Article No Longer Available
The whitelist is fairly restrictive. No Authorization
headers, Cache-Control
, Keep-Alive
, etc. I'd recommend not trying to circumvent CORS in this regard unless you find there's a significant performance opportunity and minimal additional code complexity.
URL encoded arrays can be a nightmare
Sending any kind of complex data with application/x-www-form-urlencoded
has historically been...not ideal.
The following are two patterns I've seen for sending an array called a
with members b
and c
. Neither of them are particularly great:
a[]=b&a[]=c
a[0]=b&a[1]=c
For larger payloads, you may be saving bytes with JSON as well.
tl;dr
If you can do so securely, support both content types. Always sanitize user input, don't bite off more than you can chew, etc.
If you must choose one request body for your API, go with whatever works for your use case.
- For complex data (especially array/nested object parameters) or if you already return JSON and want to be consistent: consider
application/json
. - For mostly flat param trees,
application/x-www-form-urlencoded
is tried and tested. After all, it's the default for browsers!
Further reading
https://m.alphasights.com/killing-cors-preflight-requests-on-a-react-spa-1f9b04aa5730
Top comments (1)
My app was using application/json to make requests to my API but I found a downside to it. The preflighted OPTIONS request is sent in order, but the actual POST/GET/whatever request is sent arbitrarily later after the next requests have been made. So this wrecks a stateful API if you send a POST with application/json before navigating to another page and GETing the same application/json there because the OPTIONS request for the POST will be sent first, then the next GET, then the POST itself.
For this reason I try to use application/x-www-form-urlencoded as much as possible. Preflighted requests can make race conditions.