Express routes stopped working after setting app in production on Heroku - javascript

I've deployed my app on Heroku and after some tweaking, everything works except when I try to retrieve data from the Mongo database. The console error I get is: Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0.
I have the feeling that it won't go into the get request while that should be the case. (Because it's not logging anything in the console)
Am I missing something in the way routes are handled in production?
Everything in development is working.
I'm very confused at this point, hope someone can help me
Server.js:
const bodyParser = require('body-parser')
const path = require('path');
const express = require('express');
const morgan = require('morgan');
const MongoClient = require('mongodb').MongoClient;
const cors = require('cors')
const compression = require('compression');
const helmet = require('helmet')
const app = express();
const port = process.env.PORT || 5000;
app.use(helmet())
app.use(compression());
if (process.env.NODE_ENV === 'production') {
const publicPath = path.join(__dirname, 'client/build');
const apiPath = path.join(__dirname, 'api');
app.use(express.static(publicPath));
app.use('/overview', express.static(apiPath));
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname + '/client/build/index.html'));
})
}
app.use(cors())
app.use(morgan('tiny'));
app.use(bodyParser.json())
const apiRouter = require('./api/api');
app.use('/overview', apiRouter);
// connect to the db and start the express server
let db;
const url = process.env.MONGODB_URI
MongoClient.connect(url, {useUnifiedTopology: true,useNewUrlParser: true,}, (err, client) => {
if(err) {
return console.log(err);
}
console.log('mongo connected')
db = client.db('kvdlaanmeldingen');
// start the express web server listening on port 5000
app.listen(port, () => console.log(`Listening on port ${port}`));
});
apiRouter, api.js in api/api.js:
const express = require('express');
const apiRouter = express.Router()
const MongoClient = require('mongodb').MongoClient;
const mongodb = require('mongodb');
const url = process.env.MONGODB_URI
console.log('api.js is activated') //this is logged to console, so file can be read.
let db;
MongoClient.connect(url, {useUnifiedTopology: true,useNewUrlParser: true,}, (err, client) => {
db = client.db('kvdlaanmeldingen');
});
let aanmeldingen = [];
// this is where I believe it gets stuck
apiRouter.get('/', (req, res) => {
console.log(db)
db.collection('kvdlaanmeldingen').countDocuments({}, function(err, result) {
console.log(result)
if (err) return console.log(err);
res.send(JSON.stringify(result));
})
});
module.exports = apiRouter;
The get request should be done as soon as this React component is rendered:
import React from 'react';
import './Aanmeldingen.css';
import { Link, Route } from "react-router-dom";
import XPress from './utils/Xpress.js';
import TaakComponent from './TaakComponent';
import { snakeCase } from "snake-case";
class Aanmeldingen extends React.Component {
constructor (props) {
super(props);
this.state = {
dataLoaded: 0,
taken: [// an array of different names that will be loaded as headers],
taakKlik: false,
taakData: null,
taakNaam: null,
}
}
componentDidMount(){
XPress.getTaken().then(data => {
console.log(data)
if (data) {
this.setState({
taakData: data,
dataLoaded: 1,
});
}
});
}
{...}
render(){
return (
<div className="Aanmeldingenpage">
<div className="statistics" onClick={this.aanmeldingen}>
<p className="statistics" id="counterAanmeldingen">{this.state.dataLoaded ? `Aantal aanmeldingen: ${this.state.taakData}` : 'Data aan het laden..'}</p>
</div>
</div>
);
}
}
and Xpress.getTaken is looking like this:
const XPress = {};
const baseUrl = window.location.origin;
XPress.getTaken = () => {
const url = `${baseUrl}/overview`;
return fetch(url, {method: 'GET'}).then(response => {
if (!response.ok) {
return new Promise(resolve => resolve([]));
}
return response.json().then(jsonResponse => {
return jsonResponse
}
)
})
}

The error you posted is often seen when parsing JSON fails. I guess this happens when fetch fails to parse the result in the frontend at this line: return response.json().then(jsonResponse => {.
Instead of returning valid JSON, the backend returns a file that starts with "<" (the unexpected token). Your backend responds with an HTML page instead of JSON.
Issue comes from here most likely:
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname + '/client/build/index.html'));
})
This basically says that all GET requests should serve index.html. That's why the request doesn't go to apiRouter.get('/'), it stops at the first match, which is the code above. It works on localhost because this code path is inside a conditional that checks NODE_ENV for production.
Not sure why you have it in there, but removing it would solve the issue.

