create-react-app and Express, together on Glitch

Angelo Stavrow - Feb 14 '20 - - Dev Community

Glitch is the fastest way to get an app or site up and running live on the web, but it runs apps on only one port. Sometimes you need more, like when you're building a React front end with create-react-app and the back-end with Express. Here's how to work around this constraint, with a proxy middleware and some port-switching logic!

The Problem

Remote development on Glitch has a lot of benefits — it's simply the fastest way to get an app or site up and running live on the web, abstracting away of a lot of the fiddly parts of web development. While this very much makes it a go-to for most ideas that I want to build, I will occasionally run into little speed-bumps that aren't really an issue in a typical local-development setup.

One such speed-bump is that Glitch only serves content on one port. This is typically not an issue, unless you're trying to run both a webpack development server for front-end work and a back-end server in the same project, at the same time — you get one port for serving resources, but both the front-end and back-end servers each want their own port! This is a common scenario when you're building your front end with create-react-app, and your back end with Express.

What's more — Glitch's watcher provides live previews of your changes to a file, but so does create-react-app's Webpack development server!

Ugh. Can't we all just get along?

(Spoiler: Yes. Yes, we can.)

The Solution

I ran into exactly this problem when our team built the web app for Capitol Records' Capitol Royale event in November.

When you create a Node app on Glitch based on the hello-express starter app, the starter project includes a server.js file that implements a basic Express server. This server handles routing to various parts of your app, and is set up to listen on port 3000.1

Similarly, if you use create-react-app to, uh, create a React app, react-scripts starts a webpack development server that, by default, also listens on port 3000.

So what happens when you're working on a React app, but you also want to add an Express backend?

Essentially, we got it working such that:

  1. If you set your start script to "npm run production" in package.json, it will build the React app and Express will serve the static bundle over port 3000. BUT!
  2. If you set your start script to "npm run development" in package.json, it will concurrently start the webpack dev server/watcher and the Express server. The latter will be listening on port 3001, but you don't need to change anything in your code because: proxies!

😲 WHAT IS THIS SORCERY‽

This works thanks to a couple of moving parts: a proxy that listens for requests on a certain port and forwards them to another, and a bit of scripting and server-side logic that checks for an environment variable to know which port to listen to. Let's dig in!

The proxy

Since we only have one port to work with, we want to watch for requests to some endpoints and forward them through a proxy to our backend server.

If you create src/setupProxy.js, React will register it automatically when you start the development server (details here). So, add this to your src/setupProxy.js file:

const proxy = require("http-proxy-middleware");

// This proxy redirects requests to /api endpoints to
// the Express server running on port 3001.
module.exports = function(app) {
  app.use(
    "/api",
    proxy({
      target: "http://localhost:3001"
    })
  );
};
Enter fullscreen mode Exit fullscreen mode

What React and the proxy are doing here, essentially, is working together to say, "okay, any request to /api isn't a request for a static asset, so pass it on to the target" — that target being the Express server. Don't forget to add http-proxy-middleware to your package.json file, since src/setupProxy.js requires it.

Fun fact! "Globbing" is a weird-sounding word, but what it means is that just adding "/api" in the proxy is enough to correctly route "/api/ping", "/api/user/:userid", etc., to the target — we don't have to add every possible route in the function, which makes our lives much easier.

The ports

With proxying in place, the port situation is less confusing now. However, Glitch will still only serve one port, so we have to do some switching based on what mode we're working in.

  • In development mode, the webpack dev server listens on port 3000, so we set Express to listen to port 3001, and proxy any requests to /api endpoints through as described above.
  • In production mode, there is no webpack dev server, so we set Express to listen to port 3000, and Express serves the static resources directly.

The switching

Depending on whether you npm run production or npm run development, different servers and/or proxies are started, and NODE_ENV is set to either production or development — you don't want to have to change this in an .env file.

Setting the value of NODE_ENV is best done in package.json's scripts:

"scripts": {
    "start": "npm run development",
    "development": "NODE_ENV=development concurrently --kill-others \"npm run client\" \"npm run server\"",
    "production": "npm run build && NODE_ENV=production npm run server",
    "client": "react-scripts start",
    "server": "node server/server.js",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  }
