How to solve CORS error in Node.js and React applications
Tags: node, react, cors
Published: 2022-07-16 15:09:55

In the previous article, I have explained [how to deploy a Node.js application to Heroku](https://www.codingdeft.com/posts/deploy-nodejs-heroku/). In this tutorial, we will be making use of the endpoint created there and see if we can use it in our React project.

## Project Setup

Let's create a React project using the following command:

```
npx create-react-app react-cors
```

Now update the `App.js` with the following code:

```jsx
import { useEffect, useState } from "react"
import "./App.css"

function App() {
  const [message, setMessage] = useState("")
  useEffect(() => {
    fetch("https://nodejs-using-github.herokuapp.com/")
      .then(response => response.json())
      .then(data => {
        setMessage(data.message)
      })
      .catch(err => console.log(err))
  }, [])
  return <div className="App">{message ? message : "Loading.."}</div>
}

export default App
```

Here we have a local state called `message`, which we show to the user. If the message is empty, then we display them with a loading text. When the component is mounted (useEffect), we make a call to the API endpoint and fetch the message.

Now let's run this and see if it works:

```bash
npm start
```

![loading](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cbp38g2p63fcwutkxo7k.png)
 
You will see that only "Loading.." text is displayed and the message never loads. If we inspect the page and see the console, we will see the following error:

`Access to fetch at 'https://nodejs-using-github.herokuapp.com/' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.`

![cors](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zhqyqezdyemc2s7eraj4.PNG) 

In the next sections, we will see what is CORS and how to fix this error.

## What is CORS (Cross-Origin Resource Sharing)?

CORS stands for [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS), which is an HTTP header based mechanism that helps the server to tell the browser, from which all domain requests can be made (except the same domain).

That is, in our case, the Node.js server hosted at `https://nodejs-using-github.herokuapp.com/`, does not tell the browser that request can be made from `http://localhost:3000`.

When this happens, your browser will throw an error as seen earlier.

## Why CORS (Cross-Origin Resource Sharing)?

The next question that would come to your mind is why do we really need this mechanism. Imagine you are logged into your bank account or any social media website, then you visit a malicious website.
This malicious website could run some scripts in the background to make API calls to your banking or social media to get your personal details.

To prevent this, your browser checks if the request to the banking or social media server can be made from the malicious website and throws the CORS error.

So CORS exists to share certain resources between trusted third-parties (across different origins/domains), hence the name Cross-Origin Resource Sharing.

## How to configure CORS in Node.js

Since we are clear about what and why is CORS required, let's see how to enable CORS in the Node.js application.

You may clone the Node.js [code from this repo](https://github.com/collegewap/nodejs-using-github). Once the project is cloned, open it in your code editor and install [cors package](https://www.npmjs.com/package/cors).

```bash
npm i cors
```

Now open `index.js` and update it with the following code:

```js
const express = require("express")
const cors = require("cors")
const app = express()
const port = process.env.PORT || 3000

const whitelist = ["http://localhost:3000"]
const corsOptions = {
  origin: function (origin, callback) {
    if (!origin || whitelist.indexOf(origin) !== -1) {
      callback(null, true)
    } else {
      callback(new Error("Not allowed by CORS"))
    }
  },
  credentials: true,
}
app.use(cors(corsOptions))

app.get("/", (req, res) => {
  res.send({ message: "Hello World!" })
})

app.listen(port, () => {
  console.log(`Example app listening at Port: ${port}`)
})
```

Here we check if the origin (client's domain) is in the whitelist, then we tell the clients that requests can be made. If it is not in the list then we throw an error saying the client is not allowed to make CORS requests to this server.

> The domain should not have any trailing slashes (/)

We can [deploy the changes to Heroku](https://www.codingdeft.com/posts/deploy-nodejs-heroku/) and see if this works.

Now if you reload your page, you should be able to see the message.

![cors success](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5wz67bf3phaakqamrzap.png)

You will also see that a response header called `Access-Control-Allow-Origin` has been added with the value `http://localhost:3000`.

## Making CORS domains configurable

If you have multiple client origins to be connected to you, and you want them to be configurable, you can do so by using environment variables:

```js
const express = require("express")
const cors = require("cors")
const app = express()
const port = process.env.PORT || 3000

const domainsFromEnv = process.env.CORS_DOMAINS || ""

const whitelist = domainsFromEnv.split(",").map(item => item.trim())

const corsOptions = {
  origin: function (origin, callback) {
    if (!origin || whitelist.indexOf(origin) !== -1) {
      callback(null, true)
    } else {
      callback(new Error("Not allowed by CORS"))
    }
  },
  credentials: true,
}
app.use(cors(corsOptions))

app.get("/", (req, res) => {
  res.send({ message: "Hello World!" })
})

app.listen(port, () => {
  console.log(`Example app listening at Port: ${port}`)
})
```

### Testing environment variables locally

To test environment variables locally, you can install the package called `dotenv`:

```bash
npm i dotenv
```

Now create a file called `.env` in the root directory of your project with the domains:

```.env
CORS_DOMAINS = http://localhost:3000, http://localhost:3001, https://example.com
```

Update `index.js` to use the `dotenv` package:

```js
const express = require("express")
const cors = require("cors")
const app = express()
const port = process.env.PORT || 3000

if (process.env.NODE_ENV !== "production") {
  require("dotenv").config()
}

const domainsFromEnv = process.env.CORS_DOMAINS || ""

const whitelist = domainsFromEnv.split(",").map(item => item.trim())

const corsOptions = {
  origin: function (origin, callback) {
    if (!origin || whitelist.indexOf(origin) !== -1) {
      callback(null, true)
    } else {
      callback(new Error("Not allowed by CORS"))
    }
  },
  credentials: true,
}
app.use(cors(corsOptions))

app.get("/", (req, res) => {
  res.send({ message: "Hello World!" })
})

app.listen(port, () => {
  console.log(`Example app listening at Port: ${port}`)
})
```

Here we made sure that `.env` files are loaded only in non-production environments. It is recommended to store the configurations in the server host rather than in .env files for production.

> Remember to add `.env*` to the `.gitignore` file so that you don't accidentally push them to the repo.

## Configuring environment files in heroku

With our latest code, we can configure environment files in the heroku settings:

![heroku env](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/86s0pxigap43kvac2dm6.png)
 

Go to your project settings and click on "Reveal Config Vars". Now you can provide the key and values here and click on "Add"

![heroku config vars](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5xkgxhxo3wvqrfasyili.PNG)

Once added, you can push your changes and see if the changes work.