Search code examples
expressherokupostmanreact-router-dom

GET request returning html not object, route returning cannot get in Heroku server


I have a react app, using Postman for testing, to get HTTP requests runing on a Heroku server with client and server in a single root folder.

After deployment, I have run into a problem with the react-router on the Heroku server, where I get a CANNOT Get error after refreshing a page or manually entering the endpoint. To solve this error I added a wild card endpoint to redirect the route back to index.html in server.js (code below).

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

Once the above error was fixed I have run into another problem. I am now unable to get the JSON data via the GET request, and instead in Postman html.index is returned. To solve this problem I removed the code above, however, this solution revolves back to the first error and I am stuck in a circular loop, of fixing the first error and dealing with the second error.

I believe I can write an if statement that checks the route to redirect to index.html only when needed and this is what I have tried below, but it doesn't work and I am not sure how to write out the if statement to fix these two revolving errors.

app.get("*", (req, res) => {
  let url = path.join(__dirname, '../client/build', 'index.html');
  if (!url.startsWith('/app/')) // <-- not sure what to replace here to make code work
    url = url.substring(1);
  res.sendFile(url);
});

How can I fix these two errors with an if statement or another solution dealing with react-router and the Heroku server?

my code server.js

const mongoose = require("mongoose");
const path = require("path");
const express = require("express");
const app = express();
const cors = require("cors");
const logger = require("morgan");
const PORT = process.env.PORT || 3001;

require("./models/CalanderData"); 
const router = require("./routes/routes");

app.get("/api", (req, res) => {
  res.json({ message: "Hello from server!" });
});

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

app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors());
app.use(logger("dev"));

app.get("*", (req, res) => {
  let url = path.join(__dirname, '../client/build', 'index.html');
  if (!url.startsWith('/app/')) // <-- not sure what to replace here to make code work
    url = url.substring(1);
  res.sendFile(url);
});

//MonogoDB code to access DB

app.use("/", router);

app.listen(PORT, () => {
  console.log(`Express running PORT ${PORT}`);
});

routes.js

const express = require("express");
const router = express.Router();
const calDataController = require("../controllers/CalDataController");  

router.get('/get', calDataController.getData);   
// I have other routes but /get is the one I am testing 

module.exports = router;

Solution

  • There's a handy piece of information in the Create React App documentation around deployment and client-side routing.

    Your Express app should have the following

    app.use(logger("dev"));
    app.use(cors()); // always register CORS before other request handling middleware
    app.use(express.json()); // you only need this once
    app.use(express.urlencoded()); // do you even need this?
    
    // Register API routes
    app.get("/api", (req, res) => {
      res.json({ message: "Hello from server!" });
    });
    
    app.use("/", router);
    
    // Register client routes / middleware
    const CLIENT_BUILD_DIR = path.join(__dirname, "../client/build");
    
    app.use(express.static(CLIENT_BUILD_DIR));
    
    app.get("/*", (req, res) => {
      res.sendFile(path.join(CLIENT_BUILD_DIR, "index.html"));
    });
    

    The main thing to note is that that catch-all route is defined as /* and is registered last. This is so it does not conflict with other routes.