DEV Community

Borja Canseco
Borja Canseco

Posted on

Request body encoding: JSON? x-www-form-urlencoded?

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...

import express from 'express';
import bodyParser from 'body-parser';

const app = express();

// application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({extended: false}));

// application/json
app.use(bodyParser.json());

export default app;
Enter fullscreen mode Exit fullscreen mode
A minimal example of a Node.js webserver accepting both request body types

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',
  }),
});
Enter fullscreen mode Exit fullscreen mode

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:

Postman urlencoded demo

Toggling and descriptions? Fantastic!

Whereas application/json doesn't allow toggling nor describing individual keys:

Postman json demo

Yeah yeah, I know. Comments aren't valid JSON anyway. But come on!

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:

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)

Collapse
 
zenulabidin profile image
Ali Sherief • Edited

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.