4

So I'm setting up a 5 day weather forecast web app for practice interacting with APIs using the MERN stack. I'm using Axios.js to send and respond to requests; to make sure I have my back-end working, I started building that out first before starting to communicate with the API. However, the button I have set up on the front-end (which sends a get request to my server for json data) always returns a response object with response.data having the value of:

RESPONSE: <!doctype html>
<html>
<head>
    <meta name="viewport" charset="UTF-8" content="width=device-width, initial-scale=1.0">
</head>
<body>
    <div id="app"></div>
    <script src="./dist/bundle.js"></script>
</body>
</html>

instead of

RESPONSE: "hello there!"

for a JavaScript that looks like:

{data: "hello there!"}

I know I'm probably missing a step when sending and receiving these requests, but after doing research into this I'm still not sure why I'm not receiving the expected result. My files are set up like this:

-weather_forcast
  -client
    -src
      -components(empty)
      app.jsx
  -public
    -dist
      bundle.js
    index.html
  -server
    -routes
      routes.js
    index.js
  package.json
  webpack.config.js

The contents of the files that currently have code in them are:

app.jsx

    import React, {Component} from 'react';
    import ReactDOM, {render} from 'react-dom';
    import axios from 'axios';
    // import daysOfWeek from './daysOfWeek.jsx';

    class App extends Component {
      constructor() {
          super();
          this.state = {
          }
          this.getData = this.getData.bind(this);
      }

      getData() {
          axios.get('/')
          .then((response) => {
              console.log("RESPONSE:", response.data);
          })
          .catch((error) => {
              console.log(error);
          })
      }

      render() {
          return(
              <div>
                  <button onClick={this.getData}>Hello world</button>
              </div>
          )
      }
  }

  render(<App/>, document.getElementById('app'));

index.html

<!doctype html>
<html>
    <head>
        <meta name="viewport" charset="UTF-8" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        <div id="app"></div>
        <script src="./dist/bundle.js"></script>
    </body>
</html>

routes.js

let express = require('express');
let router = express.Router();

router.get('/', (req, res) => {
    res.send({data:'hello there!'});
});

module.exports = router;

index.js

const express = require('express');
const fs = require('fs');
const path = require('path');
const bodyParser = require('body-parser');
const router = require('./routes/routes.js');
const app = express();
let port = 8000;

app.use(bodyParser.urlencoded());
app.use(bodyParser.json());
app.use(express.static(path.join(__dirname, '../public')));

app.use('/', router);

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

webpack.config.js

const path = require('path');
const SRC_DIR = path.join(__dirname, '/client/src');
const DIST_DIR = path.join(__dirname, '/public/dist');

module.exports = {
    entry: `${SRC_DIR}/app.jsx`,
    output: {
        filename: 'bundle.js',
        path: DIST_DIR
    },
    module: {
        rules: [
            {
                test: /\.jsx?/,
                include: SRC_DIR,
                exclude: /(node_modules|bower_components)/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env', '@babel/preset-react']
                    }
                }
            }
        ]
    }
}

The same problem presents itself before I added the "routes" folder and had set up my index.js file like this:

const express = require('express');
const fs = require('fs');
const path = require('path');
const bodyParser = require('body-parser');
const router = require('./routes/routes.js');
const app = express();
let port = 8000;

app.use(bodyParser.urlencoded());
app.use(bodyParser.json());
app.use(express.static(path.join(__dirname, '../public')));

app.get('/', (req, res) => {
  res.send({data: "hello there!"});
);

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

Any help would be greatly appreciated! I can't seem to get my json object to the front end as data, but I'm not sure what I'm missing from this set up.

Alex Friedman
  • 41
  • 1
  • 4

1 Answers1

1

The response you're getting seems to indicate the dev server is serving you the react application (note the line: <script src="./dist/bundle.js"></script>).

When you're running two servers simultaneously on different ports (ex webpack dev server and your express app), you have a few options to handle them.

1) CORS Make a request to your other server with its full address:

"http://localhost:8000/<path>"

This is generally not recommended unless your server is meant to be completely separate from your React application and allows CORS. Given that both server and client exist in the same repository, it seems that you're going to want your server to serve your React Application as well.

