Call Us: +1-888-227-1645

Using CORS and Ember-Data to point an Ember app to multiple backend deployments

javascript

At Curious Minds we have developers working from different offices doing backend and frontend work on the same projects, so we need a deployment scenario that allows developers to make local changes without fear of breaking things for everyone, but also to be able to develop against a shared environment so that bugs can be replicated and everyone can be sure they’re on the same page. For the backend, this is simple enough: we deploy to three different backend servers besides the production system:

For the frontend, we’ve been using Ember.js, and here things get a little more complicated. Developers need to be able to point their local ember serve instance at all three (local, development, staging) backend deployments so that they can ensure their local development branches of the frontend will work against the different deployments. This kind of flexibility is especially helpful because the frontend and backend are not always going to be synced up in terms of stability: sometimes a backend API feature might be considered stable, but the frontend feature that makes use of that API is still in progress. (This only applies to active development of the ember app. For deployments we use ember-cli-deploy). In that case we’ll want to point a “development” branch of the frontend against the staging backend. Or, if a frontend bug is discovered in staging — or even production! — that requires a hotfix, it’s helpful to be able to make the fix using

ember serve 
against the backend it will be deployed for.

CORS Headers

So how do you point a local emberapp against multiple backends? Our solution was to configure Cross Origin Request Headers headers on our backend deployments, and configure Ember Data’s adapter to make cross-domain requests. To do this, you’ll have to pick a specific origin URL that you’ll use to hit your ember server for each backend, and configure the Origin on each backend to use this URL:

Access-Control-Allow-Origin: http://emberapp.app-deploy.tld:4200/
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, PATCH, DELETE, OPTIONS

Note the URL used in the Allow-Origin header. It has to be the exact address used to hit the ember server, including protocol (http) and port (4200). You'll also have to map it to localhost in your local DNS configuration (for example, in your /etc/hosts

 
file or your dnsmasq settings). So for a three tiered development deployment like we use, you’d configure your /etc/hosts file like so:

127.0.0.1 emberapp.local-deploy.app emberapp.development-deploy.tld emberapp.staging-deploy.tld

Be sure to also configure the Access-Control-Allow-Headers with any other headers necessary (wildcards (*) are not allowed, if you need the equivalent to a wildcard, you can use this list of headers.)

Configure Ember and Ember-data to support CORS requests Environment configuration

Once you have the CORS headers configured, you’re ready to set up your Ember app to support cross domain requests to your api. The easiest way is to configure ember environments, one for each backend, in config/environment.js .

 
First we add some defaults to the APP key of the environment config.

var ENV = {
	...	
        APP: {
		...
		usingCors: false,
		corsWithCreds: false,
		apiURL: null
	}
};

Next, we use a

 
switch
 
on environment
 
to configure our API URLs:

switch (environment) {
	case 'local-backend':
		ENV.APP.usingCors = true;
		ENV.APP.corsWithCreds = true;
		ENV.APP.apiURL = 'http://local-backend.app:8000'
		break;
	case 'development-backend':
		ENV.APP.usingCors = true;
		ENV.APP.corsWithCreds = true;
		ENV.APP.apiURL = 'https://development-backend.tld/'
		break;
	case 'staging-backend':
		ENV.APP.usingCors = true:
		ENV.APP.corsWithCreds = true;
		ENV.APP.apiURL = 'https://staging-backend.tld/'
		break;
}

This will enable to point the emberapp to the backend we want to test against by just setting the environment when running ember serve, like so:

ember s --environment local-backend

Substitue local-backend

 
for development-backend
 
or staging-backend to point Ember at the other backends). Then point your browser at the ember server via the URL you’ve configured as the CORS Origin, e.g., http://emberapp.development-backend.tld:4200/ for the development environment.

Patching the Ember-Data Adapter

Once you’ve got the environments set up, you’re ready to patch ember-data’s “adapter” to support CORS requests against the environment specific API URL by setting up your app/adapters/application.js like so (here we use the JSONAPIAdapter but this override will work with any adapter provided by Ember Data):

import JSONAPIAdapter from 'ember-data/adapters/json-api';
import ENV from '../config/environment';

export default JSONAPIAdapter.extend({
	namespace: 'your-api-namespace',
	host: ENV.APP.apiURL,
	headers: {
		'X-Requested-With': 'XMLHttpRequest'
	},
	ajax(url, method, hash) {
		if (ENV.APP.usingCors) {
			if (hash === undefined) { hash = {}; }

			hash.crossDomain = true;

			if (ENV.APP.corsWithCreds) {
				hash.xhrFields = { withCredentials: true };
			}
		}
	}

	return this._super(url, method, hash);
});

This override is pretty simple. It sets the host to the configured API URL, and ensures that XHR requests made by the adapter have the crossDomain

 
and withCredentials
 
field set correctly. The browser will take care of the rest.

Patching jQuery’s ajax function

For many applications, this will be all you need to do. Running ember s against the configured environments will direct your ember data requests to the respective backend seamlessly. But you’ll still run into problems if you bypass Ember Data’s adapters at any point and make direct ajax calls through jQuery.

To handle that sort of situation, you’ll have to patch jQuery’s ajax function to also support CORS requests. Doing so is not so hard, and jQuery is stable enough that it likely won’t break in the near future. Here’s how we get it done (put this in app/initializers/services.js or some other initializer that will run early):

/*global jQuery */
import ENV from '../config/environment';

export function initialize() {
	if (ENV.APP.usingCors) {
		(function($) {
			var _old = $.ajax;
			$.ajax = function() {
				var url, settings, apiURL = ENV.APP.apiURL;

				/* Handle the different function signatures available for $.ajax() */
				if (arguments.length === 2) {
					url = arguments[0];
					settings = arguments[1];
				} else {
					settings = arguments[0];
				}

				settings.crossDomain = true;
				if (!settings.xhrFields) {
					settings.xhrFields = {};
				}
				settings.xhrFields.withCredentials = true;

				if (!url) {
					url = settings.url;
				}

				/* If we still don't have an URL, execute the request and let jQuery handle it */
				if (!url) {
					return _old.apply(this, [settings]);
				}

				/* combine the apiURL and the url request if necessary */
				if (!url.includes(apiURL)) {
					/* Do we need a '/'? */
					if (url[0] !== '/' && apiURL[apiURL.length-1] !== '/') {
						url = '/' + url;
					}
					url = apiURL + url;
				}
				settings.url = url;

				return _old.apply(this, [settings]);
			};
		})(jQuery);
	}
}

This override is again fairly straightforward. It should handle 9 out of 10 cases of ajax requests in your emberapp.

Let's build something amazing together

Give us a ring and let us know how we can help you reach your goals. Or if you'd like, start a chat. We're usually available 9-5 EST. We try to respond to every inquiry within one business day.

Phone number
+1-888-227-1645

Technologies and services we work with:

Laravel Laravel
WordPress WordPress
React ReactJS
EmberJS EmberJS
woocommerce WooCommerce
next.js NextJS
gatsby Gatsby
Shopify Shopify
VueJs VueJS
contentful Contentful
next.js JAMStack
gatsby Laravel Jigsaw
WPEngine WP Engine
Laravel Livewire Laravel Livewire
Netlify Netlify