6

The title says it all. I've built a minimal working example here: https://github.com/sehailey/proxytest

I've tried so may things I've lost count (though they're stored in the commits). I promise once I get an answer to follow up on the dozens of answered threads asking this question.

Sarah Hailey
  • 494
  • 5
  • 19

1 Answers1

7

Looks like they changed how the create-react-app utilizes a proxy. Remove the proxy from the package.json. Then...

Add this package:

npm i -S http-proxy-middleware

Then create a setupProxy.js in src:

src/setupProxy.js

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

module.exports = app => {
  app.use(proxy("/api/*", { target: "http://localhost:5000/" }));
};

Now from inside the React component, you can do this:

src/App.js

import React, { Component } from "react";
import logo from "./logo.svg";
import "./App.css";

export default class App extends Component {
  state = {
    message: "",
    error: "",
    eee: "",
    text: ""
  };

  componentDidMount = () => this.fetchAPIMessage();

  fetchAPIMessage = async () => {
    try {
      const res = await fetch(`/api/message`);
      const { message } = await res.json();
      this.setState({ message });
    } catch (err) {
      console.error(err);
    }
  };

  render = () => (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>WELCOME CREATE REACT APP!</p>
        <div className="App-link">{this.state.message}</div>
      </header>
    </div>
  );
}

index.js (I added npm i -D morgan which is a handy logging framework -- when a request hits the API, it displays it in the console).

const path = require("path");
const express = require("express");
const app = express();
const morgan = require("morgan");

app.use(morgan("tiny")); // logging framework

// Serve our api message
app.get("/api/message", async (req, res, next) => {
  try {
    res.status(201).json({ message: "HELLOOOOO FROM EXPRESS" });
  } catch (err) {
    next(err);
  }
});

if (process.env.NODE_ENV === "production") {
  // Express will serve up production assets
  app.use(express.static("build"));

  // Express will serve up the front-end index.html file if it doesn't recognize the route
  app.get("*", (req, res) =>
    res.sendFile(path.resolve("build", "index.html"))
  );
}

// Choose the port and start the server
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Mixing it up on port ${PORT}`));

package.json (use node to serve production assets -- see "start" script)

{
  "name": "proxytest",
  "version": "0.1.0",
  "private": true,
  "homepage": "https://proxytest2.herokuapp.com/",
  "dependencies": {
    "concurrently": "^4.0.1",
    "express": "^4.16.4",
    "http-proxy-middleware": "^0.19.0",
    "react": "^16.5.2",
    "react-dom": "^16.5.2",
    "react-scripts": "2.0.5",
    "serve": "^10.0.2"
  },
  "scripts": {
    "start": "NODE_ENV=production node index.js",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "client": "react-scripts start",
    "server": "nodemon server",
    "eject": "react-scripts eject",
    "dev": "concurrently --kill-others-on-fail \"npm run server\" \"npm run client\"",
    "heroku-postbuild": "npm run build"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": [
    ">0.2%",
    "not dead",
    "not ie <= 11",
    "not op_mini all"
  ]
}

What you'll see in the console when running in production:

m6d@m6d-pc:~/Desktop/proxytest-master$ npm start

> proxytest@0.1.0 start /home/m6d/Desktop/proxytest-master
> NODE_ENV=production node index.js

Mixing it up on port 5000
GET / 200 2057 - 6.339 ms
GET /static/css/main.068b9d02.chunk.css 304 - - 1.790 ms
GET /static/js/1.9a879072.chunk.js 304 - - 0.576 ms
GET /static/js/main.e3ba6603.chunk.js 304 - - 0.605 ms
GET /api/message 201 36 - 4.299 ms
GET /static/media/logo.5d5d9eef.svg 304 - - 0.358 ms

Other notes:

  • Make sure to separate your client src from the API (for example, put everything from the front-end React application into a client folder with its own dependencies).
  • Remove any and all React dependencies from your API's package.json
Matt Carlotta
  • 18,972
  • 4
  • 39
  • 51
  • fantastic answer, nice work and thanks! I'll be sharing with the dozens of others and hope it can save someone else some frustration int the future. – Sarah Hailey Oct 17 '18 at 02:27
  • So, I am using localhost:3000 to host my CRA client and localhost:3001 to host my Express Server. Adding `src/setupProxy.js` and pointing to `app.use(proxy("/api/*", { target: "http://localhost:3001/" }));` in my client/src folder is not working at the moment. Any ideas? – Ian Leatherbury Oct 25 '18 at 14:11
  • Since I don't have your code, I can't say for sure where your problem lies. – Matt Carlotta Oct 25 '18 at 14:38
  • I am actually experiencing the same problem on Azure: https://stackoverflow.com/questions/52970371/react-express-on-azure-invalid-host-header – Ian Leatherbury Oct 25 '18 at 15:22
  • Does this answer work at all? How does setupProxy.js get used? I was referred to this answer, but it doesn't help in my case, https://stackoverflow.com/questions/53056212/why-is-my-react-router-not-passing-my-express-route-to-server – newman Oct 30 '18 at 02:41
  • @MattCarlotta when you do `proxy("/api/*"...)` instead api I can just put `proxy(" /* ", ...)`? and the target is front-end url or back-end? – Catarina Oct 08 '19 at 14:25
  • 1
    The proxy should point to the backend. In the example above, the backend is served on port 5000 (`http://localhost:5000/api`). In production, when everything is served on a single port, using just `/` can interfere with client side routes if they share the same route name -- which is why I append all backend routes with `/api`. – Matt Carlotta Oct 08 '19 at 14:37
  • the setupProxy has to be in the server right? can we just put the proxy in the first lines of the server? your example is not working for me – VersifiXion Dec 29 '19 at 19:03
  • Negative. The `create-react-app` has been (or was at the time) automatically set up to look for a `setupProxy.js` within the root of the `src` folder (similar to `setupTests.js`). – Matt Carlotta Dec 29 '19 at 20:26
  • hi, in your server you do a "if (process.env.NODE_ENV === "production") but when I console log this, I always have undefined, my NODE_ENV env variable is always at undefined, but I did like you, do you know what can create his issue ? – VersifiXion Dec 31 '19 at 15:57
  • You have to set it in your `package.json` scripts (see `start` example above). – Matt Carlotta Dec 31 '19 at 18:25
  • @MattCarlotta please when you'll have 5mins, can you check my topic ? I did like you did but 2 days I'm struggling with this.. https://stackoverflow.com/questions/59554351/issue-when-deploying-with-heroku-error-504?noredirect=1#comment105277329_59554351 – VersifiXion Jan 01 '20 at 17:55
  • Will take a look at it over the weekend. – Matt Carlotta Jan 02 '20 at 01:25