Logo
Published on

How to Enable CORS in Node.js Without Express CORS Middleware

Introduction

It's very easy to simply install the cors middleware to handle all the CORS stuff while using Node.js as your backend. This, however, leaves you with a very superficial understanding of how the Cross-Origin Resource Sharing mechanism works.

In this article, we will add CORS to a very simple http server in Node.js without using Express or any other middleware.

Table of Contents

Create a Node.js HTTP Server

As always, you first need to init a node app inside your project folder.

$ mkdir learn-cors
$ cd learn-cors
$ npm init --yes
$ touch index.js

This will create a package.json file inside the learn-cors directory on your PC.

Now open the folder in the text editor of your choice. I use Visual Studio Code.

$ code .

The package.json will look something like this by default.

{
  "name": "learn-cors",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

You can see that this is simply a regular JSON file that stores some information about your project. You don't really need to do much here except change the scripts field.

In scripts, delete the test field and replace it with serve. It should look like this after the change.

{
  ...
  "scripts": {
    "serve": "node index.js"
  }
}

This will allow you to simply run your server using the npm run serve command in the terminal.

Now, open the index.js file where the meat of our code will live. Put the following code in there.

const http = require('http')
const port = 8080

http
  .createServer((req, res) => {
    const headers = {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'OPTIONS, POST, GET',
      'Access-Control-Max-Age': 2592000, // 30 days
      /** add other headers as per requirement */
    }

    if (req.method === 'OPTIONS') {
      res.writeHead(204, headers)
      res.end()
      return
    }

    if (['GET', 'POST'].indexOf(req.method) > -1) {
      res.writeHead(200, headers)
      res.end('Hello World')
      return
    }

    res.writeHead(405, headers)
    res.end(`${req.method} is not allowed for the request.`)
  })
  .listen(port)

Adding Headers To The Response

If you look through the flow, you'll see that I'm simply creating an HTTP server which will handle the incoming request to the server.

To keep things simple, we will not handle routes and other method types other than GET and POST because that is out of the scope of this article.

const headers = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Methods': 'OPTIONS, POST, GET',
  'Access-Control-Max-Age': 2592000, // 30 days
  /** add other headers too */
}

The headers variable will store a Javascript Object containing the standard HTTP Access-Control-*. As I mentioned in the comment too, you can always add other things to the header too. To make CORS work correctly, you need at least these three which I have added to the headers variable.

The Access-Control-Allow-Origin field tells the browser about the domains from where the requests should be accepted. The * denotes that any domain is allowed. You'll probably want to set it to your website URL.

The Access-Control-Allow-Methods field tells the browser which request method this server supports. You can customize this one to your liking.

The Access-Control-Max-Age header is the number of seconds for which the pre-flight request's response is to be stored in the browser cache.

You can now try to run the server as follows.

$ npm run serve

You can hit the endpoint using curl as follows:

curl http://localhost:8080

The response will be:

Hello World

This request worked perfectly and didn't have any CORS error. This is because the request to the http://localhost:8080 was a simple GET request. In the simple requests, you are only allowed to use the GET, POST, and HEAD HTTP methods.

Another limitation of a simple request is that they don't allow you to send any forbidden headers in the request. One among the forbidden (unsafe) headers is the Authorization header.

If you have ever dealt with making APIs, you know that the Authorization header is very useful for user validation and authorization. But, with these simple requests, you can't make a request to the server which has the authorization header in the request from the client.

You'll get the following error in the browser console if you add the Authorization header to your request:

Failed to load http://localhost:8080 Response to preflight request didn't pass access control check: No 'Access-Control-Allow-Origin' header is present in the requested resource. Origin http://localhost:8080 is therefore not allowed to access. The response had HTTP status code 405. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

Preflight Requests

Along with the simple requests, we also have preflight requests. I

Whenever you send a non-simple request, the browser sends a preflight request to the server. This request checks the server with the OPTIONS request method to see what headers are allowed in the request.

Based on the response to this OPTIONS request, the browser decides whether the request is safe to send.

The conditions for a preflight request are as follows:

The request method is among the following:

  • PUT
  • DELETE
  • CONNECT
  • OPTIONS
  • TRACE
  • PATCH

OR, if your request to the server has one or more of these headers:

  • Accept-Charset
  • Accept-Encoding
  • Access-Control-Request-Headers
  • Access-Control-Request-Method
  • Connection
  • Content-Length
  • Cookie
  • Cookie2
  • Date
  • DNT
  • Expect
  • Host
  • Keep-Alive
  • Origin
  • Referer
  • TE
  • Trailer
  • Transfer-Encoding
  • Upgrade
  • Via

OR, if the Content-Type in the request header is anything other than:

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

To handle all this, you can simply add a check for the request method in your server.

In the index.js, the if(/** clause */) which checks if the req.method is OPTIONS handles this. It simply writes the headers to the response and results in a 204 (No Content) to the browser.

if (req.method === 'OPTIONS') {
  res.writeHead(204, headers)
  res.end()
  return
}

When the browser sends a preflight request to your server, you can set the headers in the OPTIONS request and end the response with 204 code. From now, whenever the browser sends the request, your server will handle it perfectly.