DEV Community

Lou (🚀 Open Up The Cloud ☁️)
Lou (🚀 Open Up The Cloud ☁️)

Posted on • Updated on

Using Create-React-App with Express

Whilst setting up a test app myself, I couldn't find a simple way to deploy Create React App with Express on the same server. It took some tweaking, so here are the steps if you want to do the same.

Please note: These steps assume you want to run your app server and your API's from the same place. This is useful if you want to deploy simply to something like heroku.

Read this if you've not worked with create-react-app before: If you've not yet worked with create-react-app it has two modes of serving: from a hot-reloader which is launched with npm run start and an optimised production bundle which is a standard index.html that you can serve in any way you desire. I wanted a way to have the npm run start method and the npm run build method to work in the same way with my API, one way to do this is with the proxy setup I'm about to take you through.

Step 1: Install create-react-app

    create-react-app your-app-name
Enter fullscreen mode Exit fullscreen mode

Step 2: Install packages for create react app

 npm install; 
Enter fullscreen mode Exit fullscreen mode

Step 3: Install express

npm install express --save
Enter fullscreen mode Exit fullscreen mode

Step 4: Create a server.js file

const express = require('express');
const bodyParser = require('body-parser')
const path = require('path');
const app = express();
app.use(express.static(path.join(__dirname, 'build')));

app.get('/ping', function (req, res) {
 return res.send('pong');
});

app.get('/', function (req, res) {
  res.sendFile(path.join(__dirname, 'build', 'index.html'));
});

app.listen(process.env.PORT || 8080);
Enter fullscreen mode Exit fullscreen mode

Step 5: Update your package.json

Add the following to your package.json

"proxy": "http://localhost:8080"
Enter fullscreen mode Exit fullscreen mode

If you didn't do this we would have to create slow production builds every time (rather than the faster for development npm run start method). This is because npm start uses port 3000, which is not the same port that the express APIs are running on (8080).

Step 6: Start the express server

node server.js
Enter fullscreen mode Exit fullscreen mode

Or nodemon if you prefer.

Step 7: Start your react app

Keep node running, do this in a separate tab/ window.

npm start 
Enter fullscreen mode Exit fullscreen mode

Start the react build with hot reloading.

Conclusion

Now you can develop all you want on localhost:3000 by using npm run start and your API's will work as expected (despite requests coming from port 3000).

When you want to deploy, just run the production build npm run build and serve your app from localhost:8080, which is node server.js in this example (note the port number at the bottom of server.js).

Profit.


Lou is the editor of The Cloud Native Software Engineering Newsletter a Newsletter dedicated to making Cloud Software Engineering more accessible and easy to understand, every 2 weeks you’ll get a digest of the best content for Cloud Native Software Engineers right in your inbox.

Oldest comments (35)

Collapse
 
sammyisa profile image
Sammy Israwi

Quick question! If you don't want to develop and test the React app and the API at the same time, is the proxy step necessary?

Also, how would API requests look like on the React app? With or without proxy.

Collapse
 
loujaybee profile image
Lou (🚀 Open Up The Cloud ☁️) • Edited

Hey Sammy!

All of the requests are for absolute paths. So for the ping example would just be:

/ping

Then node works out to send it to the right place. This maps the create react app on port 3000 to the api requests on 8080.

If you want them on a separate server you'll need to call with the port number, or domain that you setup your api server on.

Like so:

yourdomain:8080/ping

With that option, however you'll neeed to fiddle with your API access control. So that you only allow access to your app, not the whole internet. However, this can be more tricky and fiddly and depends a lot on how you setup your architecture.

Collapse
 
ayanez17 profile image
ayanez17

What exactly does this mean?
"When you want to deploy, just run the production build and serve your app from localhost:8080 and everything will work as planned." ?
The Production build runs on PORT 5000 should we change that to run on port 8080?

Collapse
 
loujaybee profile image
Lou (🚀 Open Up The Cloud ☁️)

Hey, ayanez17.

There are two modes your app can run in. When built (port 8080) via npm run build (which creates an index.html) or when running live reload npm run start which runs on port 3000.

When running npm run start your app will proxy from port 3000 to port 8080 automatically. However when you create a production build your entire app should be running from port 8080, as express and the API's are running from the same server (in this setup).

You shouldn't need to change any ports. Does that help?

Collapse
 
tanobi92 profile image
Ta No Bi

Hi @Lou, when i deploy to server, it had an error: Error: ENOENT: no such file or directory, stat '/home/namld/projects/raca/build/index.html'. How to fix it?

Collapse
 
