I've expanded on a chat app project from a node course that utilizes express and socket.io. I've incorporate more routes that facilitate /signup /login /logout functionality. Everything works perfectly in the local environment. If I simply change my mongodb URI to my mLab sandbox link, the app's login route breaks. I have searched countless threads for a solution, but none of the suggestions have worked. I am reasonably new to node, so I may just be missing something that would be obvious to seasoned devs.
Here's the error that is thrown:
events.js:167
throw er; // Unhandled 'error' event
^
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:470:11)
at ServerResponse.header (/Users/B/Desktop/shadow-gab/node_modules/express/lib/response.js:767:10)
at ServerResponse.send (/Users/B/Desktop/shadow-gab/node_modules/express/lib/response.js:170:12)
at User.findOne (/Users/B/Desktop/shadow-gab/server.js:128:27)
at /Users/B/Desktop/shadow-gab/node_modules/mongoose/lib/model.js:4533:16
at process.nextTick (/Users/B/Desktop/shadow-gab/node_modules/mongoose/lib/query.js:2584:28)
at process._tickCallback (internal/process/next_tick.js:61:11)
Emitted 'error' event at:
at /Users/B/Desktop/shadow-gab/node_modules/mongoose/lib/model.js:4535:13
at process.nextTick (/Users/B/Desktop/shadow-gab/node_modules/mongoose/lib/query.js:2584:28)
at process._tickCallback (internal/process/next_tick.js:61:11)
[nodemon] app crashed - waiting for file changes before starting...
Here is the POST route that crashes the app:
app.post('/chat', (req, res) => {
if (!req.session.screenname) {
console.log(req.body);
User.findOne({ screenname: req.body.screenname }, (err,doc) => {
if (err) {
console.log(err);
res.status(500).send('error occured');
} else if (!doc) {
res.status(500).send('Account doesn\'t exist. Please sign up.');
} else if (doc) {
let valid = bcrypt.compareSync(req.body.password, doc.password);
if (!valid) {
res.status(500).send('Incorrect Password');
} else if (valid) {
req.session.screenname = req.body.screenname;
console.log(req.session);
io.on('connection', (socket) => {
console.log('New user connected');
socket.on('join', (params, callback) => {
if (!isRealString(req.body.screenname) || !isRealString(req.body.room)) {
return callback('Name and room name are required');
}
socket.join(req.body.room);
users.removeUser(socket.id);
users.addUser(socket.id, req.body.screenname, req.body.room);
io.to(req.body.room).emit('updateUserList', users.fetchUserList(req.body.room));
socket.emit('newMessage', generateMessage('Admin', `Welcome to ${req.body.room} room`));
socket.broadcast.to(req.body.room).emit('newMessage', generateMessage('Admin', `${req.body.screenname} has joined`));
callback();
});
});
}
}
});
}
res.sendFile(path.join(__dirname + '/public/chat.html'));
});
It would seem that the most common response to similar questions have been to look for places where you try to make more than one response to the request, but I don't see anywhere where i do that.
Again, I can simply switch the db url to my local machine and everything works perfect. Any direction would be greatly appreciated
EDIT: The app is broken on the first login attempt, but if I use the logout route and then log back in, the app works. Still not sure what is causing this
Related
I'm trying to make a simple login system using express sessions, but I keep getting a "Invalid status code" when the user is not found in the database.
I want to make it so when the user could not be found in the database, it redirects the client to a specific route. But instead of redirecting the client, it throws an error.
It works perfectly fine if it finds the user, no bugs.
If someone could help guide me to the solution of the problem, that would be greatly appreciated!
Log in route
//#ROUTE: Log in route
//#DESCRIPTION: Renders the login page
app.get('/login', (req, res)=> {
res.render('login.ejs', {title: "Login :: Clipit", curSession: req.session})
})
Log in post
//#ROUTE: Log in post
//#DESCRIPTION: Finds the user in the database, and logs him in (creates a new session)
app.post('/api/login', (req, res)=> {
const username = req.body.inputUsername
const password = req.body.inputPassword
userDB.findOne({username: username, password: password}, (err, result)=> {
if(err) {
throw err;
} else if(!result) {
res.redirect('/login-error', {curSession: req.session})
} else {
req.session.username = result.username
req.session.loggedIn = true
console.log(`${req.session.username} has logged in.`)
res.redirect('/')
}
})
})
Bug:
express deprecated res.redirect(url, status): Use res.redirect(status, url) instead server.js:185:17
node:events:346
throw er; // Unhandled 'error' event
^
RangeError [ERR_HTTP_INVALID_STATUS_CODE]: Invalid status code: { curSession: [Session] }
at new NodeError (node:internal/errors:329:5)
at ServerResponse.writeHead (node:_http_server:282:11)
at ServerResponse.writeHead (C:\Users\gabri\Desktop\clipit\node_modules\on-headers\index.js:44:26)
at ServerResponse._implicitHeader (node:_http_server:273:8)
at writetop (C:\Users\gabri\Desktop\clipit\node_modules\express-session\index.js:276:15)
at ServerResponse.end (C:\Users\gabri\Desktop\clipit\node_modules\express-session\index.js:356:16)
at ServerResponse.redirect (C:\Users\gabri\Desktop\clipit\node_modules\express\lib\response.js:951:10)
at C:\Users\gabri\Desktop\clipit\server.js:185:17
at C:\Users\gabri\Desktop\clipit\node_modules\mongoose\lib\model.js:4870:18
at processTicksAndRejections (node:internal/process/task_queues:76:11)
Emitted 'error' event on Function instance at:
at C:\Users\gabri\Desktop\clipit\node_modules\mongoose\lib\model.js:4872:15
at processTicksAndRejections (node:internal/process/task_queues:76:11) {
code: 'ERR_HTTP_INVALID_STATUS_CODE'
}
PS C:\Users\gabri\Desktop\clipit>
There is one error and one warning in your console:
The Warning : express deprecated res.redirect(url, status): Use res.redirect(status, url) instead server.js:185:17.
This is about the order of the params. RIght now they would work in any order. But this also gives you a hint as to what is causing the error. The params for res.redirect() are supposed to be status and url.
You cannot add an object to the params. It has to be a status code and a path.
You might want to do res.render('/login-error', {curSession: req.session}) (depending on your views) or simply res.redirect('/login-error').
So, in this piece of code I'm trying to use findOne to find and delete a particular dishId from my Favorites document. The code is working fine if I send a valid dishId but when I enter a wrong dishId the code does throw err; and the Node server stops with this error.
events.js:292
throw er; // Unhandled 'error' event
^
TypeError: Cannot read property 'dishes' of null
And then I've to do npm start again. So how should I tackle this? I don't want server to stop. I want it to keep going so that I can do further requests. Here's my code.
favoriteRouter.route('/:dishId')
.delete(cors.corsWithOptions, authenticate.verifyUser, (req,res,next) => {
Favorites.findOne({user: req.user._id, dishes: req.params.dishId} ,(err, favdel) => {
if(err) {
throw err;
}
else {
favdel.dishes.pull({_id:req.params.dishId});
favdel.save();
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.json(favdel);
}
})
});
The error is pretty clear: you're blindly accessing a property without first checking whether it exists, so: check if favdel.dishes exists before you try to get data out of it. And if it doesn't, make error handling kick in, in a way that makes sure to send the correct HTTP error code.
...
if (!favdel || !favdel.dishes) {
// only you know which 4xx or even 5xx error this should be
return next(new Error("..."));
}
favdel.dishes.pull({_id:req.params.dishId});
favdel.save();
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.json(favdel);
...
I'm sure this is a novice question, but I've only been having a go at learning express/node/react for a month or so now.
I'm just trying to make a simple node signin REST API call. Here's a snippet of code, with some 'pseudo-izing' of the unimportant parts for brevity:
server.post('/signin', (request, response) => {
const {user_email, password} = request.body
// query db for user validation
db('user_login')
/* knex query building, blah blah blah */
.then(res => {
if (res.length == 0) {
// if res.length == 0, user not found
throw new Error("bad credentials")
} else if (res.length > 1) {
// if res.length > 1, duplicate user found - shouldn't ever happen
throw new Error("CRITICAL: database error")
} else {
// everything should be ok - pass res on to bcrypt
return res
}
})
.then(res => {
// bcrypt.compare doesn't return a promise because it is being given a cb
bcrypt.compare(password, res[0].pw_hash, (err, match) => {
if (match) {
// delete pw_hash from any possible response(),
// don't give client more info than it needs
delete res[0].pw_hash
// we have a match! inform the client
response.json(res[0])
} else {
// we don't have a match
throw new Error("bad credentials") // WHY DOES THIS THROW CRASH!??!?!!?!?
}
})
})
// WHY ISNT THIS REACHED WHEN THERE'S A PASSWORD MISMATCH?
.catch(err => {
console.error('signin error: ', err)
response.status(403).json({
name: err.message,
severity: 'auth error',
code: 403
})
})
})
Ok so:
- When a correct username and correct password is supplied, it functions as expected.
- When an incorrect username is supplied, the .catch is reached (ie. functions as expected).
- BUT: when a correct username and an incorrect password is supplied, the throw statement (with the comment // WHY DOES THIS THROW CRASH?)... crashes node.
Here's the call stack:
C:\.............\server.js:83
throw new Error("bad credentials") // WHY DOES THIS THROW CRASH!??!?!!?!?
^
Error: bad credentials
at C:\.............\server.js:83:23
at C:\.............\node_modules\bcrypt-nodejs\bCrypt.js:689:3
at processTicksAndRejections (internal/process/task_queues.js:75:11)
[nodemon] app crashed - waiting for file changes before starting...
I could "cheat" and just do a response.status(403).... instead of that throw. But in my mind, throwing that error ought to jump to the .catch, and handle any auth failure there.
Can anyone help me w/ what's going on here? It's very frustrating.
PS. This crash only seems to be happening when the throw is within the bcrypt.compare callback. I figure that has something to do with it. I've googled.. I've looked around on here.. I've wrapped things in try/catch blocks. I'm just beating my head against it at this point.
Thank you! :)
Do not throw an error from Node like this. You notice that the bcrypt callback has an err parameter. Send that back and handle it how you want to in the back or front.
I have the following function for creating an user in my app, I'm trying to detect if threre's already an admin user created and prevent for creating another one.
export async function createUser (ctx) {
if ( ctx.request.body.type == undefined ) {
ctx.throw(400, 'Bad Request')
}
if (ctx.request.body.type === 'admin') {
User.findOne({type:'admin'}, (err, usr) => {
if (err)
ctx.throw(422, err.message)
if (usr){
ctx.throw(400, 'Duplicate Admin')
}
})
}
....
The first ctx.throw(400, 'Bad Request') works, but if another admin user is found the ctx.throw(400, 'Duplicate Admin') will cause the following error, crashing the app:
events.js:160
throw er; // Unhandled 'error' event
^
BadRequestError: Duplicate Admin
I'm throwing the error in an inadequate manner? what causes the first throw to work without crashing but not the second?
Thanks in advance for any help
You're throwing asynchronously inside the User.findOne callback. This crashes.
Instead, look at the library you're using that gives you User.findOne and see if it can return a promise, or wrap it in bluebird's Promise.promisify if it doesn't.
This is the code you want to arrive at:
const user = await User.findOne({ type: 'admin' }) // returns a promise
if (user) ctx.throw(400, 'Duplicate admin')
At first, check the type of 'user' variable.If it an array or object, if(user){....} will always return true, even if it's empty. And try to add
app.on('error', function() {
console.log('yep this is an error');
});
event listener
I'm not really sure why I'm getting this error. It's a simple API built on express.js to be able to add and remove posts. The error occurs when I trigger the delete router. I've read that the error typically happens when there are two callbacks, however, I don't seem to be able find any double callbacks.
_http_outgoing.js:344
throw new Error('Can\'t set headers after they are sent.');
Error: Can't set headers after they are sent.
at ServerResponse.OutgoingMessage.setHeader (_http_outgoing.js:344:11)
at ServerResponse.header (/Users/bounty/Projects/_learning/react-express/node_modules/express/lib/response.js:718:10)
at ServerResponse.send (/Users/bounty/Projects/_learning/react-express/node_modules/express/lib/response.js:163:12)
at ServerResponse.json (/Users/bounty/Projects/_learning/react-express/node_modules/express/lib/response.js:249:15)
at /Users/bounty/Projects/_learning/react-express/server/routes/posts.js:86:9
at nextTickCallbackWith0Args (node.js:452:9)
at process._tickCallback (node.js:381:13)
Here is my posts.js router:
module.exports = function(router) {
var Post = require('../models/post.js');
// middleware for the api requests
router.use(function(req, res, next) {
// do logging
console.log('something is happening.');
next(); // make sure we go to our next route and don't stop here
});
// test route to make sure everything is working (accessed at GET http://localhost:8080/api)
router.get('/', function(req, res) {
res.json({ message: 'hooray! welcome to our api!' });
});
// all routes here
// routes that end in /posts
router.route('/posts')
// create a Post (accessed at POST http://localhost:7777/api/posts)
.post(function(req, res) {
var post = new Post();
post.postTitle = req.body.postTitle; // set the post name (comes from request)
// save post and check for errors
post.save(function(err) {
if (err)
res.send();
res.json({ message: 'post created!' });
});
})
// get all Posts (accessed at GET http://localhost:7777/api/posts)
.get(function(req, res) {
Post.find(function(err, posts) {
if (err)
res.send();
res.json(posts);
});
});
// routes that end in /posts for specific id
router.route('/posts/:post_id')
// get the post with that id
.get(function(req, res) {
Post.findById(req.params.post_id, function(err, post) {
if (err)
res.send(err);
res.json(post);
});
})
// update the post with that id
.put(function(req, res) {
Post.findById(req.params.post_id, function(err, post) {
if (err)
res.send(err);
post.postTitle = req.body.postTitle;
// save the post
post.save(function(err) {
if (err)
res.send(err);
res.json({ message: 'post updated!' });
});
});
})
// deletes the post with that id
.delete(function(req, res) {
Post.remove({
_id: req.params.post_id
}, function(err, post) {
if (err) {
res.send(err);
}
res.json({ message: 'post deleted!' });
});
});
}
You need to add the 'return' so that you don't reply twice.
// save post and check for errors
post.save(function(err) {
if (err) {
return res.send();
}
res.json({ message: 'post created!' });
});
That particular error message is pretty much always caused because of a timing error in the handling of an async response that causes you to attempt to send data on a response after the response has already been sent.
It usually happens when people treat an async response inside an express route as a synchronous response and they end up sending data twice.
One place I see you would get this is in any of your error paths:
When you do this:
// save post and check for errors
post.save(function(err) {
if (err)
res.send();
res.json({ message: 'post created!' });
});
If post.save() generates an error, you will do res.send() and then you will do res.json(...) after it. Your code needs to have a return or an else so when there's an error you don't execute both code paths.
So, this can happen in Express when attempting to send res.end twice which res.send and res.json both do. In your if(err) block you'll want to return res.send() as res.send runs asynchronously and res.json is getting called as well. I'm wondering if you're getting an error in your delete route? Hope this helps.
Best!
You are using res.send() or res.json() twice in the same request
this send the headers first, followed by body of the response and then headers again.
req.next is usually not a function, next is rather passed as a third argument of the middleware. Use that if you want to drop to the next middleware. (assuming you are using Express framework)
Just for the sake of completeness I will also mention that:
Sometime problem may be in a the middleware you may be using by calling
app.use.
After checking for obvious errors as mentioned in previous answers:
You should remove all the app.use statement then reintroduce them one by one, to find problematic module.
If you are using res.send() inside any loop, then you need to break it after the use of res.send(). So that it won't allow resetting of the res headers again and again.
for e.g :
for(){
if(){
res.send();
break;
}
else(){
res.send();
break;
}
}
In my case this is the problem and I solved it like this.
Hope it may help someone in future.
Thanks
For a quick fix you can just check res.finished before calling res.send():
if (!res.finished)
res.send()