Cross-Origin Request (CORS) | Using Node.js as a Proxy for Angular.js Ajax Requests

Ajax and Angular.js

This article is going to assume you have some understanding of Angular.js and ajax requests. Since you happen to have found this, you most likely have found that you get some errors when trying to make client side ajax requests using the $http service provided. The error that you see in the Chrome console (or whatever other browser console you are using) is something similar to this:

"Script from origin 'http://someapi.com' has been blocked from loading by Cross-Origin Resource Sharing policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://someapi.com' is therefore not allowed access."

If http://someapi.com happens to be our api, we can use some tools to allow requests from our client side Angular application or even just all client side Ajax requests in general, but this solution leaves us open to any client side JavaScript Ajax requests. This can potentially be a bad thing. So, why not proxy all our requests through a server side application and have our server side application return the data back to Angular! More on this soon.

The AngularJS App

We won't spend much time on this app since this article assumes you are already at a point similar. However, you can get some starter code here by checking out the start branch. The master branch has the final product!

The Angular app will be using the Open Notify api; specifically the endpoint that gets us the current people in space! A service has been created and injected into our controller which will display this information:


/* apiService.js */
function ApiService($http) {
    this.$http = $http;
    this.url = "http://api.open-notify.org/astros.json";
};

angular.module('app').service('apiService', ApiService);
ApiService.prototype.getAstronauts = function() {
    // Return promise for controller to use.
    return this.$http.get(this.url)
};

Currently, using this code will give us a CORS error when trying to make a get request to the provided url. So let's look at our solution to this problem.

A Proxy?

A proxy in this context means a "middle man" that will take our requests and hand them off to the api, which will then return a response to our proxy to be handed off to Angular.

Proxy

Since our proxy will be on the same port and same system as our Angular app, there will be no CORs error.

We can use NodeJS as our proxy and as a means of serving our AngularJS application.

Node.js

Node.js is an open source, cross-platform runtime environment for server-side and networking applications. Node applications are written in JavaScript and can be run within the Node runtime a number of operating systems.

Node provides an event-driven architecture and a non-blocking I/O API that optimizes an application's throughput and scalability. These technologies are commonly used for real-time web applications.Node combines Google's V8 JavaScript engine, an event-loop, and a low-level I/O API.

Why Google's V8 JavaScript engine?
  • V8 is open-source under the BSD license.
  • V8 is extremely fast.
  • V8 is focused on the web, so is proficient with internet fundamentals like HTTP, DNS, TCP.

Frameworks can be used to accelerate the development of applications, and common frameworks are Express.js, Socket.IO, and Connect. We will be using Express.js!

Node has a default package manager similar to Ruby Gems or Python PIP called npm. Npm can be used to easily install any required accompanying packages such as Express.js.

Writing the Node.js Server

We can serve our Angular project with very few lines of code. There are plenty of tutorials teaching you how to install Node on your machine. So I will assume you can find that with a quick search.

Our next step is to make a project directory (or clone the start directory listed above), navigate into the project directory using our terminal, and type the following command:


npm init

You will then be prompted to fill in some data about your application. You can choose to just hit enter all the way through and update this later if needed.

NOTE: The directory structure should be similar to the following:


/ProjectName
    /app
        (angular project)
    index.js (node server)

For our project, we are using Express to help us set up our server. We need to make this part of our dependencies. We can install Express with the following command:


npm install express --save

The npm install package-name installs the package locally in our project directory. Adding --savenpm init. Having the required packages in our package.json file allows us to simply run npm install when in our project directory in the future to install all required packages if we were to move our project to another server (possibly other then our local machine).

Next, we need to create our JavaScript file for the server code required. Create a file in this root directory called index.js as shown above in our project directory structure.

Node uses a module loader for including required JavaScript files called require.js. Require is very easy to use and makes working with multiple JavaScript files extra easy for us. We can include Express at the top of our index.js file like so:


/*
 * Module Dependencies
 */
var express = require('express');

If express was installed using npm, require will find it by name. If we wished to include our own JavaScript file, we can use an absolute path or the relative path from our index.js file.

