0

Edit: I've confirmed the issue is in my validateToken middleware and have appended that code to the bottom.

I am creating a ToDo application that utilizes a node back-end and MongoDB to store user accounts/data and am having difficulties fetching a user's stored data.

The front-end issues an API call to /api/todos/fetch on the back-end each time the application is loaded, and it sends the user's token in the Authorization header which is then decrypted to grab the user's username so it can be used to query their data from the database.

Here is the API endpoint, /api/todos/fetch:


    router.get('/fetch', validateToken, (req, res) => {
        const user = req.decodedJwt.username;
        Todo.find({username: user})
            .then(data => {
                console.log(query)
                res.json(data)
            })
            .catch(err => {
                console.log(err)
                res.json(err)
            })
    })

Here is the Todo Schema:


    const Todo = new Schema(
        {
            username: { type: String, required: true, unique: true },
            todos: []
        },
    );
    
    module.exports = mongoose.model('todos', Todo)

The issue I am running into is it seems that Todo.find() is executing the .then() before actually resolving the promise, which in Postman is telling me Cannot GET /api/todos/fetch and in my Node console I am getting Cannot set headers after they are sent to the client and node:internal/process/promises:246 - triggerUncaughtException(err, true /* fromPromise */); as well as a lot of other stack feedback.

Todo.find() is working and grabbing the correct data from the database and I can log it to the console, but it only logs to the console after the HTTP response has been sent which is what's confusing me. My understanding is Todo.find() will execute and if it is successful, meaning the data was grabbed, then .then(data) portion will fire with the resolved information, otherwise the .catch(err) will kick back the error response. I have tried async/await and try/catch as well. I've tried pulling the find() functionality into a separate async helper function. I've done the same db operations with SQLite and PostgreSQL, but this is the first time I've used MongoDB and Mongoose. Sorry if this is redundant or has been asked, I've spent half a dozen hours so far reading through documentation and other user's questions.

Edit: Here is my validateToken middleware:

module.exports = (req, res, next) => {
const token = req.headers.authorization;
if (token) {
    jwt.verify(token, jwtSecret, (err, decoded) => {
        if (err) {
            console.log("\n!!!~~~Sending response~~~!!!\n")
            res.status(401).json('Please supply a valid token')
        } else {
            req.decodedJwt = decoded;
            next();
        }
    })
} else {
    console.log("\n!!!~~~Sending response~~~!!!\n")
    res.status(401).json("You must have a token to do that");
}
next();

}

Currently neither of the console.log's are firing when I use the middleware, and the data makes it to the /fetch endpoint after the middleware.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Archa9
  • 13
  • 3
  • Your code should work fine. "*Cannot set headers after they are sent to the client*" means that some **other** code has already sent a response (or at least, headers) – Bergi May 25 '21 at 23:54
  • I would recommend to [use `.then(…, …)` instead of `.then(…).catch(…)`](https://stackoverflow.com/q/24662289/1048572), the latter might lead to more confusing error messages if the `res.json()` in the `then` handler throws, causing the `catch` handler to run and call `res.json()` again (and again causing an error). This is not a solution to the underlying problem, but might make the "stack feedback" more useful. – Bergi May 26 '21 at 00:00
  • I'll definitely give that a try! I added logs to the few places my code sends back a response and none of them are hitting. The only time this endpoint breaks is when I add the ```Todo.find()``` call, but that shouldn't be sending a response back before the ```.then()```. – Archa9 May 26 '21 at 00:31
  • You're saying the `/fetch` endpoint works when you call `res.json(someStaticSampleData)` synchronously, instead of from an async callback? Does the error happen if you use `setTimeout` instead of `Todo.find`? – Bergi May 26 '21 at 00:37
  • If just after the ```const user``` line I do ```res.json(user)``` then it works, otherwise it doesn't. Interestingly enough if I do ```setTimeout(()=>{res.json("message")},1500)``` after the ```const user``` line then Postman immediately gets the ```Cannot GET /api/todos/fetch``` response and 1.5s later I get the same output from my Node console about setting HTTP headers after they've already been sent to the client. Edit: Premature enter. This should confirm that it isn't necessarily the ```.find()``` functionality breaking since the timeout should work – Archa9 May 26 '21 at 00:44
  • Yes, I was pretty certain it's not related to mongodb. What exactly does postman receive as the response when it "*Cannot GET*", is that a 404? Something in your server code sends a response on that route. – Bergi May 26 '21 at 00:47
  • Assuming you're using express, try [its debugging options](https://expressjs.com/en/guide/debugging.html) – Bergi May 26 '21 at 00:51
  • So I ripped out my middleware and hardcoded the username into the ```/fetch``` endpoint with the ```Todo.find()``` and that is working as intended. I put my middleware code in the bottom of the main post if you're familiar with JWT or see any glaring mistakes with the logic. It should decode the token and save the username in ```req.decodedJwt``` which it was doing, but I'm not sure what about it is causing everything to break. I'll look into the express debugging options in the meantime. – Archa9 May 26 '21 at 01:00

1 Answers1

0

Your validateToken middleware calls next() twice! Remove the second one.

module.exports = (req, res, next) => {
    const token = req.headers.authorization;
    if (token) {
        jwt.verify(token, jwtSecret, (err, decoded) => {
            if (err) {
                res.status(401).json('Please supply a valid token')
            } else {
                req.decodedJwt = decoded;
                next();
            }
        })
    } else {
        res.status(401).json("You must have a token to do that");
    }
    // next();
    // ^^^^^^^
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Removing that and setting everything back to how it was cleared the problems right up and now everything is working as intended, whew :) Thank you so much for the troubleshooting assistance and the resources you provided! – Archa9 May 26 '21 at 01:13