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:
- 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! - 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"
})
);
};
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 require
s 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 port3000
, so we set Express to listen to port3001
, 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 port3000
, 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"
}
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);
});
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
}
Here, we specify a couple of conditions for Glitch's watcher.
- 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. - 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 thethrottle
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. - 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.