Please try adding the heroku postbuild script to your json file in the root directory as same as the existence of the server.js file, that might help, using in react we must add heroku postbiuld so that the build is saved in the server, and that might not produce an issue,

Related

Put request req.body is empty

As all my requests are working fine, I have a problem with the put. req.body stays empty and then gives that error :
errmsg: "'$set' is empty. You must specify a field like so: {$set:
{: ...}}"
PUT :
router.put('/books/:name', (req, res, next) => {
const localdb = db.client.db(process.env.DB_NAME);
const collection = localdb.collection(process.env.COLL_BOOKS);
collection.replaceOne(
{ "name": req.params.name },
{ $set: req.body },
function (err) {
if (err) throw err
res.status(201).send(true);
});
App.js
const express = require('express'),
app = express();
os = require('os');
const bodyParser = require('body-parser');
const cors = require('cors');
const router = require('./router.js')
require('dotenv').config()
app.use(cors());
app.use(bodyParser.json());
app.use('/api/v1', router);
const port = (process.env.PORT || '3001');
let server = app.listen(port, os.hostname(), () => {
let host = server.address().address,
port = server.address().port;
console.log("Example app listening at http://%s:%s", host, port);
});
axios request :
updateItem = newBook => {
Axios.put(process.env.REACT_APP_API_PATH_BOOKS + `${newBook.name}`, newBook)
.then(res => {
this.setState({ newBook: res.data });
this.props.history.push('/admin');
})
.catch(err => console.log(err));
}
I don't understand what I am doing wrong
Make sure you don't have any middlware stripping or incorrectly parsing the body. For instance, you may have a JSON body parser, and not be sending JSON data with JSON application headers.
Can you give a bit of context, in code, for how you are making the put request and also the result of logging the req in a pastebin?

How can we implement an admin panel in nodeJS with express?

I am making a Portfolio application with nodeJS and express. I want to implement an admin panel which I can create,delete, update and edit my skills,experience,about etc, but I don't know how can I keep those admin routes secret and what kind of authentication to make.If we can do by putting Basic authentication on post,patch,delete route then how will we implement basic authentication on routes.
index.js
const express = require('express');
const app = express();
var cors = require('cors');
require('./db/mongoose')
const menuRouter = require('./routers/menu')
const skillRouter = require('./routers/skill')
const aboutRouter = require('./routers/About')
const experienceRouter = require('./routers/Experience')
const resumerouter = require('./routers/Resume')
const userRouter = require('./routers/user')
const port = process.env.PORT || 4000;
app.use(express.json());
app.use(cors());
app.use(menuRouter);
app.use(skillRouter);
app.use(aboutRouter);
app.use(experienceRouter);
app.use(resumerouter);
app.use(userRouter)
app.listen(port, () => {
console.log("Server is runing on port" + port)
});
skill.js
const express = require('express');
const Skill = require('../model/skill');
const router = new express.Router();
router.post('/skill', async (req, res) => {
const skill = new Skill(req.body);
try {
await skill.save();
res.status(201).send(skill);
} catch (e) {
console.log(e);
res.status(400).send(e);
}
})
router.get('/skill', async (rq, res) => {
try {
const skill = await Skill.find({});
res.status(201).send(skill);
} catch (e) {
res.status(400).send(e);
}
})
module.exports = router;
As specified in the comments, I would refactor your code a bit, seems messy and you're kind'a repeating yourself every line you import a route, so, you should do it better as well...
have an index.js file in your /routers folder with the content of the demo repo I've made for other StackOverflow question
then, to separate things, I would do something like:
const routes = require('./routes')
...
const protectRouteWithApiKey = (req, res, next) => {
const auth = req.headers['x-apikey']
if (auth && auth === '<YOUR API KEY>') return next()
return next(new Error('403 | Authorization is missing or value is wrong'))
}
...
app.use('/api', protectRouteWithApiKey, routes) // point to your routes and protect access
app.use('/', defaultEngine) // your engine to render html
you would then have a protected route in /api/* and normal routes for everything else
A middleware where you detect if the logged user is the admin?
In this sample checking by the email, and you can save the adminemail as a global variable
ensureAdmin: function(req, res, next) {
if (req.isAuthenticated()) {
if (req.user.email === adminemail) {
return next();
} else {
return res.redirect('/adminsecretroute');
}
}
res.redirect('/');
}

Express gives the same response even with different input-values in MongoDB

I'm new learning Express and MongoDB. I'm following an Udemy course, and I'm sure that my code is exactly the same.
My problem:
When I post some data to a MongoDB collection, it works as expected. But when I try to add a new value, it works, but inserts the same value that the first post, even when the inputs values are differents.
Here is some of my code:
pacienteControllers.js
const Paciente = require('../models/Paciente');
exports.newClient = async (request, response, next) =>{
const paciente = new Paciente(request.body);
try {
await paciente.save();
response.json({mensaje: 'El cliente se agregó correctamente'});
} catch (error) {
console.log(error);
next();
}
}
routes/index.js:
const express = require('express');
const router = express.Router();
const PacienteController = require('../controllers/PacienteControllers');
module.exports = () =>{
router.get('/', () =>{
console.log("Petición enviada");
})
router.post('/pacientes',
PacienteController.newClient
)
return router;
}
index.js:
const express = require('express');
const mongoose = require('mongoose');
const routes = require('./routes/index');
const bodyParser = require('body-parser');
const server = express();
mongoose.Promise = global.Promise;
mongoose.connect('mongodb+srv://AlexisDominguez:11399102a#my-free-cluster-ojd2d.mongodb.net/veterinaria?retryWrites=true&w=majority', {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false
});
server.use(bodyParser.json());
server.use(bodyParser.urlencoded({extended: true}));
server.use('/', routes());
server.listen(4000, () => console.log("servidor funcionando"));
Note: username and password are correct values, just censured for security reasons.
I would like to know why is this happening. ¿Is there some kind of cache?
TESTS
I'm using Postman to do posts tests. When I do the first post I get the message: El cliente se agregó correctamente meaning that the client was added successfuly.
But when I try to add a new register to the database, I get the same message but, when I update the database to see new changes, I get the new register but with the same values of the first post.
EDIT
Added server.use(bodyParser.json()); but still getting same results.
You only import routes folder but not index.js file in your root file index.js so import as
const routes = require('./routes/index');
You are sending message in JSON object But you didn;t use middleware in index.js so add
server.use(bodyParser.JSON());
Your routes and controller can be merged like this: In your routes/index.js file add this code:
const express = require('express');
const router = express.Router();
const Paciente = require('../models/Paciente');
router.post('/pacientes', async (req, res) => {
const paciente = new Paciente(req.body);
try {
const saveData = await paciente.save();
res.json({ message: "El cliente se agregó correctamente" });
}
catch ( err ) {
res.json({ message: err });
}
});
//You can view all the data from here
router.get('/', async (req,res) => {
const data = await Paciente.find();
try {
res.json(data);
} catch( err ) {
res.json({ message: err })
}
});
module.exports = router;
You can now remove pacienteController.js file

SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data on React app

I have a React app that uses express in the backend. I try to get from my db a list of messages through a fetch API call.
The code in the Frontend:
App.js
getMessages = () => {
fetch('/api/mess')
.then(res => res.json())
.then(res => {
var Messages = res.map(r => r.messages);
this.setState({ Messages });
});
};
The code in the backend:
api/mess.js
var express = require('express');
var Mess = require('../queries/mess');
var router = express.Router();
router.get('/', (req, res) => {
Mess.retrieveAll((err, messages) => {
if (err)
return res.json(err);
return res.json(messages);
})
});
router.post('/', (req, res) => {
var message = req.body.message;
Mess.insert(message, (err, result) => {
if (err)
return res.json(err);
return res.json(result);
});
});
module.exports = router;
queries/mess.js
const db = require('../database');
class Mess {
static retrieveAll(callback) {
db.query('SELECT * FROM mess;', (err, res) => {
if (err.error)
return callback(err);
callback(res);
});
}
static insert(mess, callback) {
db.query('INSERT INTO mess (messages) VALUES ($1)', [mess], (err, res) => {
if (err.error)
return callback(err);
callback(res);
});
}
}
module.exports = Mess;
index.js
const path = require('path');
const express = require('express');
const bodyParser = require('body-parser');
var db = require('./database');
const ENV = process.env.NODE_ENV;
const PORT = process.env.PORT || 3011;
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use('/api/mess', require('./api/mess'));
app.use('/api/roles', require('./api/roles'));
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}...`);
});
module.exports = app;
I get this error on my Frontend:
SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data
I have tried and changed every response and request to use either JSON.parse or .json and I get the same message no matter what.
When I use my browser and go to the api channel I get a correctly formatted JSON with the contents of the db.
Did I miss something?
EDIT:
The stack trace is super minimal:
When I try:
getMessages = () => {
fetch('/api/mess')
.then(res => console.log(res));
};
I get this object:
The problem is that the backend is at PORT 3011 and the api call is made to PORT 3000 from the frontend.
I need to have a proxy to point from the forntend to the backend port.
I need to add inside client/package.json (Frontend) this line:
"proxy": "http://localhost/3011"

Route.delete() requires a callback function but got a [object Object]

I have node-express app where I have bunch of Routes for login, logout and signup and one Route for checking authorised Route which can be accessed only through providing authToken. I moved the Routes to separate Route file and I got the above error.
This is my Users Routes File:
const express = require('express');
const authenticate = require('./../middleware/authenticate');
const router = express.Router();
const {User} = require('./../models/user');
router.post('/',(req, res) => {
var body = _.pick(req.body,['email','password']);
var user = new User(body);
user.save().then(() => {
return user.generateAuthToken()
}).then((token) => {
res.header('x-auth', token).send(user);
}).catch((e) => {
res.status(400).send(e);
});
});
router.post('/login',(req, res) => {
var body = _.pick(req.body, ['email', 'password']);
User.findByCredentials(body.email, body.password).then((user) => {
return user.generateAuthToken().then((token) => {
res.header('x-auth', token).send(user);
});
}).catch((e) => {
res.status(400).send(e);
});
});
router.delete('/logout',authenticate, (req, res) => {
req.user.removeToken(req.token).then(() => {
res.status(200).send();
},(e) => {
res.status(400).send(e);
}) ;
});
router.get('/me',authenticate, (req,res) => {
res.send(req.user);
});
module.exports = router;
Following is my main server.js file:
const express = require('express');
const _ = require('lodash');
var app = express();
const usersRoutes = require('./routes/users');
app.use(express.json());
app.use('/users', usersRoutes);
var {mongoose} = require('./db/mongoose');
var {User} = require('./models/user');
var {authenticate} = require('./middleware/authenticate');
const port = process.env.PORT || 3000 ;
app.listen(port, () => console.log(`Listening on ${port}...`))
I have a model/Schema(mongoose) file for User so If You feel you need that I am ready to edit my question. Thanks.
The problem is that router.delete is expecting a function on the middleware parameter (like you did in your server.js file with app.use(express.json())) so it can be used like a callback which gets called whenever a request reach your route.
Try changing authenticate to authenticate().
It seems like in your users routes file you are importing the entire module who contains the authenticate function, so when try to access it like a function you'll get an error. You need to import it like you did in your server.js file.
Change the line const authenticate = require('./../middleware/authenticate'); for const {authenticate} = require('./../middleware/authenticate');.

Categories