9

After deploying my mern app to Heroku, the GET request on the home page ('http://localhost:8000/post/') is now returning index.html instead of json data from the request. I'm getting 200 status code but the response is html. However, it works fine locally.
All the other requests are working except this one. Whenever I think I've fixed it, Heroku displays the json data instead of the UI on this same route. I'm assuming that these issues are related.

How can I solve this? Thanks!

route/controller - list posts

router.get('/', (list)) 

exports.list = (req, res) => {
  const sort = { title: 1 };
  Post.find()
    .sort(sort)
    .then((posts) => res.json(posts))
    .catch((err) => res.status(400).json("Error: " + err));
};

server.js

require("dotenv").config();

// import routes
...
const app = express();

// connect db - first arg is url (specified in .env)
const url = process.env.MONGODB_URI 
mongoose.connect(url, {
  useNewUrlParser: true,
  useCreateIndex: true,
  useUnifiedTopology: true,
  useFindAndModify: false,
});
mongoose.connection
  .once("open", function () {
    console.log("DB Connected!");
  })
  .on("error", function (error) {
    console.log("Error is: ", error);
  });

// middlewares
app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", '*');
  res.header("Access-Control-Allow-Credentials", true);
  res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
  res.header("Access-Control-Allow-Headers", 'Origin,X-Requested-With,Content-Type,Accept,content-type,application/json');
  next();
});

// middleware
...
//  app.use(express.static(path.join(__dirname, './client/build')))
app.use(authRoutes);
app.use(userRoutes);
app.use('/post', postRoutes);

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

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

const port = process.env.PORT || 80;

app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});

ListPosts.js

class ListPosts extends React.Component {
    state = {
        title: '',
        body: '',
        date: '',
        posts: []
    }

    componentDidMount = () => {
        this.getPosts()
    }

   getPosts = () => {
        axios.get(`${API}/post`)
        .then((response) => {
            const data = response.data
            this.setState({posts: [data]})
            console.log(data)
        })
        .catch((error) => {
            console.log(error)
        })
    }
     
    displayPosts = (posts) => {
        if (!posts.length) return null;
      posts.map((post, index) => (
            <div key={index}>
           ...
            </div>    
        ))
    }


    render() {
        return (
         <div>
           {this.displayPosts(this.state.posts)}
           </div>
        )
    }
}

export default ListPosts
SK1dev
  • 1,049
  • 1
  • 20
  • 52

5 Answers5

14

Your request 'http://localhost:8000/' matches two route handler

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

router.get('/', (list)) 

Since your client build route is placed above the list route it will always return the index.html because precedence matters in express when defining routes.

A good practice and solution is to always differentiate your api routes from the static ones by appending /api before all routes as below

app.use('api/auth', authRoutes);
app.use('api/post', postRoutes);
app.use('api/user', userRoutes);
malong11
  • 668
  • 3
  • 9
4

In your current implementation you have 2 app.get for the same path -> '/' So, express responds with the first one. Which right now is:

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

You can either, specify a different path

app.use("/post", postRoutes);

or rearrange the sequence.

app.use(authRoutes);
app.use(userRoutes);
app.use(postRoutes);

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

or change the controller

app.post('/' ...) // instead of app.get('/'...)

You need to specify the routes url or avoid two 'app.get' for the same route. If you want you can also change the controller from 'app.get' to 'app.post' and express will deem them different. But, if both are app.get for the same route, the first one will send the response and the second one will never be called.

What you can do is, first try to re-arrange the sequence. If it works and that is indeed the problem, don't stick with it as a solution. It's the wrong way to go about. Instead, give your routes a different url or change the controller from 'app.get' to 'app.post'

Tom Bombadil
  • 3,455
  • 1
  • 13
  • 24
4

As some of the answers already mentioned seperating your API and client routes and found the exact issue, I would like to just add a little bit of recommendations based on my experience with serving your react app using express. (Trick is to also add versioning)

app.use('/api/v1/auth', authRoutes);
app.use('/api/v1/user', userRoutes);
app.use('/api/v1/post', postRoutes);
if (process.env.NODE_ENV === 'production') {  
    app.use(express.static(path.join(__dirname, "client/build")));
    app.get("/*", (_, res) => {
     res.sendFile(path.join(__dirname, "client/build", "index.html"));
    });
}
Rishabh Anand
  • 763
  • 7
  • 8
2
router.get('/', (list)) 

const list = (req, res) => {
  const sort = { title: 1 };
  Post.find()
    .sort(sort)
    //  .limit(10)
    .then((posts) => res.json(posts))
    .catch((err) => res.status(400).json("Error: " + err));
};    
module.exports = router;

In server.js

if (process.env.NODE_ENV === "production") {
  app.use(express.static("client/build"));
}
app.get("/*", function (req, res) {
  res.sendFile(path.join(__dirname, "./client/build/index.html"));
});

....

app.use(authRoutes);
app.use(userRoutes);
app.use(postRoutes);

You need to update like this.

It's just sequence problem in express middlewares. You have updated middlewares about get method(/*). So it's always returning index.html instead of JSON.

alpha-bird
  • 318
  • 2
  • 10
  • Thanks for your answer. I have tried this but unfortunately it's still responding with index.html. @NattawutPornpetcharat – SK1dev Oct 26 '20 at 15:10
  • module.exports = router; Have you updated like this on route? Can you post more code about postRoutes? – alpha-bird Oct 26 '20 at 15:16
  • Yes, I already had an export statement in the route file. I just hadn't added it to my code on SO. All my other routes work fine, it's just this one that's returning html. @NattawutPornpetcharat – SK1dev Oct 26 '20 at 15:18
  • @Rahni, are you getting the index.html file in your public folder by chance?? – Mars2024 Jul 16 '21 at 18:02
0

You can change const port = process.env.PORT || 80; line without 80 port. its worked for me const port = process.env.PORT;