Avoiding pre-flight OPTIONS calls on CORS requests

A typical web application architecture

Above we have the typical way web apps are architected today. 
The backend is run on an entirely different machine and its API is exposed for the world.

On another machine we have our SPA application being served up by express whose environment is built using webpack/browserify.

Because of this architecture, we end up having to make CORS requests to our own backend.

If you do a bit of reading about CORS requests on Mozilla Developer Network, you’ll find out that pre-flight OPTIONS calls are sent for all GET/POST unless they are classified as simple requests.

Pre-flight OPTIONS call

Criteria to be considered a simple request :

> If the request uses methods

> Allowed headers

> If the Content-Type header has a value

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

It goes without saying that those are some tight restrictions to be considered a simple request. In todays web apps, where most of us use Authorization headers, most requests (even GETs) will NOT be considered simple and hence a pre-flight call will be sent to the domain from the browser.

This is a huge blow and plays a big part in latency. Initially, I went down the path of messing about with headers to cut down the number of non-simple requests but soon realized that was a huge waste of time and code.

The right thing to do would be to route the requests to your App server(the one which serves up your built app and assets) which forwards it to the backend. And because the forwarded call is from server to server and not browser to server, we successfully avoid ALL pre-flight CORS requests!

I achieved this by choosing a decent enough open sourced library for proxy middleware: http-proxy-middleware.

Braver souls might go down the path of whipping up their own proxy concoction.


Steps to route your calls to the backend through your app server:

> Install http-proxy-middleware.

> Go to your server.js or similarly named file which whips up the express server and tell it to use the proxy middleware.

import proxy from 'http-proxy-middleware';
...
let options = {
target: baseApiUrl, //api.example.com
changeOrigin: true,
logLevel: ‘debug’,
onError: function onError(err, req, res) {
console.log(‘Something went wrong with the proxy middleware.’, err)
res.end();
}
};
app.use(‘/api’, proxy(options)); //only forward calls with ‘/api’ route

So a call that was previously api.example.com/posts/post_id would become app.example.com/api/posts/post_id, which would then be forwarded to the backend route anyways.

> Construct headers for your proxy calls.

const proxyOptions = {
headers: {
Accept: ‘application/json’,
‘Content-Type’: ‘application/vnd.example.v1+json’,
},
};

This needs to be sent as a param to whichever library you are using to make GET/POST/PUT/DELETE calls.

Below is an example GET call using the axios library.

axios.get(url, proxyOptions).then(response => resolve(response)).catch(error => resolve(error.response)); 

> And finally, don’t forget to tell your module builder about the new proxy middleware that you’re using (code below is for webpack production config).

module.exports = {
entry: {
app: ['./src/index.js'],
vendor: [ 'axios', 'http-proxy-middleware']
}
...
};

And that is all! Enjoy avoiding those nasty OPTIONS calls using the above option :)