Enter fullscreen mode Exit fullscreen mode

What we're doing in the above scripts is defining a development and a production script.

In development, we set the NODE_ENV to development, and use the concurrently package to run both the front-end Webpack server (client) and Express server (server) scripts at the same time. This is like opening two terminals on your local machine, running npm run client in one, and npm run server in the other.

In production, we build the React app, set the NODE_ENV to production, and then start the Express server.

That works great! However, there's still the issue of telling Express which port to use in each mode — we want it to use port 3001 in development, and port 3000 in production. We handle this in server/server.js, where there's a bit of logic towards the end of the file that checks the value of NODE_ENV and sets the listener's port appropriately:

let port;
console.log("❇️ NODE_ENV is", process.env.NODE_ENV);
if (process.env.NODE_ENV === "production") {
  port = process.env.PORT || 3000;
  app.use(express.static(path.join(__dirname, "../build")));
  app.get("*", (request, response) => {
    response.sendFile(path.join(__dirname, "../build", "index.html"));
  });
} else {
  port = 3001;
  console.log("⚠️ Not seeing your changes as you develop?");
  console.log(
    "⚠️ Do you need to set 'start': 'npm run development' in package.json?"
  );
}

const listener = app.listen(port, () => {
  console.log("❇️ Express server is running on port", listener.address().port);
});
Enter fullscreen mode Exit fullscreen mode

Specifically, if NODE_ENV=development, the port Express listens on is set to 3001 and serving assets is delegated to the webpack dev server. We also print a message to the console, suggesting that if you're not seeing your changes as you develop, you might need to check that you've changed the start script in package.json to npm run development.

Likewise, if NODE_ENV=production, the port Express listens on is set to 3000 and a route is added to serve the static resources from the /build directory.

👀 Watching the Watcher

We've now got requests going where they need to go: in development mode, we're serving the webpack development server and proxying API requests to the Express server, and in production mode, we have the Express server handling both the API endpoints and serving the static resources. But we're not quite done yet!

There's one last thing we want to sort out with our project: file watching. Both the Webpack development server and Glitch watch for changes to files, and automatically update the live app as you type. This rapid feedback look is really handy for previewing your modifications, but we don't want the watchers to interfere with each other!

The Webpack watcher only kicks in when the project is in development mode, and watches for changes in the /src directory. We can't really reach in and change much there, but we don't need to — all we really need is to tell the Glitch watcher to only look at what's changing in the /server folder.

We do that by adding a special watch.json file in the project root:

{
  "install": {
    "include": [
      "^package\\.json$",
      "^\\.env$"
    ]
  },
  "restart": {
    "exclude": [
      "^public/",
      "^src/"
    ],
    "include": [
      "^server/",
      "^watch\\.json$"
    ]
  },
  "throttle": 100
}
Enter fullscreen mode Exit fullscreen mode

Here, we specify a couple of conditions for Glitch's watcher.

  1. We only want to run install scripts when changes are made to the package.json and .env files. Installation can take a while, so we don't want to trigger it with any other changes.
  2. We only want to restart the project when changes are made in the /server folder, or to the watch.json file. We're including watch.json in case we need to kick off a restart — a change to the throttle value will trigger this. We're also explicitly ignoring any files in the /public and /src directories from kicking off a restart — we only want the Webpack watcher to handle these files.
  3. We're setting a throttle of 100, which means that the Glitch watcher will wait 100 milliseconds before restarting anything. If that seems too quick, you can increase it.

And with those changes, we're ready to go! If you want a one-click solution, remix this starter app to get going:

Add API routes in server/server.js (I've started you off with a GET route to /api/ping for checking that the server is up) and build your React app in the src directory (the create-react-app starter app you know and love is already there for you to start hacking on).

Have fun, and don't forget to show us what you build!


1 There's some subtlety to port handling in Glitch that, for the purposes of this article, I'm going to skip explaining. Long story short, Glitch does some work in the background and actually serves apps running on a couple of ports; the default ~hello-node project's Express server uses 3000, but ports 8000 and 8080 would also work, for example.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .