Accessing internal API with React, Axios on Heroku - node.js

I am building a full stack React application that accesses its own back end API with Axios. In my local environment, the following works as expected, with the server responding with JSON data, which is then rendered properly.
axios.get('/api/questions/categories')
I deployed to Heroku, and the app is launching normally and MongoDB is connected. Now, when the same GET request is made, it is not reaching the back end. When I log the response from Axios to the console, it contains the actual HTML of the page, instead of the JSON object expected.
For further clarification, if I manually type 'http://localhost:8080/api/questions/categories' in the address bar, the expected JSON data is displayed. If I do the same with the app on Heroku, I see that a '#' is appended to the url and the page display does not change, no error messages. This leads me to think that react-router is involved, but I have not been able to figure out how/why.
My stack: Node, Express, Mongo, React
Not using Redux
Using Axios to call my own API
// Dependencies
var express = require('express');
var path = require('path');
var webpack = require('webpack');
var webpackMiddleware = require('webpack-dev-middleware');
var webpackHotMiddleware = require('webpack-hot-middleware');
var config = require('./webpack.config.js');
var bodyParser = require('body-parser');
var mongoose = require('mongoose');
var morgan = require('morgan');
var inDev = process.env.NODE_ENV !== 'production';
var port = inDev ? 8080 : process.env.PORT;
var app = express();
// MIDDLEWARE
if (inDev){
var compiler = webpack(config);
var middleware = webpackMiddleware(compiler, {
publicPath: config.output.publicPath,
contentBase: 'app',
stats: {
colors: true,
hash: false,
timings: true,
chunks: false,
chunkModules: false,
modules: false
}
});
app.use(morgan('dev'));
app.use(middleware);
app.use(webpackHotMiddleware(compiler));
app.get('/', function response(req, res) {
res.write(middleware.fileSystem.readFileSync(path.join(__dirname, 'dist/index.html')));
res.end();
});
} else {
app.use(express.static(__dirname + '/dist'));
app.get('*', function response(req, res) {
res.sendFile(path.join(__dirname, 'dist/index.html'));
});
}
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
app.use(function(req, res, next) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Access-Control-Allow-Methods', 'GET,HEAD,OPTIONS,POST,PUT,DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers');
//and remove caching so we get the most recent comments
res.setHeader('Cache-Control', 'no-cache');
next();
});
// DATABASE
var dbPath = inDev ? 'mongodb://localhost/quizMe' : 'mongodb://heroku_pmjl5579:c28cf07fpf05uus13ipjeur5s7#ds143000.mlab.com:43000/heroku_pmjl5579';
mongoose.connect(dbPath);
// ROUTING / API
// var indexRoute = require('./routes/index');
var questionsRoute = require('./routes/api/questions');
// app.use('/', indexRoute);
app.use('/api/questions', questionsRoute);
app.listen(port, function(){
console.log('Express server up on ' + port);
});
Thanks for any help!

Most single page applications route all requests to the root path and let the front end router take over. I suspect that is what is happening to your app.
Do you have any form of requests redirection logic in your back end code or any server configuration code?
What you can do is to whitelist some paths that you don't want front end routing to take over, such as those that start with /api. Pasting your server side config here will be helpful.
In your server config, when inDev is false, you have a app.get('*', ...) that catches all requests and responds with the static single page app. Hence API requests will also give the same response. You will need to restructure your routes to match /api before the wildcard *. Some examples can be found here

Related

Angular image not served properly from nodejs backend

I want to serve a image from my nodejs backend and show it in frontend, my backend running at 8081 port and frontend at 8080. I can see images in http://localhost:8081/image.JPG but in frontend I am getting 404 as it is looking for http://localhost:8081/image.JPG.
var express = require('express');
var cors = require("cors");
var bodyParser = require('body-parser');
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
global.__basedir = __dirname;
app.use(express.static(__dirname + '/resources/static/assets/uploads/'));
app.use(function (req, res, next) {
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8080');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
// Request headers you wish to allow
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
// Set to true if you need the website to include cookies in the requests sent
// to the API (e.g. in case you use sessions)
res.setHeader('Access-Control-Allow-Credentials', true);
// Pass to next layer of middleware
next();
});
const db = require('./app/config/db.config');
app.use(cors({origin: 'http://localhost:8080'}));
// force: true will drop the table if it already exists
db.sequelize.sync({force: false}).then(() => {
console.log('Drop and Resync with { force: false }');
});
require('./app/routers/upload.router.js')(app);
// Create a Server
var server = app.listen(8081, function () {
var host = server.address().address
var port = server.address().port
console.log("App listening at http://%s:%s", host, port)
})
i can see image when i query in browser as it is looking for 8081 port
frontend looking for port 8080 so getting below error--
someone please suggest me a solution. I am not getting any clue. Thank you.
An approach that you can take is to use proxy config provided by Angular like below:
{
"/api/assets": {
"target": "http://localhost:8081",
"secure": false
}
}
Now, from the frontend when you hit this URL: http://localhost:8080/api/assets/image.JPG it will proxy to http://localhost:8081/api/assets/image.JPG
your image tag will look like <img src="/api/assets/image.JPG">
More on Proxy config & how to configure: https://angular.io/guide/build#proxying-to-a-backend-server

How to serve a Nodejs & Express app using Phusion Passanger

I have a node & express app that is currently hosted on a shared hosting. I would like to run and manage the app using Phusion Passenger. My hosting account supports nodejs applications managed by Passenger which i have never used before.
The server code generated when setting up the Node app is the basic server setup as below.
var http = require('http');
var server = http.createServer(function(req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
var message = 'It works!\n',
version = 'NodeJS ' + process.versions.node + '\n',
response = [message, version].join('\n');
res.end(response);
});
server.listen();
I would like to replace this with the code below that has elements of express that i am using to serve my API routes.
//import modules
var express = require('express'),
bodyParser = require('body-parser'),
morgan = require('morgan'),
cors = require('cors');
path = require('path');
var app = express();
var port = process.env.PORT || 3000;
//import database connection from dbconnect.js file
var mysql = require('./dbconnect/dbconnect');
//Parse as urlencoded and json.
app.use(bodyParser.urlencoded({extended:false}));
app.use(bodyParser.json());
//adding middleware - cors
app.use(cors());
//Http logger
app.use(morgan('dev'));
//Uncomment for production
//app.use(express.static(__dirname + '/public'));
// Point static path to public
app.use(express.static(path.join(__dirname, 'public')));
//import routes from /routes/routes.js
var user = require('./routes/Users');
route = require('./routes/route');
router = require('./router/router');
//adding routes
app.use('/api', user, route, router);
// Catch all other routes and return the index file
app.get('/*', (req, res) => { res.sendFile(path.join(__dirname, '/public/index.html'));
});
app.use(function (req,res,next){
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "x-access-token, Origin, Content-Type, Accept");
next();
});
app.listen(port, function() {console.log('Server started at http://localhost:'+port+'/');});
but i get the error:
I am currently running my server script on the shared server using Forever, which is working fine but that hasn't been efficient, so i would like to switch to using Passenger.
Are you able to set your NODE_ENV=development and then look at your page again? It will likely output a lot more information, why it throws the Error.
With the error information, we can have a better look at what might be wrong.
Thanks,
Marc
I suspect it has to do with your routes and it not finding the files.

Angular front end not getting appropriate response for http request from express backend

