14 min read

In this article by Randall Goya, and Rajesh Gunasundaram the author of the book CORS Essentials, Node.js is a cross-platform JavaScript runtime environment that executes JavaScript code at server side. This enables to have a unified language across the web application development. JavaScript becomes the unified language that runs both on client side and server side.

(For more resources related to this topic, see here.)

In this article we will learn about:

  • Node.js is a JavaScript platform for developing server-side web applications.
  • Node.js can provide the web server for other frameworks including Express.js, AngularJS, Backbone,js, Ember.js and others.
  • Some other JavaScript frameworks such as ReactJS, Ember.js and Socket.IO may also use Node.js as the web server.
  • Isomorphic JavaScript can add server-side functionality for client-side frameworks.
  • JavaScript frameworks are evolving rapidly. This article reviews some of the current techniques, and syntax specific for some frameworks. Make sure to check the documentation for the project to discover the latest techniques.
  • Understanding CORS concepts, you may create your own solution, because JavaScript is a loosely structured language.
  • All the examples are based on the fundamentals of CORS, with allowed origin(s), methods, and headers such as Content-Type, or preflight, that may be required according to the CORS specification.

JavaScript frameworks are very popular

JavaScript is sometimes called the lingua franca of the Internet, because it is cross-platform and supported by many devices. It is also a loosely-structured language, which makes it possible to craft solutions for many types of applications.

Sometimes an entire application is built in JavaScript. Frequently JavaScript provides a client-side front-end for applications built with Symfony, Content Management Systems such as Drupal, and other back-end frameworks.

Node.js is server-side JavaScript and provides a web server as an alternative to Apache, IIS, Nginx and other traditional web servers.

Introduction to Node.js

Node.js is an open-source and cross-platform library that enables in developing server-side web applications. Applications will be written using JavaScript in Node.js can run on many operating systems, including OS X, Microsoft Windows, Linux, and many others. Node.js provides a non-blocking I/O and an event-driven architecture designed to optimize an application’s performance and scalability for real-time web applications. The biggest difference between PHP and Node.js is that PHP is a blocking language, where commands execute only after the previous command has completed, while Node.js is a non-blocking language where commands execute in parallel, and use callbacks to signal completion. Node.js can move files, payloads from services, and data asynchronously, without waiting for some command to complete, which improves performance.

Most JS frameworks that work with Node.js use the concept of routes to manage pages and other parts of the application. Each route may have its own set of configurations. For example, CORS may be enabled only for a specific page or route.

Node.js loads modules for extending functionality via the npm package manager. The developer selects which packages to load with npm, which reduces bloat. The developer community creates a large number of npm packages created for specific functions.

JXcore is a fork of Node.js targeting mobile devices and IoTs (Internet of Things devices). JXcore can use both Google V8 and Mozilla SpiderMonkey as its JavaScript engine. JXcore can run Node applications on iOS devices using Mozilla SpiderMonkey.

MEAN is a popular JavaScript software stack with MongoDB (a NoSQL database), Express.js and AngularJS, all of which run on a Node.js server.

JavaScript frameworks that work with Node.js

Node.js provides a server for other popular JS frameworks, including AngularJS, Express.js. Backbone.js, Socket.IO, and Connect.js. ReactJS was designed to run in the client browser, but it is often combined with a Node.js server.

As we shall see in the following descriptions, these frameworks are not necessarily exclusive, and are often combined in applications.

Express.js is a Node.js server framework

Express.js is a Node.js web application server framework, designed for building single-page, multi-page, and hybrid web applications. It is considered the “standard” server framework for Node.js. The package is installed with the command npm install express –save.

AngularJS extends static HTML with dynamic views

HTML was designed for static content, not for dynamic views. AngularJS extends HTML syntax with custom tag attributes. It provides model–view–controller (MVC) and model–view–viewmodel (MVVM) architectures in a front-end client-side framework.  AngularJS is often combined with a Node.js server and other JS frameworks.

AngularJS runs client-side and Express.js runs on the server, therefore Express.js is considered more secure for functions such as validating user input, which can be tampered client-side. AngularJS applications can use the Express.js framework to connect to databases, for example in the MEAN stack.

Connect.js provides middleware for Node.js requests

Connect.js is a JavaScript framework providing middleware to handle requests in Node.js applications. Connect.js provides middleware to handle Express.js and cookie sessions, to provide parsers for the HTML body and cookies, and to create vhosts (virtual hosts) and error handlers, and to override methods.

Backbone.js often uses a Node.js server

Backbone.js is a JavaScript framework with a RESTful JSON interface and is based on the model–view–presenter (MVP) application design. It is designed for developing single-page web applications, and for keeping various parts of web applications (for example, multiple clients and the server) synchronized. Backbone depends on Underscore.js, plus jQuery for use of all the available fetures. Backbone often uses a Node.js server, for example to connect to data storage.

ReactJS handles user interfaces

