Search code examples
node.jsreactjsherokucreate-react-approuter

Create-React-App + Heroku + Api Routes = 404 Errors


I've been able to use React for heroku applications before and I've had successful api calls without React on heroku, but I've never been able to mix the two. Not even once.

The api route works on localhost.

I have incredibly basic code that produces a 404 error whenever I attempt to access one of the api routes on deployment to Heroku. Below is my server.js file:

const express = require("express");
const mongoose = require("mongoose");
const PORT = process.env.PORT || 3001;
const path = require("path");
const routes = require("./routes");
const app = express();

let MONGODB_URI = process.env.MONGODB_URI || "mongodb://localhost/database";

app.use(express.urlencoded({extended:false}));
app.use(express.json());
app.use(express.static("public"));

if (process.env.NODE_ENV === "production") {
    app.use(express.static("client/build"));
}

mongoose.connect(MONGODB_URI, {useNewUrlParser: true, useUnifiedTopology: true});
mongoose.connection.on("connected", function() {
    console.log("~ Connected To Database ~");
});

app.use(routes);

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

app.listen(PORT, function() {
    console.log("App listening in on " + PORT);
});

My api route is set via the file structure:

routes (located in the same directory as server.js)

index.js api.js

Here is the index.js:

const apiRoutes = require("./api.js");
const router = require("express").Router();

router.use("/api", apiRoutes);

module.exports = router;

And here is the api.js:

const router = require("express").Router();

router.get("/users/all", function(req, res) {
    console.log("Running! The API Route is Being Called!");

    res.send("Success");
});

module.exports = router;

Here is the Base react component where the axios call is initiated:

import React, {Component} from "react";
import "./style.css";
import axios from "axios";


class Base extends Component {
    testAxios = async () => {
        axios.get("/api/users/all");   
    }

    render() {
        return (
            <div>
            <p>Helloooo</p>
            <button onClick = {this.testAxios}>Test Axios</button>
            </div>
        )
    }
}

export default Base;

And, finally, here are the relevant package.json files:

For the Client Folder:

{
  "name": "client",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^16.12.0",
    "react-dom": "^16.12.0",
    "react-router-dom": "^5.1.2",
    "react-scripts": "3.2.0"
  },
  "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"
    ]
  }
}

For the Root Folder:

{
  "name": "garbage",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "if-env NODE_ENV=production && npm run start:prod || npm run start:dev",
    "start:prod": "node server.js",
    "start:dev": "concurrently \"nodemon --ignore 'client'/*'\" \"npm run client\"",
    "install": "cd client && npm install",
    "client": "cd client && npm run start",
    "build": "cd client && npm run build",
    "heroku-postbuild": "cd client && npm install --only=dev && npm install && npm run build"
  },
  "devDependencies": {
    "concurrently": "^5.0.0",
    "nodemon": "^1.19.4",
    "http-server": "^0.11.1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "axios": "^0.19.0",
    "concurrently": "^5.0.0",
    "express": "^4.17.1",
    "mongoose": "^5.7.10",
    "node": "^12.11.1",
    "nodemon": "^1.19.3",
    "react": "^16.10.2",
    "react-router-dom": "^5.1.2"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/garbage/garbage.git"
  }
}

I've tried adding a static.json file, and it didn't work. If you have any ideas, please let me know.


Solution

  • I've discovered the source of my problem. In the package.json, it was one line:

    "scripts": { "start": "node server.js", "start:original": "if-env NODE_ENV=production && npm run start:prod || npm run start:dev" }

    The "start:original" line was, as it implies, the original start function. Rather than rely on the "if-env NODE...", I simply replaced it with "node server.js". When I'm developing and I want to start the server with concurrently, I now just use node run start:dev.

    Since then, all my React apps have successfully worked with api routes.