Resolving CORS Conflicts with the ArcGIS JavaScript API

All right. For the sake of the article, I will look at this error one more time:

Here I was, innocent developer that I am, just trying to make an innocent AJAX request to retrieve some innocent data from the ArcGIS Hub at Esri. I was playing around with my Esri developer account and I wanted to use some clean, location-based data, and there aren’t too many places better than the Hub.

(This was not intended to be an Esri advertisement, but honestly, check the site out. Tons of free materials, APIs and SDKs for JavaScript, Python, Java, and mobile dev, and incredible documentation. Everyone complains about bad documentation but nobody cheers when it’s good.)

Anyway.

Up until now, for all of my AJAX requests, I was an axios man. I’m a Node developer, I’ve never needed another tool, and since there are so many other things to learn, I figured I could call it good.

ArcGIS’s JS API, however, is built on top of Dojo, a cool JavaScript framework with a LOT of built-in functionality of which I am only beginning to scratch the surface. It’s a bit older and is a bit more out of the limelight than frameworks like Angular and React, but it’s still interesting to work with and, most importantly, Esri likes it, so therefore we like it.

The Dojo ‘Require’ function format

To set up a basic map through the ArcGIS API, your code should start out looking something like this:

require(["esri/Map", "esri/views/MapView"], function(Map, Mapview){
let map = new Map({
basemap: "streets"
})

let view = new MapView({
container: "viewDiv", // a div you established in your HTML
map: map
})
})

This will render a very basic map and you will feel very smart for doing so. In essence, your map rendering script is just big giant ‘require’ function, which accepts an array of all the stuff you’re importing from “esri/” (and “dojo/”, to be discussed in a moment), and a callback that immediately uses all that stuff.

But wait! What if you want to do, like, anything useful with this map? What if you want to import some data? Are you trying to use axios with Dojo? Are you out of your mind?!

Fortunately, the creators of Dojo thought AJAX might be a useful thing to have in a framework for web development and so they made a built in package for async requests called ‘request’. This must be imported like everything else, but remember, it’s a Dojo thing, not an Esri thing. Oh, and make sure to pass it into the function after you’ve imported it:

require(["dojo/request", "esri/Map", "esri/views/MapView"], function(request, Map, Mapview){
let map = new Map({
basemap: "streets"
})

let view = new MapView({
container: "viewDiv",
map: map
})
})

Step one complete!

AJAX in Dojo

Now, let’s put that ‘request’ import to good use. I want to use some free data from the aforementioned ArcGIS Hub for police incidents in Minneapolis in 2018. For a sanity check, I’m going to open up Postman to see if I can actually get a response:

Yep! That data looks to me to be about one metric crapload, as my high school calculus teacher would say.

So we’re good then, right? Now I’ll make that same request in my code:

const getData = async (req, res, next) => {
try {
const data = await request.get("https://services.arcgis.com/afSMGVsC7QlRK1kZ/arcgis/rest/services/Police_Incidents_2018_PIMS/FeatureServer/0/query?where=1%3D1&outFields=*&outSR=4326&f=json")
if (data) {
console.log(data)
} else {
console.log('STILL GOT PROBLEMS')
}
} catch (error) {
console.log('Oh noes!!!', error)
}
}

Save, refresh, and…

Okay fine, that was my last look.

CORS Policy

What’s happening here is an issue with CORS (Cross-Origin Resource Sharing) policy. I go more into depth on what CORS is and why it exists in another article, but essentially CORS is sort of like a code of etiquette when you’re requesting data from a server that is not from the same domain as your browser-side is. A lot of sites are CORS-enabled, because what is the Internet if not the greatest resource-sharing technology in the history of known civilization?

CORS has some rules, though, namely surrounding the use of headers in your HTTP requests. That GET request I made has (or should have) a lot more than a simple URL. Even though this data is totally free and doesn’t even need an API key, I still have to check a few boxes.

I won’t paste it again, but look at the above error one more time.

“Request header field x-requested-with is not allowed by Access-Control-Allow-Headers in preflight response.”

If we go back to Postman, and click on the “Headers” field, we see a ton of headers that it was sending off behind the scenes without me even telling it to:

Postman knows how to get things

There’s one problem though. ‘x-requested-with’, the apparent issue at hand, is nowhere to be found in this list. What gives?

The Solution

That “problem” actually leads to the solution: the data source doesn’t want the x-requested-with header, and I was sending it automatically from my application! Let’s fix that.

Regardless of which request header was causing the issue, the solution comes down to a very simple manual alteration of my headers in the code. Remember, req.body is an object (it’s an optional parameter that comes directly after the url), and we can add whatever we want to it, including a “headers” key. Let’s make sure we aren’t sending anything associated with that strange x-requested-with header. Make sure to use capitals and string form:

const getData = async (req, res, next) => {
try {
const data = await request.get("https://services.arcgis.com/afSMGVsC7QlRK1kZ/arcgis/rest/services/Police_Incidents_2018_PIMS/FeatureServer/0/query?where=1%3D1&outFields=*&outSR=4326&f=json",
{
headers: {
"X-Requested-With": null
}
}

)
if (data) {
console.log(data)
} else {
console.log('STILL GOT PROBLEMS')
}
} catch (error) {
console.log('Oh noes!!!', error)
}
}

Save, refresh, and…

Now that’s the kind of console I want to see!

Conclusion

That’s really all there is to it. A run-in with the CORS police is usually just a misunderstanding and an absence of necessary headers (or a presence of bad ones). Modify your request body, compare and contrast with Postman or your REST client of choice, and keep on coding.

Now, if you’ll excuse me, I’m going to go and actually use that data for something.

Thanks for reading!

P.S. If you want to know a little more about Cross-Origin Resource Sharing and its ancestor, same-origin-policy, check out my article Origin’s Origin Story: the History of Web Content Origin.

Software developer specializing in JavaScript/Node, React, Express, and Postgres. Other interests include GIS, data viz, and the piano. Looking for a good job!

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store