zawadzkip profile image
Patrick Zawadzki

make sure you run npm run build before, otherwise you will not have a build directory

Collapse
 
loujaybee profile image
Lou (🚀 Open Up The Cloud ☁️)

Patrick's suggestion sounds correct, let me know if you had any other issues.

Collapse
 
zawadzkip profile image
Patrick Zawadzki

Is it possible to serve the "public/index.html" instead of "build" for development, edit my react files, and still have the site on localhost:8080 catch the updates?

Collapse
 
loujaybee profile image
Lou (🚀 Open Up The Cloud ☁️)

The public/index.html is the (slower) build script. I'm not quite sure why you'd want to do that. Running on port 3000 is just better for speed of local dev. If you can elaborate a little more, maybe I can help!

Collapse
 
fxalvarezd profile image
fxalvarezd

hey Lou, thanks for the article.
I'm trying this to see if I can get some environment variables to my static app, but I can't seem to get it to work. I'm assuming I have to pass them in to the client some how.
Do you have any ideas?

thx

Collapse
 
loujaybee profile image
Lou (🚀 Open Up The Cloud ☁️)

I'm a little confused, environment variables are typically for configuring your back end rather than exposing anywhere else. Could you elaborate on what you're trying to achieve?

Collapse
 
fxalvarezd profile image
fxalvarezd

I don't remember exactly what I was trying to achieve, but an example I can think of is hitting endpoints in different dev environments. For example:
GET => dev.mydomain.com/myendpoint
GET => qa.mydomain.com/myendpoint
GET => mydomain.com/myendpoint

I would want to consume the same environment variables that my Microservices use, instead of creating a hardcoded copy in my JS code.

Hope it makes sense.

Collapse
 
tomsoderlund profile image
Tom Söderlund

Thank you for this article!

But how can I enable hot reloading in the CRA client when running node/nodemon?

Currently I'm stuck with this solution:

"scripts": {
  "devserver": "nodemon -w server -w package.json server/server.js",
  "devclient": "react-scripts start"
}
Collapse
 
j8298c profile image
Julio Mojica

Hey Tom you can use this package npmjs.com/package/concurrently and create a script for it to run both devserver and devclient at the same time for you.

Collapse
 
henryjw profile image
Henry Williams

Found this through a Google search. Thanks for this post!

Collapse
 
loujaybee profile image
Lou (🚀 Open Up The Cloud ☁️)

Awesome, glad to know Henry!

Collapse
 
rl4444 profile image
Rory L • Edited

Hey Lou, really love this simple version to add a backend to create react app. It's really cool.

I'm having some trouble deploying this to heroku. Is there a particular package.json change I need to make to get this to work? Everything works in development, but when deployed I get an 'invalid host error' rendering on the page with nothing else

My package.json looks like this. I also ran 'npm run build' before deploying.

{
  "name": "my-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "axios": "^0.19.0",
    "express": "^4.17.1",
    "radium": "^0.25.2",
    "react": "^16.8.6",
    "react-animations": "^1.0.0",
    "react-dom": "^16.8.6",
    "react-scripts": "3.0.1",
    "react-slick": "^0.24.0",
    "spiced-pg": "^1.0.0",
    "styled-components": "^4.3.2"
  },
  "engines": {
    "npm": "6.10.0",
    "node": "v10.16.0"
  },
  "proxy": "http://localhost:8080",
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}
Collapse
 
loujaybee profile image
Lou (🚀 Open Up The Cloud ☁️) • Edited

Given the popularity of the post, I should probably completely overhaul it. A few things have changed in create-react-app since.

Sounds like this though...

facebook.github.io/create-react-ap...

It's probably an issue with your proxy settings. CRA has changed a few bits with regards to proxies since, it's worth taking a look at their docs.

Collapse
 
kevinburton profile image
Kevin Burton

Would it be possible to provide a little context for the instruction to insert "proxy" into packages.json?

Collapse
 
jaspersurmont profile image
Jasper Surmont

Hey,
When I try to serve my production build app from localhost (here 8014) I just get a blank page. In the console i get the error message: GET localhost:8014/static/js/2.2612b9d... net::ERR_ABORTED 404 (Not Found)
Any idea?

Collapse
 
casal0x profile image
Sebastian Eduardo Casal

did you find a solution for this? i having the same problem.

Collapse
 
jaspersurmont profile image
Jasper Surmont

I did fix it, however I'm not so sure what exactly I did.
I think that one of the issues was that I didn't refer to the build folder correctly. Try messing around with that a bit

Collapse
 
bordenjardine profile image
BordenJardine • Edited