2) Proxying Requests

See Docs For More Info

Webpack gives you the ability to proxy server requests. This is useful if you're using a different port in development, but your server and react app will sit together in production. In your webpack.config.js you can do the following:

webpack.config.js:

module.exports = {
  // prior rules
  module: {
    // module rule followed by comma
  },
  devServer: {
    proxy: {
      "/api": "http://localhost:8000"
    }
  }
}

In your express server, append each request with 'api' like this: /api/<path>

routing:

app.use('/api', router);

app.jsx

getData() {
  axios.get('/api')
  .then((response) => {
    console.log("RESPONSE:", response.data);
  })
  .catch((error) => {
    console.log(error);
  })
}

In The Future

Eventually, you may want "/" to send the React application, instead of using a purely static approach.

In your express app, you can do something like this:

  // serve index.html
  const path = require('path')
  app.get('*', (req, res) => {
    res.sendFile(path.resolve('<PATH TO BUILT APP index.html>')) 
  })

The * is "any request not previously defined", meaning you should define this after all of your api routes. This way, you're react app is served unless a /api/.... request is made. The real advantage (in my opinion) of doing something like this is that all requests that do not match a server route are handled in the React application.

vapurrmaid
  • 2,287
  • 2
  • 14
  • 30
  • Right, I'm aware of the CORS request issue, though I still haven't figured out how to include Cross-Origin-Request-Headers with an axios request. Also, I didn't use create-react-app, this was all done manually so if the solution is specific to some dependencies given by create-react-app, I'm not sure if I would have those same ones installed necessarily. – Alex Friedman Apr 22 '18 at 14:39
  • Here are my current dependencies (not typed out in JSON) DEPENDENCIES: "axios": "^0.18.0", "express": "^4.16.3", "mongodb": ^3.0.7", "react": "^16.3.2", "react-dom": "^16.3.2" DEV DEPENDENCIES: "@babel/core": "^7.0.0-beta.44", "@babel/preset-env": "^7.0.0-beta.44", "@babel/preset-react": "^7.0.0-beta.44", "babel-loader": "^8.0.0-beta.2", "babel-preset-env": "^1.6.1", "webpack": "^4.6.0", "webpack-cli": "^2.0.14" – Alex Friedman Apr 22 '18 at 14:40
  • Updated my answer to use webpack configuration. If you're going a CORS route (again, not at all recommended for this setup!) you don't have to set any headers manually client side. You can issue an `axios.get('')`. However, your server will have to enable CORS. – vapurrmaid Apr 22 '18 at 15:25
  • Ohhh I see, so in other words, the client side is free to make the request. However, if the request fails or doesn't go through because of CORS issues, then it's something that has to be handled on my server? Currently, I have no middle-ware that adds cross-origin-request-headers to my requests for the weather API. I would need to add those, wouldn't I? – Alex Friedman Apr 23 '18 at 15:46
  • Also, if I opt for serving my React application with "/", wouldn't that mean I should also get rid of the static page that's being served up with "app.use(express.static(path.join(__dirname, '../public')));" in index.js? It just seems as if I wouldn't need to serve a static page if I would be serving up the application with "/" anyway, but it's also possible that I'm misunderstanding the purpose of serving up the app with "/", as opposed to serving the static index.html... – Alex Friedman Apr 23 '18 at 16:02
  • 1) Cors: if you issue a cross-origin request, the browser adds the header automatically. Your server will see this, and if you don't use CORS middleware, will reject the request. You can easily add CORS by using [this package](https://www.npmjs.com/package/cors). So if you want to make a public API, use this. In the exact setup you have, it's not necessary as your server is serving the application. – vapurrmaid Apr 23 '18 at 16:21
  • 2) You can actually have both. It's just that in your scenario, the only way to access the react application is by the specific route `localhost:8000/index.html`. The logic I added (as a suggestion) instead says "any request I don't understand, just respond with the application". Then you don't have to manage unknown URLs in two places: you can just handle them on the client. It's a sugggestion, you don't have to do it by any means – vapurrmaid Apr 23 '18 at 16:25