I'm using Angular 7 to send a http request to an Express 4 backend code, but keep getting a 404 response in return. I think it might be an issue with the way I've listed the path for the http request, but not sure. This is the first time I'm attempting something like this. I have set up a proxy.conf.js file in Angular to allow from cross origin communication (angular runs on localhost:4200, express on localhost:8085). My Express code is saved under C:/ABC/myapp. Here's the relevant code:
Angular proxy.conf.js file:
{
"/": {
"target": "http://localhost:8085",
"secure": false,
"logLevel": "debug"
}
}
Angular service code which sends the http request:
export class ContactUsService {
private url = '/contactus';
private result: any;
message: string;
addMessage(contactUsMessage: contactUsInterface): Observable<contactUsInterface> {
return this.http.post<contactUsInterface>(this.url, contactUsMessage). (retry(3));
};
constructor(private http: HttpClient) {}
};
Express app.js code:
var bodyParser = require('body-parser');
var cookieParser = require('cookie-parser');
var createError = require('http-errors');
var express = require('express');
var logger = require('morgan');
var mongoose = require('mongoose');
var path = require('path');
var winston = require('winston');
var contactUsRouter = require(path.join(__dirname, 'routes', 'contactUs'));
var app = express();
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/contactus', contactUsRouter);
app.use(function(req, res, next) {
next(createError(404));
});
app.use(function(err, req, res, next) {
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
contactUs.js code (for contactUsRouter):
var express = require('express');
var router = express.Router();
var contactUsMessage = require('../models/contactUsMessage');
router.route('/contactus')
.get(function(req,res,next){
res.send("Hello")
})
.put(function(req,res,next){
res.send("Hello")
});
module.exports = router;
When I reach the contactus page (url: localhost:4200/contactus) and execute the submit button for the form, I get the following errors:
In the browser console: "HTTP404: NOT FOUND - The server has not found anything matching the requested URI (Uniform Resource Identifier).
(XHR)POST - http://localhost:4200/contactus"
In the npm log: "POST /contactus 404 3.081 ms - 2128
Error: Failed to lookup view "error" in views directory "C:\ABC\myapp\views".
Any words of wisdom on what I'm doing incorrectly?
Currently you are exposing a route of POST /contactus/contactus because you are specifying a base route with the statement app.use('/contactus', contactUsRouter);, then adding/appending an additional/extra /contactus with the registration of the POST/PUT routes.
Try change the POST route to path to just '/':
var express = require('express');
var router = express.Router();
var contactUsMessage = require('../models/contactUsMessage');
router.route('/')
.get(function(req,res,next){
res.send("Hello")
})
.post(function(req,res,next){
res.send("Hello")
});
module.exports = router;
Hopefully that helps!
Your proxy conf file is not working properly.
The requests are still trying to look for a POST method route /contactus in angular application running on port 4200 ((XHR)POST - http://localhost:4200/contactus) which does not exist and hence you are getting a 404.
Make sure you have followed the steps mentioned on official angular website - Add Proxy for backend server - angular.io
It will be advisable if you use proxy path /api instead of / . Something like this
{
"/api": {
"target": "http://localhost:8085",
"secure": false
}
}
Hope it helps

Cannot GET when using forever with node and express

We are using NodeJS and Express in combination with forever. That worked fine, but now we had to update our NodeJS version and it all stops working.
We use angular with ui-routing for frontend routing so we have an static folder.
I can go to our homepage (/) and from there I can navigate to the whole site. But when I refresh an page or I go directly to an page (eg. /products) I get an
Cannot GET /products
error.
Node gives an 404 error.
When I run the script directly without forever everything works fine. As you can see we have 2 static folders configured in express. Before the update everything works fine.
We also use Apache to redirect custom domainnames to specific pages without changing the address in the browser, but that works fine (only shows the GET error instead of the page).
Anybody any idea how to solve this?
our app.js
var path = require('path');
var fs = require('fs');
// Return values from different config files will be added to this object so you can call config.[category].[property].
config = {};
// Save the app root directory to a global variable so it can be used in config files and other parts of the app. global.root is reserved, but global.path.root can be used without problems.
global.path = {root: path.resolve(__dirname)};
// Set environment and initialize environment-specific config variables
config.env = require(path.join(__dirname, 'config', 'env.config'));
// Set up database connection to use throughout the application
config.db = require(path.join(__dirname, 'config', 'db.config'));
// HTTP for development environment, HTTPS for production environment
var http = require('http');
var https = require('https');
// Set up debugging/logging for the development environment
var debug = require('debug')('http');
// Start the app using the Express settings/routines in express.config.
var app = require(path.join(__dirname, 'config', 'express.config'));
// Start GraphQL process
// require(path.join(__dirname, 'config', 'graphql.config'))(app);
var router = require(path.join(__dirname, 'config', 'routes.config'));
router(app);
// Running in production mode: HTTPS only
if(config.env.name === 'production') {
var credentials = {
privateKey: fs.readFileSync('privkey'),
certificate: fs.readFileSync('fullchain')
};
var server = https.createServer(credentials, app);
server.listen(4443);
server.on('error', onError);
server.on('listening', onListen);
var server2 = http.createServer(app);
server2.listen(8080);
// Running in development mode: HTTP only
} else {
var server = http.createServer(app);
server.listen(config.env.port);
server.on('error', onError);
server.on('listening', onListen);
}
//some listeners here
Our express.config.js
var path = require('path');
console.log('Initializing API...');
var express = require('express');
var bodyParser = require('body-parser');
var cookieParser = require('cookie-parser');
var compression = require('compression');
var morgan = require('morgan');
var session = require('express-session');
var MongoStore = require('connect-mongo')(session);
var config = require(path.join(__dirname, 'db.config'));
// The GraphQL server implementation for Express by the Apollo team.
var graphqlExpress = require('graphql-server-express').graphqlExpress;
var graphiqlExpress = require('graphql-server-express').graphiqlExpress;
var OpticsAgent = require("optics-agent");
var passport = require('passport');
var app = express();
// Handle application/json requests
app.use(bodyParser.json({ limit: '50mb' }));
// Handle application/x-www-form-urlencoded requests (usually POST, PUT, etc.)
app.use(bodyParser.urlencoded({ extended: false, limit: '50mb' }));
app.use(cookieParser());
app.use(session({
store: new MongoStore({
url: 'mongodb://' + config.url + ':' + config.port + '/' + config.name
}),
secret: 'secret',
key: 'skey.sid',
resave: false,
saveUninitialized: false,
cookie : {
maxAge: 604800000 // 7 days in miliseconds
}
}));
app.use(passport.initialize());
app.use(passport.session());
require(path.join(__dirname, 'auth.config'))(passport); //Load passport config
app.use(function(req, res, next) {
req.resources = req.resources || {};
// res.locals.app = config.app;
res.locals.currentUser = req.user;
res.locals._t = function (value) { return value; };
res.locals._s = function (obj) { return JSON.stringify(obj); };
next();
})
// Use gzip compression (following the Express.js best practices for performance)
app.use(compression());
// Serve frontend and static files like stylesheets and images from the Express server
app.use(express.static(path.join(__dirname, '..', '..', 'app')));
app.use(express.static(path.join(__dirname, '..', '..', 'public')));
// Morgan logger (previously named express-logger)
app.use(morgan("dev"));
// Generate the GraphQL schema
var schema = require(path.join(__dirname, 'graphql.config'))().then(function(schema) {
/* Use Apollo Optics middleware for query optimization/tracing. */
OpticsAgent.instrumentSchema(schema);
app.use('/apiv2', OpticsAgent.middleware());
console.log('GraphQL schema generated.');
/* Return params object for Apollo GraphQL Server using a request handler function. */
app.use('/apiv2', graphqlExpress(function(req) {
return {
schema: schema,
debug: true,
context: {
opticsContext: OpticsAgent.context(req)
}
};
}));
app.use('/graphiql', graphiqlExpress({endpointURL: '/apiv2'}));
console.log('GraphQL started.');
/* Handle all other HTTP requests AFTER graphql server API endpoint and other routes are defined. */
app.use('*', express.static('app'));
});
// Always keep the errorHandler at the bottom of the middleware function stack!
// Returns a user-friendly error message when the server fails to fulfill the request
app.use(function(err, req, res, next) {
var status = 500, response = {message: 'An internal server error has occurred while trying to load this page. '};
console.error(err.stack);
res.status(status).json(response);
next(err);
});
module.exports = app;
Not really an solution to this problem, but we changed forever to PM2 (https://github.com/Unitech/pm2) and that program is doing his job fine!

React axios calls to Express API in Heroku deployment

I have a React todo app I built using create-react-app and I built a simple express server to query mongoDB to get all the appointment objects. It works just as expected when I am running it on my machine. The front end spins up on localhost:3000 and the server on localhost:3001. I use axios to make a get request to localhost:3000/api/appointments to load all the appointments into the App.js state. I uploaded it to Heroku and I got a CORS error on the request. After that, I tried to just use the route 'api/appointments' in the request and every permutation of that I can come up with which all respond with 404 errors.
Where does the node.env variable spin up the server on Heroku? And how do I call it fromm a React app with axios?
Same question in a different context if it helps:
When I run the app on my machine and access it with Postman, I can GET localhost:3001/api/appointmentsand it returns an array of JSON objects from the database just as I would expect. When I deploy to Heroku GET https://appointment-ledger-map.herokuapp.com/api/appointments returns all the markup for index.html. I assume this means that the api server is up and running because it responds but why is it not responding with the array of JSON objects as expected?
// server.js
var express = require('express');
var mongoose = require('mongoose');
var bodyParser = require('body-parser');
var Appointment = require('./model/appointments');
//and create our instances
var app = express();
var router = express.Router();
//set our port to either a predetermined port number if you have set
//it up, or 3001
var nodeEnv = process.env.NODE_ENV || 'development';
var port = process.env.PORT || 3001;
var host = process.env.HOST || '0.0.0.0';
//db config
mongoose.connect('mongodb://josh11:josh11#ds133162.mlab.com:33162/heroku_tl016m5d');
//now we should configure the API to use bodyParser and look for
//JSON data in the request body
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
//To prevent errors from Cross Origin Resource Sharing, we will set
//our headers to allow CORS with middleware like so:
app.use(function(req, res, next) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Access-Control-Allow-Methods', 'GET,HEAD,OPTIONS,POST,PUT,DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers');
//and remove cacheing so we get the most recent appointments
res.setHeader('Cache-Control', 'no-cache');
next();
});
//now we can set the route path & initialize the API
router.get('/', function(req, res) {
res.send({ message: 'API Initialized!'});
console.log('Api initialized');
});
//Use our router configuration when we call /api
app.use('/api', router);
//starts the server and listens for requests
app.listen(port, host, function() {
console.log(`api running on port ${port}`);
});
//adding the /appointments route to our /api router
router.route('/api/appointments')
//retrieve all appointments from the database
.get(function(req, res) {
//looks at our Appointment Schema
Appointment.find(function(err, appointments) {
if (err)
res.send(err);
//responds with a json object of our database appointments.
res.send(appointments)
});
console.log(appointments);
})
//post new appointment to the database
.post(function(req, res) {
var appointment = new Appointment();
//body parser lets us use the req.body
appointment.appointmentTitle = req.body.appointmentTitle;
appointment.appointmentDate = req.body.appointmentDate;
appointment.appointmentTime = req.body.appointmentTime;
appointment.appointmentDescription = req.body.appointmentDescription;
appointment.appointmentDestination = req.body.appointmentDestination;
appointment.appointmentOrigin = req.body.appointmentOrigin;
appointment.travelMode = req.body.travelMode;
appointment.save(function(err) {
if (err)
res.send(err);
res.send({ message: 'Appointment successfully added!' });
});
});
// App.js
loadAppointments() {
axios.get('/api/appointments')
.then(res => {
this.setState({
appointments: res.data,
filteredAppointments: res.data
});
})
}
npm install cors --save
then
var cors = require('cors');
finally
mongoose.connect('mongodb://josh11:josh11#ds133162.mlab.com:33162/heroku_tl016m5d');
//now we should configure the API to use bodyParser and look for
//JSON data in the request body
app.use(cors()); **//Must be before BodyParser**
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
//To prevent errors from Cross Origin Resource Sharing, we will set
//our headers to allow CORS with middleware like so:
Re-deploy it and voila :)
Hope it helped you

Resources