Handling CORS with Node.js

Introduction

In this article, we are going to take a look at what CORS is, how you can configure CORS with Express, and how to customize the CORS middleware to your needs.

What is CORS

CORS is shorthand for Cross-Origin Resource Sharing. It is a mechanism to allow or restrict requested resources on a web server depend on where the HTTP request was initiated.

This policy is used to secure a certain web server from access by other website or domain. For example, only the allowed domains will be able to access hosted files in a server such as a stylesheet, image, or a script.

If you are currently on http://example.com/page1 and you are referring an image from http://image.com/myimage.jpg you won't be able to fetch that image unless http://image.com allows cross-origin sharing with http://example.com.

There is an HTTP header called origin in each HTTP request. It defines from where the domain request has originated. We can use header information to restrict or allow resources from our web server to protect them.

By default requests from any other origins will be restricted by the browser.

For example, while you are still in the development stage - if you are using a frontend library such as React, your front end application will be served on http://localhost:3000. Meanwhile, your Express server might be running on a different port such as http://localhost:2020.

Because of this, you'll need to allow CORS between those servers.

If you see this common error in your browser console. CORS restrictions could be the issue:

chrome cors

CORS is really useful when you're offering a public API and would like to controll the access to certain resources and how people use them.

Also, if you want to use your own API or files on a different web page you can simply configure CORS to allow that, while still blocking others out.

Configuring CORS with Express

Let's start off with a fresh project. We'll make a directory for it, enter it and run npm init with the default settings:

$ mkdir myapp
$ cd myapp
$ npm init -y

Then let's install the required modules. We'll be using express and the cors middleware:

$ npm i --save express
$ npm i --save cors

Then let's start creating an express web application with two routes to demonstrate how CORS works.

We'll make a file, called index.js that acts as a web server, with a couple of request handlers:

const express = require('express');
const cors = require('cors');

const app = express();

app.get('/', (req, res) => {
    res.json({
        message: 'Hello World'
    });
});

app.get('/:name', (req, res) => {
    let name = req.params.name;

    res.json({
        message: `Hello ${name}`
    });
});

app.listen(2020, () => {
    console.log('server is listening on port 2020');
});

Let's run the app and the server:

$ node index.js

Now, if you go to http://localhost:2020/ - the server should return a JSON message:

{
  "message": "Hello World"
}

Alternatively, if you go to http://localhost:2020/something you should see:

{
  "message": "Hello something"
}

Enable All CORS Requests

If you want to enable CORS for all the request you can simply use the cors middleware before configuring your routes:

const express = require('express');
const cors = require('cors');

const app = express();

app.use(cors())

......

This will allow all the routes to be accessed anywhere on the web if that is what you need. So in our example, both routes will be accessible for every domain.

For example, if our server is running on http://www.example.com and serves content such as images - we allow other domains, such as http://www.differentdomain.com to refer the content from http://www.example.com.

Thus, a web page on http://www.differentdomain.com can use our domain as a source for an image:

<img src="http://www.example.com/img/cat.jpg">

Enable CORS for a Single Route

But if you need a certain route to be accessible and not other routes, you can configure cors in a certain route as a middleware instead of configuring it to the whole app:

app.get('/', cors(), (req, res) => {
    res.json({
        message: 'Hello World'
    });
});

This will allow a certain route to be accessible by any domain. So in your case, only the / route will be accessible for every domain. The /:name route will be only accessible for the requests that initiated in the same domain as the API which is http://localhost:2020 in our case.

For example, if you try to send a fetch request to the / path from a different origin - it will be successful and you will get the Hello World message as the response:

fetch('http://localhost:2020/')
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(err => console.error(err));

You should see the response from the server is successfully logged into the console if you run this code:

{
    message: 'Hello World'
}

But if you try to access any other path other than the root path such as http://localhost:2020/name or http://localhost:2020/img/cat.png this request will be blocked by the browser:

fetch('http://localhost:2020/name/janith')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(err => console.error(err));

You should see the following error if you try to run this code in a different web app:

console error

Configure CORS with Options

You can also use the configuration options with CORS to customize this further. You can use configuration to allow a single domain or subdomains access, configure HTTP methods that are allowed such as GET and POST depending on your requirements.

Here is how you can allow a single domain access using CORS options:

var corsOptions = {
    origin: 'http://localhost:8080',
    optionsSuccessStatus: 200 // For legacy browser support
}

app.use(cors(corsOptions));

If you configure the domain name in the origin - the server will allow CORS from the configured domain. So the API will be accessible from http://localhost:8080 in our case and blocked for other domains.

If we send a GET request, accessing any path should work, since the options are applied at app-level, not at function-level.

So, if we run the following code and send a request from http://localhost:8080 to http://localhost:2020:

fetch('http://localhost:2020/')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(err => console.error(err));

// Or

fetch('http://localhost:2020/name/janith')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(err => console.error(err));

We're allowed to fetch the information from that application and domain.

You can also configure allowed HTTP methods if you'd like:

var corsOptions = {
    origin: 'http://localhost:8080',
    optionsSuccessStatus: 200 // For legacy browser support
    methods: "GET, PUT"
}

app.use(cors(corsOptions));

If we send a POST request from http://localhost:8080, it'll be blocked by the browser as only GET and PUT are supported:

fetch('http://localhost:2020', {
  method: 'POST',
  body: JSON.stringify({name: "janith"}),
})
.then(response => response.json())
.then(data => console.log(data))
.catch(err => console.error(err));

To see a full list of configuration options, please refer the official documentation.

Configuring Dynamic CORS Origins using a Function

If configurations do not satisfy your requirements, you can create your function to customize CORS.

For example, let's assume that you want to allow CORS sharing for .jpg files http://something.com and http://example.com:

const allowlist = ['http://something.com', 'http://example.com'];

    const corsOptionsDelegate = (req, callback) => {
    let corsOptions;

    let isDomainAllowed = whitelist.indexOf(req.header('Origin')) !== -1;
    let isExtensionAllowed = req.path.endsWith('.jpg');

    if (isDomainAllowed && isExtensionAllowed) {
        // Enable CORS for this request
        corsOptions = { origin: true }
    } else {
        // Disable CORS for this request
        corsOptions = { origin: false }
    }
    callback(null, corsOptions)
}

app.use(cors(corsOptionsDelegate));

The callback function will accept two parameters. The first one is an error where we passed null and the second one is options where we passed { origin: false }. The second parameter could be many options that are constructed using the request object from the Express request handler.

So a web app which is hosted on http://something.com or http://example.com would be able to refer an image with .jpg extension from the server as we have configured in our custom function.

So the following image attachment will be successful from either of these:

<img src="http://yourdomain.com/img/cat.jpg">

But the following attachment will be blocked:

<img src="http://yourdomain.com/img/cat.png">

Loading List of Allowed Origins from as Data Source

You can use also use a list of allowed domains from a database or using any backing data source to allow CORS:

var corsOptions = {
    origin: function (origin, callback) {
        // Loading a list of allowed origins from the database
        // Ex.. origins = ['http://example.com', 'http//something.com']
        database.loadOrigins((error, origins) => {
            callback(error, origins);
        });
    }
}

app.use(cors(corsOptions));

Conclusion

In this article, we've covered what CORS is and how you can configure it with Express. Then, we've set up CORS for all requests, for specific requests, added options and restrictions as well as defined a custom function for dynamic CORS configuration.

Author image
About Janith Kasun
Colombo, Sri Lanka Twitter