ReactJS is a JavaScript library for creating user interfaces while addressing challenges encountered in developing single-page applications where data changes over time. React handles the user interface in model–view–controller (MVC) architecture. ReactJS typically runs client-side and can be combined with AngularJS.

Although ReactJS was designed to run client-side, it can also be used server-side in conjunction with Node.js. PayPal and Netflix leverage the server-side rendering of ReactJS known as Isomorphic ReactJS. There are React-based add-ons that take care of the server-side parts of a web application.

Socket.IO uses WebSockets for realtime event-driven applications

Socket.IO is a JavaScript library for event-driven web applications using the WebSocket protocol ,with realtime, bi-directional communication between web clients and servers. It has two parts: a client-side library that runs in the browser, and a server-side library for Node.js. Although it can be used as simply a wrapper for WebSocket, it provides many more features, including broadcasting to multiple sockets, storing data associated with each client, and asynchronous I/O. Socket.IO provides better security than WebSocket alone, since allowed domains must be specified for its server.

Ember.js can use Node.js

Ember is another popular JavaScript framework with routing that uses Moustache templates. It can run on a Node.js server, or also with Express.js. Ember can also be combined with Rack, a component of Ruby On Rails (ROR). Ember Data is a library for  modeling data in Ember.js applications.

CORS in Express.js

The following code adds the Access-Control-Allow-Origin and Access-Control-Allow-Headers headers globally to all requests on all routes in an Express.js application. A route is a path in the Express.js application, for example /user for a user page. app.all sets the configuration for all routes in the application. Specific HTTP requests such as GET or POST are handled by app.get and app.post.

app.all('*', function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "X-Requested-With");
  next();
});

app.get('/', function(req, res, next) {
  // Handle GET for this route
});

app.post('/', function(req, res, next) {
// Handle the POST for this route
});

For better security, consider limiting the allowed origin to a single domain, or adding some additional code to validate or limit the domain(s) that are allowed. Also, consider limiting sending the headers only for routes that require CORS by replacing app.all with a more specific route and method.

The following code only sends the CORS headers on a GET request on the route/user, and only allows the request from http://www.localdomain.com.

app.get('/user', function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "http://www.localdomain.com");
  res.header("Access-Control-Allow-Headers", "X-Requested-With");
  next();
});

Since this is JavaScript code, you may dynamically manage the values of routes, methods, and domains via variables, instead of hard-coding the values.

CORS npm for Express.js using Connect.js middleware

Connect.js provides middleware to handle requests in Express.js.

You can use Node Package Manager (npm) to install a package that enables CORS in Express.js with Connect.js:

npm install cors

The package offers flexible options, which should be familiar from the CORS specification, including using credentials and preflight. It provides dynamic ways to validate an origin domain using a function or a regular expression, and handler functions to process preflight.

Configuration options for CORS npm

origin: Configures the Access-Control-Allow-Origin CORS header with a string containing the full URL and protocol making the request, for example http://localdomain.com.

Possible values for origin:

  • Default value TRUE uses req.header(‘Origin’) to determine the origin and CORS is enabled.
  • When set to FALSE CORS is disabled.
  • It can be set to a function with the request origin as the first parameter and a callback function as the second parameter.
  • It can be a regular expression, for example /localdomain.com$/, or an array of regular expressions and/or strings to match.

methods: Sets the Access-Control-Allow-Methods CORS header.

Possible values for methods:

  • A comma-delimited string of HTTP methods, for example GET, POST
  • An array of HTTP methods, for example [‘GET’, ‘PUT’, ‘POST’]

allowedHeaders: Sets the Access-Control-Allow-Headers CORS header.

Possible values for allowedHeaders:

  • A comma-delimited string of  allowed headers, for example “Content-Type, Authorization
  • An array of allowed headers, for example [‘Content-Type’, ‘Authorization’]
  • If unspecified, it defaults to the value specified in the request’s Access-Control-Request-Headers header

exposedHeaders: Sets the Access-Control-Expose-Headers header.

Possible values for exposedHeaders:

  • A comma-delimited string of exposed headers, for example ‘Content-Range, X-Content-Range
  • An array of exposed headers, for example [‘Content-Range’, ‘X-Content-Range’]
  • If unspecified, no custom headers are exposed

credentials: Sets the Access-Control-Allow-Credentials CORS header.

Possible values for credentials:

  • TRUE—passes the header for preflight
  • FALSE or unspecified—omit the header, no preflight

maxAge: Sets the Access-Control-Allow-Max-Age header.

Possible values for maxAge

  • An integer value in milliseconds for TTL to cache the request
  • If unspecified, the request is not cached

preflightContinue: Passes the CORS preflight response to the next handler.

The default configuration without setting any values allows all origins and methods without preflight. Keep in mind that complex CORS requests other than GET, HEAD, POST will fail without preflight, so make sure you enable preflight in the configuration when using them. Without setting any values, the configuration defaults to:

{
  "origin": "*",
  "methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
  "preflightContinue": false
}