the express server doesn't know how to host the static files generated by created webpack.
put app.use(express.static('build')) to tell express where to look for them.
@Lou maybe this should be included in the article.

Collapse
 
loujaybee profile image
Lou (🚀 Open Up The Cloud ☁️)

Thanks @bordenjardine . The post does currently refer to the build folder:

app.use(express.static(path.join(__dirname, 'build')));

But depending on how you're referencing the build folder it may depend on where you're the server start commands from. Try as @jaspersurmont to ensure that you're correctly pointing to the build folder, and that it has at the least an index.html inside.

Collapse
 
thomasgauvin profile image
Thomas Gauvin

leaving this here for anyone that might be encountering the same issue! Here's how I fixed it, I just needed to add a route that matches any (*) that don't match exactly '/', and serve the react app:

app.get('/', function (req, res) {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
});

// Handles any requests that don't match the ones above
app.get('*', (req,res) =>{
res.sendFile(path.join(__dirname, 'build', 'index.html'));
});

Cheers!

Collapse
 
i3ats profile image
i3ats

(*) did the trick for me too :)

Collapse
 
nickangtc profile image
Nick Ang

Thanks for the tutorial! I just followed it successfully to setup a new project that uses CRA and Express.

Just to be clear for anyone who is confused, what's not been stated explicitly is that you should just load localhost:3000 when you're developing because that's your single page app front end. As long as you configure the proxy in package.json and created the server.js according to this article, you should never need to think about the fact that localhost:8080 is the API server when developing.

Hope this helps!

Collapse
 
loujaybee profile image
Lou (🚀 Open Up The Cloud ☁️)

Hey Nick! Glad to hear it worked out for you. I imagine the article is a little bit out of date now since it was written some years ago. I think what you mention is what I tried to address in the conclusion: dev.to/loujaybee/using-create-reac.... But hopefully your re-wording will help someone else who stumbles upon the article!

Collapse
 
bkrmsp profile image
Bikram Singh Patel

Hi, can you also help out to setup HTTPS with regards to this arcticle? I have tried to use export HTTPS=true as i am working on MAC. It works fine when i do npm start (i.e. development mode) but not when i started hosting the production build as per the steps you mentioned above.

Collapse
 
lisa profile image
Lisa • Edited

Hi,
Did you get a solution to this?

I tried creating a self-signed certificate by following these steps -

openssl genrsa -out key.pem
openssl req -new -key key.pem -out csr.pem
openssl x509 -req -days 9999 -in csr.pem -signkey key.pem -out cert.pem
rm csr.pem

And to my server.js file, I included the following code -

var express = require("express");
var https = require("https");
var fs = require("fs");
const path = require('path');
var app = express();
const options = {
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem')
};
var server = https.createServer(options, app);
app.use(express.static(__dirname));
app.get('/', function (req, res) {
res.sendFile(path.join(__dirname, 'build'));
});
server.listen(8000);

But this doesn't seem to work for me. When I open https://localhost:3000 it throws a "The site can't provide a secure connection". Can someone point out where I am going wrong?

Collapse
 
kingdopamine profile image
kingdopamine

This has been driving me insane, stayed up until 4 in the morning after 7 hours of pulling my hair out and I am still struggling today.

localhost/8080/pong works perfectly fine. As soon as run build and deploy to my web server the response is:
404 Not Found

I presume this is do with "just run the production build npm run build and serve your app from localhost:8080"

The casual nature of this comment makes me believe this is an obvious thing to be able to do, yet I have no idea how to change the server from localhost:3000 to local host 8080 when I run build.

I'm not even sure if this is what the issue is either.

Collapse
 
itsalb3rt profile image
Albert E. Hidalgo Taveras

Thanks!!!!

Collapse
 
lisa profile image
Lisa • Edited

Hey,

Great article! I would like to know how to set up HTTPS here.

I tried creating a self-signed certificate following the steps below,

openssl genrsa -out key.pem
openssl req -new -key key.pem -out csr.pem
openssl x509 -req -days 9999 -in csr.pem -signkey key.pem -out cert.pem
rm csr.pem

I'm adding the following code in my server.js

var express = require("express");
var https = require("https");
var fs = require("fs");
const path = require('path');
var app = express();​
const options = {
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem')
};
var server = https.createServer(options, app);​
app.use(express.static(__dirname));
app.get('/', function (req, res) {
res.sendFile(path.join(__dirname, 'build'));
});
server.listen(8000);

But this doesn't seem to work as when I try to open https://localhost:3000, it throws a "The site can't provide a secure connection". Could you please point out where exactly I am going wrong?