Table of Contents
In the previous article, I have explained how to deploy a Node.js application to 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:
1npx create-react-app react-cors
Now update the App.js
with the following code:
App.js
1import { useEffect, useState } from "react"
2import "./App.css"
3
4function App() {
5 const [message, setMessage] = useState("")
6 useEffect(() => {
7 fetch("https://nodejs-using-github.herokuapp.com/")
8 .then(response => response.json())
9 .then(data => {
10 setMessage(data.message)
11 })
12 .catch(err => console.log(err))
13 }, [])
14 return <div className="App">{message ? message : "Loading.."}</div>
15}
16
17export 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:
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.
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,
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.
Once the project is cloned, open it in your code editor and install cors package.
Now open index.js
and update it with the following code:
index.js
1const express = require("express")
2const cors = require("cors")
3const app = express()
4const port = process.env.PORT || 3000
5
6const whitelist = ["http://localhost:3000"]
7const corsOptions = {
8 origin: function (origin, callback) {
9 if (!origin || whitelist.indexOf(origin) !== -1) {
10 callback(null, true)
11 } else {
12 callback(new Error("Not allowed by CORS"))
13 }
14 },
15 credentials: true,
16}
17app.use(cors(corsOptions))
18
19app.get("/", (req, res) => {
20 res.send({ message: "Hello World!" })
21})
22
23app.listen(port, () => {
24 console.log(`Example app listening at Port: ${port}`)
25})
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 and see if this works.
Now if you reload your page, you should be able to see the message.
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:
index.js
1const express = require("express")
2const cors = require("cors")
3const app = express()
4const port = process.env.PORT || 3000
5
6const domainsFromEnv = process.env.CORS_DOMAINS || ""
7
8const whitelist = domainsFromEnv.split(",").map(item => item.trim())
9
10const corsOptions = {
11 origin: function (origin, callback) {
12 if (!origin || whitelist.indexOf(origin) !== -1) {
13 callback(null, true)
14 } else {
15 callback(new Error("Not allowed by CORS"))
16 }
17 },
18 credentials: true,
19}
20app.use(cors(corsOptions))
21
22app.get("/", (req, res) => {
23 res.send({ message: "Hello World!" })
24})
25
26app.listen(port, () => {
27 console.log(`Example app listening at Port: ${port}`)
28})
Testing environment variables locally
To test environment variables locally, you can install the package called dotenv
:
Now create a file called .env
in the root directory of your project with the domains:
1CORS_DOMAINS = http://localhost:3000, http://localhost:3001, https://example.com
Update index.js
to use the dotenv
package:
index.js
1const express = require("express")
2const cors = require("cors")
3const app = express()
4const port = process.env.PORT || 3000
5
6if (process.env.NODE_ENV !== "production") {
7 require("dotenv").config()
8}
9
10const domainsFromEnv = process.env.CORS_DOMAINS || ""
11
12const whitelist = domainsFromEnv.split(",").map(item => item.trim())
13
14const corsOptions = {
15 origin: function (origin, callback) {
16 if (!origin || whitelist.indexOf(origin) !== -1) {
17 callback(null, true)
18 } else {
19 callback(new Error("Not allowed by CORS"))
20 }
21 },
22 credentials: true,
23}
24app.use(cors(corsOptions))
25
26app.get("/", (req, res) => {
27 res.send({ message: "Hello World!" })
28})
29
30app.listen(port, () => {
31 console.log(`Example app listening at Port: ${port}`)
32})
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:
Go to your project settings and click on «Reveal Config Vars». Now you can provide the key and values here and click on «Add»
Once added, you can push your changes and see if the changes work.
If you have liked article, stay in touch with me by following me on twitter.
I don’t believe this is a bug so I removed the template.
I have an up-to-date create-react-app application and my database environment is based around horizon/rethinkdb.
I’m trying to log in using github as my OAuth provider which I can successfully do if express is serving the client.
The issue happens when create-react-app is serving the client. Unfortunately, I’m using HTTPS so the «proxy» won’t work for me.
XMLHttpRequest cannot load https://localhost:8181/horizon/auth_methods.
Response to preflight request doesn't pass access control check:
The 'Access-Control-Allow-Origin' header contains the invalid value ''.
Origin 'https://localhost:3000' is therefore not allowed access.
Horizon provides a solution for the previous issue; however, the following issue has not been resolved: rethinkdb/horizon#765
https://localhost:8181/horizon/auth_methods.
Request header field X-Requested-With is not allowed
by Access-Control-Allow-Headers in preflight response.
All I want to do is bypass CORS in the development Environment. I have tried many suggestions that I’ve found from the docs here, but also from around the web including the npm package Cors and even installing a Chrome Extension to bypass CORS. Nothing has worked.
Here’s a sample of the express app minus any additions for CORS.
const express = require('express');
const https = require('https');
const path = require('path');
const fs = require('fs');
const horizon = require('@horizon/server');
const app = express();
const PORT = process.env.PORT || 8181;
const options = {
key: fs.readFileSync(path.resolve(__dirname, './config/tls/horizon-key.pem')),
cert: fs.readFileSync(path.resolve(__dirname, './config/tls/horizon-cert.pem'))
};
const server = https.createServer(options, app);
server.listen(PORT, function() {
console.log('Express server running at localhost:' + PORT);
});
const horizon_server = horizon(server, {
project_name: 'react_horizon',
permissions: false,
auth: {
token_secret: "Some_Secret_Code"
},
access_control_allow_origin: '*'
});
horizon_server.add_auth_provider(horizon.auth.github, {
path: 'github',
id: 'xxxxxxxxxx',
secret: 'xxxxxxxxxxxxxxxxxxxxxxxx',
});
One problem we often face as frontend developers is dealing with CORS when making API requests.
What is CORS?
CORS (Cross-Origin Resource Sharing) is essentially a mechanism that allows a server to express what other domains can make requests to it. For example, my app could be hosted on http://domain-a.com
, so only requests made from that same domain are allowed. If another app hosted on http://domain-b.com
tried to make a request to http://domain-a.com
, it would fail depending on the CORS policy.
Where does CRA fit in?
While using CRA (create-react-app), I’ve often run into a situation where I want to test my local changes against another team’s API endpoint. I mean, there’s only so much mocking you can rely on! By default, CRA runs locally on http://localhost:3000
, so if I try to make an API request out to http://domain-a.com/users.json
, CORS would block it. However, when developing locally, CRA lets us get around this by proxying all unknown requests to a specified domain. This can now help us make sure our frontend code lines up with the backend’s responses.
What’s the workaround?
All we need to do is add one new field in package.json
. For example:
"proxy": "http://domain-a.com",
After you restart your CRA dev server, you should now be free to make requests to http://domain-a.com/users.json
. This will of course only work locally and you should only make requests to a dev API endpoint, not production. That’s it!
Want to see more?
I mainly write about real tech topics I face in my everyday life as a Frontend Developer. If this appeals to you then feel free to follow me on Twitter: https://twitter.com/cmacdonnacha
Bye for now 👋
CORS actually stands for Cross-Origin Resource Sharing and is a mechanism that allows services to communicate with each other. You must have come across the infamous error that bothered almost every single person dabbling with web development. I came across it as well and was utterly frustrated with it.
SOP
Same Origin Policy (SOP) is a mechanism implemented by modern web browsers that block your application from requesting data that lives on a different URL. If you make a request that lives on a different URL or origin, the browser will block this data from being shared in your application. And to make matters worse it will throw these really vague errors that make it almost impossible for newbies to debug.
Client and Server
I have used the demo example of the express server taken from the official documentation. The application is running on port 4000 and we have only one route / that responds with a string ‘Hello World!’
const express = require('express')
const app = express()
const port = 4000
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
On the client side, we are using a freshly generated React project that makes a request to our server and logs the output to the console. We’re using axios to help us make that request.
import axios from 'axios'
function App() {
const fetchData = async () => {
const resp = await axios.get('http://localhost:4000')
console.log(resp)
}
fetchData()
return (
<>
</>
);
}
export default App;
But when you check the console of your client application you can see that some things didn’t go exactly as planned.
Does the screenshot below look familiar to you? You most likely came across it when trying to write a React application that interacts with your server
If yes then keep on reading this article as we’ll quickly get rid of it. Let’s start by translating the error into plain English so that we could be on the same page when it comes to understanding why the applied fix works.
Origin, in this case, is defined as a combination of URI, hostname and port in the following format SCHEME://HOSTNAME:PORT. In the example of my application, my client is running on
http://localhost:3000
but my server is running at
http://localhost:4000
Solution
The solution to this problem needs to be implemented on the server. We will make use of the CORS middleware to help us with that. Let’s start by installing it on our server. Run the following command in the root directory of your server application
$ npm install cors
We can now go ahead and use it in our application.
const express = require('express')
const app = express()
const cors = require('cors')
const port = 4000
app.use(cors())
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
Now restart the server and refresh your client in the browser, you will see that the error went away and the response is printing in the console as expected.
In our case, we have taken the easy way out and enabled CORS requests across all of the routes in our application. If you’re working on a slightly more complicated project then you should take a look at the official documentation and check for yourself on how to best handle this error.
Hello! I’m Dawid. I’m a full-stack developer with a couple of years of experience under my belt. I spent most of my career working with React. Now it’s time to pass the knowledge onto somebody else!