I'm trying to run a website on an AWS EC2 instance using React, Express and Axios.
The problem: I'm unable to make axios calls to the Express back-end running on the same instance. The code works fine on my local machine, but on the EC2 instance I get Error: Network Error. The server runs on port 5000 and the client runs on port 3000. I'm able to access the website from the outside using the URL, and am able to make GET requests to the back-end directly using Postman.
Home.js (frontend)
import { useState, useEffect } from 'react';
import axios from 'axios';
function Home() {
const [text, setText] = useState('');
useEffect(() => {
axios.get(`http://172.31.20.10:5000/`)
.then(
result => {
setText(result.data);
},
err => {
alert(err);
setText('An error has occurred');
}
);
});
return (
<h1>{ text }</h1>
);
}
export default Home;
App.js (frontend)
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Home from './Home';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<Home />
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
App.js (backend)
const express = require('express');
const cors = require('cors');
const app = express()
const port = 5000;
app.use(express.json());
app.use(cors());
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(port, () => {
console.log(`App listening on http://localhost:${port}`);
});
172.31.20.10 is the private IP of the EC2 instance. I've tried using localhost instead of the IP, https:// instead of http://, not use http(s) at all, leaving the http and IP/localhost out entirely, etc etc. None have worked so far. Any help would be much appreciated.
To answer the comments: The code did work on my own computer, and the whole network error was a CORS error ("CORS request did not go through" or some such).
I'm still not exactly sure what went wrong, but I "solved" it by hosting the client and server on two different instances. Not sure how or why it works, but it did...
Related
I am wondering why the Fetch API in javascript isn't call the endpoint I created in express. When I start my server and go to the '/characters' endpoint, it returns correctly formatted JSON.
Here is my express index.js
const app = express();
const PORT = 3000;
const charactersRoute = require('./routes/characters');
//Characters Route
app.use('/characters', charactersRoute)
app.listen(PORT, function(err) {
if(err) console.log(err);
console.log(`Server is listening on port ${PORT}`)
})
Here is my Characters Route
const express = require('express'); // Web Framework
const https = require('https');
const router = express.Router();
const PORT = 3000;
// app.listen(PORT, function(err) {
// if(err) console.log(err);
// console.log(`Server is listening on port ${PORT}`)
// })
const api = 'https://www.breakingbadapi.com/api/characters/?limit=20';
router.get("/", function(req, res) {
https.get(api, (response) => {
console.log(response.statusCode);
response.on('data', (d) => {
const data = JSON.parse(d);
res.send(data);
})
// res.send("Running")
})
})
module.exports = router;
Here is my Characters.jsx file where I'm using Fetch
import React, {useEffect, useState} from 'react';
import Card from '#mui/material/Card';
import axios from 'axios';
export default function Character() {
const [data, setData] = useState();
useEffect(() => {
fetch('/characters')
.then(res => res.json())
.then(data => setData(data))
// console.log(data);
}, []);
}
When I run my front end and check the response I receive, it returns my index.html.
If I fetch the API URL itself, then it correctly returns the JSON in my frontend. But When I try to fetch the API endpoint I created in express, I don't get any data. Any suggestions?
You did not set the endpoint for the fetch function. It doesn't know what API '/characters' is. It is similar to saying the house number but not telling the street, you don't know where to go. So you need to pass the absolute path to fetch to request data from the server, because the server is a different 'entity', it's not the same with your front-end. Therefore, you need to provide the full API URL. Or, if using axios, since I see you imported it above, you must set axios.defaults.baseURL = <API_URL>, (or provide the full URL in the request itself just like in fetch) and then make the request with the help of axios, not fetch.
Therefore your React code will look a little something like this:
import React, {useEffect, useState} from 'react';
import Card from '#mui/material/Card';
import axios from 'axios';
axios.defaults.baseURL = 'http://localhost:3000';
export default function Character() {
const [data, setData] = useState();
useEffect(() => {
const getData = async () => {
try {
const {data} = await axios.get('/characters');
setData(data);
} catch (err) {
console.error(err);
}
};
getData();
}, []);
}
But I suggest you create a different file with all the axios requests and there you set the baseUrl - that's a better practice.
I think is that you are not using cors in your app.
const cors = require('cors');
const app = express();
app.use(cors());
app.use(express.json());
Try this, I hope it helps you!
Error pattern
This kind of error occurs when we send a request from the client-side to the server-side using a relative URL (/characters in this case). But the frontend app and the backend app run on 2 different ports.
Reason
When we use relative URLs, the URL will be concatenated with the current host (the frontend host, not the backend). Usually, we receive a 404 error because the resource doesn't exist.
Example
Backend Express app running on port 5000. React app running on port 3000 for development. In React app, if we send a request to /users, then the full URL is http://localhost:3000/users. The request goes to the React app, not the backend server. And we don't receive the desired output.
Action
You should use an absolute URL, something like: http://localhost:5000/users to send the request to your backend app. Consider saving the host part (http://localhost:5000) in a global variable in order to use it in multiple places in your frontend code. When you have different environments for the backend (DEV/STAGING/PRODUCTION), you can change the backend host in only 1 place.
For the first time, the server side is refreshed, but the next time, only the client side is changed as usual, and the server does not change.
For example, with each refresh or typing of the address in the browser, the server also changes and works, but if I switch between pages on the client side with react router, the server does not change.
what is the problem?
#server/server.js
import path from 'path';
import fs from 'fs';
import express from 'express';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import App from '../src/App';
const PORT = 5000;
const app = express();
const router = express.Router();
app.use('/build', express.static('build'));
app.use((req, res, next) => {
if (/\.js|\.css|\.png|\.jpg|\.jpeg/.test(req.path)) {
res.redirect('/build' + req.path);
} else {
next();
}
})
app.get('*', (req, res) => {
const context = {};
const app = ReactDOMServer.renderToString(
<StaticRouter location={req.path} context={context}>
<App />
</StaticRouter>
);
const indexFile = path.resolve('./build/index.html');
fs.readFile(indexFile, 'utf-8', (err, data) => {
if (err) {
console.log("Something went wrong:", err);
return res.status(500).send("Oops, better luck next time!");
}
return res.send(data.replace('<div id="root"></div>', `<div id="root">${app}</div>`));
});
});
router.use(express.static(path.resolve(__dirname, '..', 'build'), { maxAge: '10d' }));
app.use(router);
app.listen(PORT, () => {
console.log(`SSR running on ${PORT}`);
});
#server/index.js
require('ignore-styles');
require('#babel/register')({
ignore: [/(node_module)/],
presets: ['#babel/preset-env', '#babel/preset-react'],
plugins: ['#babel/transform-runtime'],
});
require('./server');
#index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import * as serviceWorker from './serviceWorker';
import App from './App';
ReactDOM.hydrate(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
);
There is nothing wrong with your Server Side Rendering setup. This is what's going on under the hood.
When you first type and enter the URL for a route of the application or you refresh the browser tab it hits the server and loads the index.html file rendered on the server-side with the help of renderToString.
ReactDOMServer.renderToString(...)`
Then index.html is viewed and is hydrated (attaching event handlers...etc) to this skeleton HTML file.
Note that you load the index.html from the build folder and replace only the div with root as the id. After building the app it adds js resources need to change the DOM (these resources are actually your frontend logic you wrote for your app) and this is required for client-side rendering. If you check the index.html it has the following script tags to load them.
...
<script src="/static/js/xxxxxxxxxxxxxxx.chunk.js">
...
When you go to another route by clicking a link inside your app. It does not hit the server again and it starts to execute js attached from the client bundle as I said above. Client-side js does the routing of the app properly. That's why it's not hitting your server. This is the expected nature of an isomorphic web application(Behaves the same on the server-side or client-side). And after that, if you refresh the browser that again loads the index.html from the server.
Forgive me if this is something you shouldn't do, but I've looked around to see what is possible.
I want to verify that my express app has a middleware called/used for the app.
import express from 'express';
import cors from 'cors';
const app = express();
app.use(cors()); // <----- I WANT TO TEST THIS
app.get('/', (_req, res) => res.send({ hello: 'world' });
app.listen(5000, () => console.log(`Listening on port 5000`));
export default app;
Potential jest test
import app from './index.ts';
// This is a poor attempt to try and get this to work
test('test if CORS is implemented', () => {
const mockCors = jest.mock('cors');
const myApp = app;
expect(mockCors).toHaveBeenCalledTimes(1);
});
If anyone has the solution and if I should not be doing this, what is the reason behind it?
My guess is that you don't actually care that cors is called. Your client won't say to you "I want the cors middleware to be called". All the client should care about is the service that is provided, not how it's implemented. Your tests should do the same. If you decide one day to use another module for CORS, your tests shouldn't need to change, why would they?
My (personal) preferred approach is exactly that. Testing that given some input, my programs gives me the output I desire. Everything inside the program can be changed, as long as the behavior does not change. That will give you peace of mind when refactoring.
Also, for testing an Express app, you don't need a server, you just need the app. Actually starting the server can add complexity to your tests, since they might be hanging if you forget to close the server after them.
So in this example, the very first thing I would do is move everything you have in index.ts into a file called app.ts, and remove this line:
app.listen(5000, () => console.log(`Listening on port 5000`));
And in index.ts, only have this:
import app from './app.ts';
// Start the server
app.listen(5000, () => console.log(`Listening on port 5000`));
Then, I would use Supertest along with Jest, to make requests to that app:
npm i -D supertest
And then, in your test file, test what matters to the client:
import request from 'supertest';
import app from './app.js'; // Just import the app, not the server
describe("myApp", () => {
it('should implement CORS', async() => {
const { headers } = await request(app).get('/');
expect(headers['access-control-allow-origin']).toEqual('*');
});
});
I'm trying to use GraphQL to populate fake data for Styleguidist. I'm using Express to make my GraphQL server but I'm unsure how to connect Apollo into Styleguidist? The examples use the index.js file and wrap the root component in an tag for Apollo.
I am unsure how Styleguidist works, I don't know where the index.js file is.
There are ways to configure Styleguidist through webpack, but I don't know how to use webpack to use Apollo.
Each example in Styleguidist is rendered as an independent React tree, and the Wrapper component is the root component, so you need to override it as show in the Redux example like this:
// lib/styleguide/Wrapper.js
import React, { Component } from 'react';
import ApolloClient, { createNetworkInterface } from 'apollo-client';
import { ApolloProvider } from 'react-apollo';
const client = new ApolloClient({ /* ... */ });
export default class Wrapper extends Component {
render() {
return (
<ApolloProvider client={client}>
{this.props.children}
</ApolloProvider>
);
}
}
// styleguide.config.js
const path = require('path');
module.exports = {
styleguideComponents: {
Wrapper: path.join(__dirname, 'lib/styleguide/Wrapper')
}
};
So you can use Styleguidist in two ways, one by using Create React App then installing an NPM Styleguidist package. Then the other method that I found is starting from an example github registry and replacing the components as you go. I had done the first: where I used Create React App so Webpack was not installed in my main folder but was being used in the NPM module.
With that method I was getting the error:
"Module parse failed: Unexpected token (16:6)
You may need an appropriate loader to handle this file type."
Which means that I needed to configure Webpack. I didn't solve this, but it may just need to have styleguide.config.js file configured to work with Babel. (just a guess)
So, could not (so far), successfully use the Wrapper that solves the problem. So instead I downloaded an example of Styleguidist at https://github.com/styleguidist/example and started fresh. I'm not sure what the difference is, but when I used a wrapper it worked well to add an ApolloProvider wrapper to every component on my page.
To get Apollo 2 to work though you also need to use HttpLink and InMemoryCache. The have a general setup about this at: https://www.apollographql.com/docs/react/basics/setup.html. Under creating a client.
I was using a different port for my GraphQL server because it was using a GraphQL/Express server at port 4000 and Styleguidist by default is at port 6060. So I did two things: passed a uri to the new HttpLink and added a line to the express server to allow cors.
The ref for cors in Express GraphQl and Apollo server:
https://blog.graph.cool/enabling-cors-for-express-graphql-apollo-server-1ef999bfb38d
So my wrapper file looks like:
import React, { Component } from 'react';
import ApolloClient, { createNetworkInterface } from 'apollo-client';
import { ApolloProvider } from 'react-apollo';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
const link = new HttpLink({
uri: 'http://localhost:4000/graphql'
});
const client = new ApolloClient({
link,
cache: new InMemoryCache()
});
export default class Wrapper extends Component {
render() {
return (
<ApolloProvider client={client}>
{this.props.children}
</ApolloProvider>
);
}
}
and my server file looks like:
const express = require('express');
const expressGraphQL = require('express-graphql');
const schema = require('./schema/schema');
const cors = require('cors');
const app = express();
app.use(cors());
app.use('/graphql', expressGraphQL({
schema: schema
, graphiql: true
}));
app.listen(4000, () => {
console.log('..listening');
});
The stack I'm working with consists of node, express, webpack, redux, react-router, and react. My express server looks as follows:
import Express from 'express';
import { Server } from 'http';
import path from 'path';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { Provider } from 'react-redux';
import { StaticRouter } from 'react-router-dom';
import { applyMiddleware, createStore } from 'redux';
import thunkMiddleware from 'redux-thunk';
import App from './src/App';
import routes from './src/routes';
import reducer from './src/reducer';
const app = new Express();
const server = new Server(app);
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'src'));
app.use(Express.static(path.join(__dirname, 'clients/Sample/bundle')));
const store = createStore(
reducer,
{},
applyMiddleware(thunkMiddleware),
);
app.get('*', (req, res) => {
const context = {};
const markup = renderToString(
<Provider store={store}>
<StaticRouter location={req.url} context={context}>
<App routes={routes} />
</StaticRouter>
</Provider>,
);
return res.render('index', { markup });
});
const port = process.env.PORT || 3000;
const env = process.env.NODE_ENV || 'production';
server.listen(port, (err) => { // eslint-disable-line consistent-return
if (err) {
return console.error(err);
}
console.info(`Server running on http://localhost:${port} [${env}]`);
});
The application works perfectly if I attempt to access it directly on http://localhost:3000/. From there, I can navigate to other pages without issues. However, if I try to visit one of those other pages directly in the URL (e.g. http://localhost:3000/user) the console displays the following stack trace:
Uncaught Error: Unable to find element with ID 15.
at invariant (vendor.js:1)
at precacheChildNodes (vendor.js:1)
at Object.getNodeFromInstance (vendor.js:1)
at Object.didPutListener (vendor.js:1)
at Object.putListener (vendor.js:1)
at Object.putListener (vendor.js:1)
at CallbackQueue.notifyAll (vendor.js:1)
at ReactReconcileTransaction.close (vendor.js:1)
at ReactReconcileTransaction.closeAll (vendor.js:1)
at ReactReconcileTransaction.perform (vendor.js:1)
This then breaks navigation on the site, and redux state changes too. I've identified the element with this ID as being a imported from react-router-dom. I did find GitHub issues describing similar errors (one and two), sadly though the proposed solutions haven't worked for me. Any suggestions on what might be causing this for me?
I found this was actually related to a DOM validation error which was occurring as a result of having react-router-dom's <Link> within react-bootstrap's <NavItem>. In effect, the application was trying to render the following:
<a role="button" href="#" data-reactid="14">
Welcome, User
</a>
Of course, nested anchor tags are invalid markup. See the question here for further detail and some proposed methods of fixing the issue: React-Bootstrap link item in a navitem