We can then create an instance of express as our server, set the port we wish to listen on, and choose a directory for serving static files (our angular app).


/*
 * Module Dependencies
 */
var express = require('express');

var server = express();
server.set('port', 3000);
server.use(express.static(__dirname + '/app'));

The final step is to tell our server to listen on that port for any requests!


/*
 * Module Dependencies
 */
var express = require('express');

var server = express();
server.set('port', 3000);
// Serve static directory where our angular app is located.
server.use(express.static(__dirname + '/app'));

server.listen(server.get('port'), function() {
    console.log('Express server listening on port ' + server.get('port'));
});

We can now run the following command and visit localhost:3000 and see our Angular app in action!


node index.js

13 lines of code including spaces and comments? Not too bad!

Writing the Node Proxy

We can work at writing this proxy from scratch, but we usually don't have time for that! So we can use npm to install the http-proxy module!


npm install http-proxy --save

This module will help us forward all requests to our desire web api!

Let's include the new module in our file, create a variable for our api forwarding url, and set up an endpoint to grab all requests to the server starting with /space/.


var express = require('express');
var httpProxy = require('http-proxy');

var apiForwardingUrl = 'http://api.open-notify.org/astros.json?';

var server = express();
server.set('port', 3000);
server.use(express.static(__dirname + '/app'));

var apiProxy = httpProxy.createProxyServer();

// Grab all requests to the server with "/space/".
server.all("/space/*", function(req, res) {
    console.log("Request made to /space/");
});

server.listen(server.get('port'), function() {
    console.log('Express server listening on port ' + server.get('port'));
});

We can now head to our Angular app and update the $http request to be a local request since Node is now handling our requests.


function ApiService($http) {
    this.$http = $http;
    this.url = "/space/";
};

angular.module('app').service('apiService', ApiService);

ApiService.prototype.getAstronauts = function() {
    return this.$http.get(this.url)
};

If we use ctrl+c to cancel the currently running node server and re-run our server using node index.js, we can see the terminal output "Request made to /space/" every time we refresh the Angular page since the requests are now going to the Node server.

Last thing! Use the http-proxy proxy module to forward our request to the apiForwardingUrl.


var express = require('express');
var httpProxy = require('http-proxy');

var apiForwardingUrl = 'http://api.open-notify.org/astros.json?';

var server = express();
server.set('port', 3000);
server.use(express.static(__dirname + '/app'));

var apiProxy = httpProxy.createProxyServer();

console.log('Forwarding API requests to ' + apiForwardingUrl);

server.all("/space/*", function(req, res) {
    apiProxy.web(req, res, {target: apiForwardingUrl});
});

server.listen(server.get('port'), function() {
    console.log('Express server listening on port ' + server.get('port'));
});

Restarting our server using the method from before, we can see the request is successfully forwarded to the open notify api and then proxied back to the Angular app!

Get the code here!

Further Learning

Handling post requests are a bit different. In order to read the body of the post request on our end, we may require an extra module called body-parser. If we are just forwarding the request, often it is not required.


var bodyParser = require('body-parser');
server.use(bodyParser.json());
server.use(bodyParser.urlencoded({
    extended: true
}));

Additionally, if we wish to forward from a http site to an https site, we require special instructions for the http-proxy proxy module. We may also want an error callback method incase anything goes wrong. We can do this using the following code (adding a proxy options object and prototyping an error handler):


// Solution for forwarding from http to https taken from:
// http://stackoverflow.com/questions/15801014/how-to-use-node-http-proxy-for-http-to-https-routing
var proxyOptions = {
    changeOrigin: true
};

httpProxy.prototype.onError = function (err) {
    console.log(err);
};

var apiProxy = httpProxy.createProxyServer(proxyOptions);

Finally, if we wish to only handle specific types of request (get or post or delete or put - instead of all), we can do that using express.



server.get("/stuff", function(req, res) {
   res.json({"stuff": "some stuff"});
});

There is also plenty more to learn using express routers which will be in another blog coming soon!