Code examples for CORS npm

These examples demonstrate the flexibility of CORS npm for specific configurations. Note that the express and cors packages are always required.

Enable CORS globally for all origins and all routes

The simplest implementation of CORS npm enables CORS for all origins and all requests. The following example enables CORS for an arbitrary route “ /product/:id” for a GET request by telling the entire app to use CORS for all routes:

var express = require('express')
  , cors = require('cors')
  , app = express();
 
app.use(cors()); // this tells the app to use CORS for all re-quests and all routes
 
app.get('/product/:id', function(req, res, next){
  res.json({msg: 'CORS is enabled for all origins'});
});
 
app.listen(80, function(){
  console.log('CORS is enabled on the web server listening on port 80');
});

Allow CORS for dynamic origins for a specific route

The following example uses corsOptions to check if the domain making the request is in the whitelisted array with a callback function, which returns null if it doesn’t find a match. This CORS option is passed to the route “product/:id” which is the only route that has CORS enabled. The allowed origins can be dynamic by changing the value of the variable “whitelist.”

var express = require('express')
  , cors = require('cors')
  , app = express();
 
// define the whitelisted domains and set the CORS options to check them
var whitelist = ['http://localdomain.com', 'http://localdomain-other.com'];
var corsOptions = {
  origin: function(origin, callback){
    var originWhitelisted = whitelist.indexOf(origin) !== -1;
    callback(null, originWhitelisted);
  }
};
 
// add the CORS options to a specific route /product/:id for a GET request
app.get('/product/:id', cors(corsOptions), function(req, res, next){
  res.json({msg: 'A whitelisted domain matches and CORS is enabled for route product/:id'});
});
 
// log that CORS is enabled on the server
app.listen(80, function(){
  console.log(''CORS is enabled on the web server listening on port 80'');
});

You may set different CORS options for specific routes, or sets of routes, by defining the options assigned to unique variable names, for example “corsUserOptions.” Pass the specific configuration variable to each route that requires that set of options.

Enabling CORS preflight

CORS requests that use a HTTP method other than GET, HEAD, POST (for example DELETE), or that use custom headers, are considered complex and require a preflight request before proceeding with the CORS requests. Enable preflight by adding an OPTIONS handler for the route:

var express = require('express')
  , cors = require('cors')
  , app = express();
 
// add the OPTIONS handler
app.options('/products/:id', cors()); // options is added to the route /products/:id 

// use the OPTIONS handler for the DELETE method on the route /products/:id
app.del('/products/:id', cors(), function(req, res, next){
  res.json({msg: 'CORS is enabled with preflight on the route '/products/:id' for the DELETE method for all origins!'});
});
 
app.listen(80, function(){
  console.log('CORS is enabled on the web server listening on port 80'');
});

You can enable preflight globally on all routes with the wildcard:

app.options('*', cors());

Configuring CORS asynchronously

One of the reasons to use NodeJS frameworks is to take advantage of their asynchronous abilities, handling multiple tasks at the same time. Here we use a callback function corsDelegateOptions and add it to the cors parameter passed to the route /products/:id. The callback function can handle multiple requests asynchronously.

var express = require('express')
  , cors = require('cors')
  , app = express();
 // define the allowed origins stored in a variable
var whitelist = ['http://example1.com', 'http://example2.com'];

// create the callback function
var corsDelegateOptions = function(req, callback){
  var corsOptions;
  if(whitelist.indexOf(req.header('Origin')) !== -1){
    corsOptions = { origin: true }; // the requested origin in the CORS response matches and is allowed
  }else{
    corsOptions = { origin: false }; // the requested origin in the CORS response doesn't match, and CORS is disabled for this request 
  }
  callback(null, corsOptions); // callback expects two parameters: error and options 
};
 
// add the callback function to the cors parameter for the route /products/:id for a GET request
app.get('/products/:id', cors(corsDelegateOptions), function(req, res, next){
  res.json({msg: ''A whitelisted domain matches and CORS is enabled for route product/:id'});
});
 
app.listen(80, function(){
  console.log('CORS is enabled on the web server listening on port 80'');
});

Summary

We have learned important stuffs of applying CORS in Node.js. Let us have a qssuick recap of what we have learnt:

Node.js provides a web server built with JavaScript, and can be combined with many other JS frameworks as the application server.

Although some frameworks have specific syntax for implementing CORS, they all follow the CORS specification by specifying allowed origin(s) and method(s). More robust frameworks allow custom headers such as Content-Type, and preflight when required for complex CORS requests.

JavaScript frameworks may depend on the jQuery XHR object, which must be configured properly to allow Cross-Origin requests.

JavaScript frameworks are evolving rapidly. The examples here may become outdated. Always refer to the project documentation for up-to-date information.

With knowledge of the CORS specification, you may create your own techniques using JavaScript based on these examples, depending on the specific needs of your application. https://en.wikipedia.org/wiki/Node.js 

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here