Why is res.json() returning null? - javascript

I'm trying to code the authentification part of my react app using jwt. I kept getting this error in my App.js file: 'Uncaught (in promise) TypeError: Cannot read properties of null (reading 'error')', which led me to find out that when a certain route is accessed, which should return some data about the user, the response body is actually empty. These are some snippets of my code, where I think the problem might be. Any help would be much appreciated, thanks in advance! Also, please let me know if I should provide other snippets of code
This is from my App.js file
const [authState, setAuthState] = useState({
username: "",
id: 0,
status: false,
});
useEffect(() => {
axios
.get("http://localhost:3001/users/auth", {
headers: {
accessToken: localStorage.getItem("accessToken"),
},
})
.then((response) => {
if (response.data.error) { //this is where the error appears
setAuthState({ ...authState, status: false });
} else {
setAuthState({
username: response.data.username, //if I comment the line where the error appears, I get the same error here and on the line below, but with 'username' and 'id' instead of 'error'
id: response.data.id,
status: true,
});
}
});
}, []);
This is where res.json doesn't return anything
router.get('/auth', validateToken, (req, res) => {
res.json(req.user);
});
This is the validateToken middleware
const { verify } = require('jsonwebtoken');
const validateToken = (req, res, next) => {
const accessToken = req.header("accessToken");
if (!accessToken) return res.json({ error: "User not logged in!" })
try {
const validToken = verify(accessToken, "importantsecret");
req.user = validToken; //we can access the username and id
if (validToken) {
return next();
}
} catch (err) {
return res.json({ error: err });
}
};
module.exports = { validateToken };

Okay, so in your middleware function, in this particular line,
const validToken = verify(accessToken, "importantsecret");
req.user = validToken; //we can access the username and id
if (validToken) {
return next();
}
Here, you are creating a constant variable named "validToken" and setting req.user to it. And then, you are checking if it exists, then run next(), but what if validToken is null/undefined? next() is never ran then!

Maybe it's because you literally don't return your response.
Try this one.
router.get('/auth', validateToken, (req, res) => { return res.json(req.user); });

Related

Why is this user id not coming out of req.user;

This is my code for getting user info
router.post("/getuser", fetchuser, async (req, res) => {
try {
userId = req.user.id;
const user = await User.findById(userId).select("-password");
res.send(user);
} catch (error) {
console.error(error.message);
res.status(500).send("Internal Server Error");
}
});
and this is the code for middleware fetchuser
const fetchuser = async (req, res, next) => {
const token = req.header('auth-token');
if (!token) {
res.status(401).send({ error: "Please authenticate using a valid token" })
}
try {
const data = jwt.verify(token, process.env.SECRET);
console.log(data);
req.user = data.user;
next();
} catch (error) {
res.status(401).send({ error: "Please authenticate using a valid token" })
}
};
I am getting the user id in console.log but when I try to get the user id in the router.post then I am not able to get the user Info.
Here is the result I am getting.
Server is running on port 5000
Connected to database
{ id: '61e98c45a9d8818292b38505', iat: 1642743501 }
Cannot read properties of undefined (reading 'id')
Please can anyone tell me what is wrong with this?
router.post("/getuser", fetchuser, async (req, res) => {
try {
const params = JSON.parse(req)
userId = params.id;
const user = await User.findById(userId).select("-password");
res.send(user);
} catch (error) {
console.error(error.message);
res.status(500).send("Internal Server Error");
}
});
Your code is working fine , you just have to change this line on fetchuser middleware
req.user = data.user
to
req.user = data
and your code work as expected..
add file: src/types/express/index.d.ts
content:
declare namespace Express {
interface Request {
user: {
id: string;
};
}
}

Troubles with nodejs GET endpoint

I have the following endpoint:
app.get('/users/:id', async (req, res) => {
const _id = req.params.id;
try {
const user = await User.findById(_id);
if(!user) {
res.status(404).send();
}
res.send(user);
} catch (e) {
res.status(500).send(e);
}});
When I make the request with a valid user ID, the server sends back the user, no problem with that.
The problem is when I try to find a user with a ID which doesnt exist in the database. The server should response with a 404 Error but instead it sends back a Error 500 and I dont understand why!
Could anyone help me please?
Thank you in advance!
One nice way to handle the errors is to create an express error middleware, this allows you to put all of your error handling in one place so that you dont have to write it more than once.
With express when you use async routes handlers if a promise rejects the error will automatically be passed to the next error middleware.
// First register all of your routes
app.get('/user/:id', async (req, res) => {
const user = await User.findById(req.params.id);
if(!user) return res.status(404).send();
res.send(user);
})
// Then register you error middleware
app.use((err, req, res, next) => {
console.error(err.message)
// if mongoose validation error respond with 400
if(err.message.toLowerCase().includes('validation failed'))
return res.sendStatus(400)
// if moongoose failed because of duplicate key
if(err.message.toLowerCase().includes('duplicate key'))
return res.sendStatus(409)
// if mongoose failed to cast object id
if(err.message.toLowerCase().includes('objectid failed'))
return res.sendStatus(404)
res.sendStatus(500)
})
Thank you for your answers.
I have solved it adding the following to the user model schema:
_id: {type: String}
And adding a return before sending the 404 error:
app.get('/users/:id', async (req, res) => {
const _id = req.params.id;
try {
const user = await User.findById(_id);
if (!user) {
return res.status(404).send();
}
res.send(user);
} catch (error) {
res.status(400).send(error);
}});

OPTIONS request inconsistency (in deployment)?

Introduction
So, I'm using the MERN stack (with Heroku + Netlify), and I'm having some really strange consistency problems with how the DELETE request is being handled. I've tried countless solutions in the last
three days trying to get this to work and none of them have worked. A lot of these solutions have
come from stack overflow, so if you want to direct me to another post, the chance is that I've already seen it. I've scoured every part of the web and making this post is my last resort.
The Problem
So, when I make a delete request, I'm getting the per-usual OPTIONS request since I'm sending a token in a custom header of the request ('x-auth-token'). The OPTIONS request always resolves with a 204, meaning that everything should be alright. However, afterward, there is no DELETE request like there should be. This, in essence, is my problem. I've checked my Heroku logs, and all I can see is the OPTIONS request, and nothing else.
Inconsistencies?
So this is where I've been very confused. The thing is, that sometimes it DOES work. And other routes I use in my API (like login, and creating a new post) work, even though I'm using the same middleware.
Every time it works, I get the OPTIONS request and then the DELETE request (with a 200 status) like I would expect to happen.
If you want an example of a re-creatable scenario:
I create X number posts after logging in and getting a valid token, then I can see those posts rendering in the posts listing on my home page. I then navigate one of the posts and delete it by clicking and then a confirmation button. I automatically get redirected to the next post in the list. I repeat this till I get to the last post. I delete that post, and since there are no more posts left, I get redirected to the posts listing which is... not empty! The last post I tried deleting is still there.
Keep in mind, that the DELETE requests all get sent in exactly the same way, so I'm pretty sure this isn't a front-end issue, so no need to poke around in the code there. I've logged everything and debugged, and it's 100% consistent with what I would expect.
(The create post doesn't redirect, while the delete post does? I don't see how this would effect anythign as the DELETE request gets sent as per usual... Though maybe a solution lies within this fact.)
Solutions I've tried
Cors
First off, you might already be rushing to your keyboard to tell me that this is a CORS issue. I thought the same thing yesterday, but I'm not so sure now. I've tried messing with all the config settings possible in CORS to get this to work. Since my two websites are on different domains, then CORS verifies the requests. I've already added my front-end website to a whitelist, and all other requests are going through properly, so no problem there. I've tried adding an allowHeaders option in the config, but it didn't do anything more than the default setting. I've also added 'OPTIONS' to the allowed methods in the config, still nothing. I'm also using app.use(cors({config})). I'll include some code later to see some more of this in detail.
Debugging
I've basically tested things out by inserting console.logs everywhere and discovered that neither the middleware, the options route (I tried making an options route with same route url), or the original post route get executed when the OPTIONS request doesn't result in a DELETE request.
Static Server
This is maybe where some of my inexperience shows (this is my first Web project). I saw some solutions telling that a static server is needed. So I tried setting up a static server, but I didn't see any results. So I'm not too sure what this accomplished.
Async and Await
I was just trying things at this point, so I made all my routes async to see if it would do anything. It didn't.
Others
I've also messed around with environment variables and dotenv, and other stuff I can't remember. I think everything here should already be sufficient information understand the situation.
Code
index.js
const express = require('express');
require("dotenv").config({ path: "variables.env" });
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const routes = require("./routes/router");
const cors = require("cors");
const morgan = require('morgan')
const app = express();
const whitelist = [
process.env.ORIGIN
];
app.use(
cors({
origin: function (origin, callback) {
if (whitelist.indexOf(origin) !== -1) {
callback(null, true);
} else {
console.log(origin);
callback(new Error("Not allowed by CORS"));
}
}, //frontend server localhost:3000
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
credentials: true, // enable set cookie
}));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(morgan('dev'));
mongoose.connect(process.env.MONGODB_URL, {
useNewUrlParser: true,
useCreateIndex: true,
useUnifiedTopology: true
});
const db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', () => {
console.log('connected to db');
});
const userSchema = mongoose.Schema({
name: String,
password: String
});
// Routes
// TODO: make seperate routers/routes
app.use("/", routes);
// Serve static assets if in production
if (process.env.NODE_ENV === 'production') {
// Set static folder
app.use(express.static('client/build'));
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html'));
});
}
// TODO: set up custom port in future
app.listen(process.env.PORT, () => console.log(`Server listening at http://localhost:${process.env.PORT}`));
// Callback functions?
router.js
const express = require('express');
const router = express.Router();
const Post = require('../models/Post');
const User = require('../models/User');
const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')
const auth = require('../middleware/auth');
const adminAuth = require('../middleware/adminAuth');
const cors = require("cors");
require("dotenv").config({ path: "variables.env" });
// import 'moment'
// second onwards are handlers => triggers like the post body then next() to go to the next handler
router.post('/api/add_post', adminAuth, async (req, res, next) => {
try{
newPost = new Post({
title: req.body.title,
body: req.body.body,
author: req.body.author,
created: req.body.created,
});
const savedPost = await newPost.save();
if (!savedUser) throw Error('Something went wrong saving the post');
res.send(savedPost);
} catch (e) {
res.status(400).json({ msg: e.message });
}
});
router.delete('/api/delete_post/:id', adminAuth, async (req, res, next) => {
// timeout?
// console.log(req.body);
try{
const id = req.params.id;
if(!id) throw Error('Invalid ID');
const post = await Post.findById(id);
if (!post) throw Error('Post doesn\'t exist');
const removed = await post.remove();
if(!removed) throw Error('Problem with deleting the post');
res.status(200).json({ success: true });
} catch(e) {
console.log("Error: ", e.message);
res.status(400).json({ msg: e.message, success: false });
}
});
// TODO : UPDATE for async soon
router.post('/api/update_post', adminAuth, async (req, res, next) => {
const id = req.body._id;
test_post_data = {
title: req.body.title,
body: req.body.body,
author: req.body.author,
modified: req.body.modified,
};
console.log(test_post_data, id);
Post.updateOne({ _id: id }, test_post_data, (err) => {
if(err) return next(err);
return res.status(200);
});
});
router.get('/api/get_posts', async (req, res, next) => {
try{
const posts = await Post.find();
if(!posts) throw Error('Error with fetching the posts')
res.send(posts.reverse());
} catch (e) {
res.status(400).json({ msg: e.message });
}
});
router.get('/api/get_chapter/:id', async (req, res, next) => {
try{
const id = req.params.id;
const post = await Post.findOne({_id: id})
if(!post) throw Error('No post was found')
res.send(post);
} catch(e) {
res.status(400).json({ msg: e.message })
}
});
// User routes
// TODO : make in seperate file
router.post('/api/user/register', async (req, res) => {
const { name, email, password } = req.body;
// Simple validation
if (!name || !email || !password) {
return res.status(400).json({ msg: 'Please enter all fields' });
}
try {
const user = await User.findOne({ email });
if (user) throw Error('User already exists');
const salt = await bcrypt.genSalt(10);
if (!salt) throw Error('Something went wrong with bcrypt');
const hash = await bcrypt.hash(password, salt);
if (!hash) throw Error('Something went wrong hashing the password');
const newUser = new User({
name,
email,
password: hash,
admin: false
});
const savedUser = await newUser.save();
if (!savedUser) throw Error('Something went wrong saving the user');
// TODO : check up on expires stuff : 3600 = 1 hr
const token = jwt.sign({ id: savedUser._id, admin: savedUser.admin }, process.env.JWT_SECRET, {
expiresIn: 3600
});
res.status(200).json({
token,
user: {
id: savedUser.id,
name: savedUser.name,
email: savedUser.email,
admin: savedUser.admin
}
});
} catch (e) {
res.status(400).json({ error: e.message });
}
});
router.post('/api/user/login', async (req, res) => {
const { name, password } = req.body;
// Simple validation
if (!name || !password) {
return res.status(400).json({ msg: 'Please enter all fields' });
}
try {
// Check for existing user
const user = await User.findOne({ name });
if (!user) throw Error('User Does not exist');
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) throw Error('Invalid credentials');
const token = jwt.sign({ id: user._id, admin: user.admin }, process.env.JWT_SECRET, { expiresIn: 3600 });
if (!token) throw Error('Couldnt sign the token');
res.status(200).json({
token,
user: {
id: user._id,
name: user.name,
email: user.email,
admin: user.admin
}
});
} catch (e) {
res.status(400).json({ msg: e.message });
}
});
module.exports = router;
adminAuth.js
const jwt = require('jsonwebtoken')
require("dotenv").config({ path: "variables.env" });
module.exports = (req, res, next) => {
console.log(req.header('x-auth-token'));
const token = req.header('x-auth-token');
// Check for token
if (!token)
return res.status(401).json({ msg: 'No token, authorizaton denied' });
try {
// Verify token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
console.log('decoded:', decoded);
if(!decoded.admin)
return res.status(401).json({ msg: 'Not an admin, authorization denied' });
// Add user from payload
// console.log('decoded:', decoded);
req.user = decoded;
next();
} catch (e) {
res.status(400).json({ msg: 'Token is not valid' });
}
};
Link for request examples, and Heroku log since Stackoverflow says it's spam:
https://gist.github.com/macklinhrw/b2fec97642882ba406c49cce3e195c39
Edit
I pasted the Chrome request and response headers into the gist at the bottom, but there was no response data to go along with either.
I've debugged a little using this to check the difference and I discovered that with delete action that ends up working, the red (canceled) request has headers, while the non-working is completely empty (filled with 'provisional headers' if that means anything).
I couldn't copy-paste the request headers into the gist for the working red (canceled) one. But, I pasted everything that I thought could possibly be useful from chrome, hopefully it helps.
Also, I didn't see any DELETE requests when I was using the Chrome network tool, and I was seeing them on the other tool. Not sure if it matters, probably just a config option somewhere.
So, I haven't found an exact answer, but I've found a workaround.
As it turns out, it might have something to do with axios, and I've been searching for the wrong things in the last 3 days.
This thread helped me: https://github.com/axios/axios/issues/1428
I've added an e.preventDefault() to the onClick method I use for the delete button.
This fixed the problem, but doesn't redirect (I use href={link}), so I'm going to add a conditional render for react-router to redirect the page. I don't know of a better method so maybe give me some ideas. I'll edit if I have further troubles.

Node.js error handling setup not working as intended

I am trying to have all my error messages in one file, each error is denoted by an error code, then in my functions/services, when there is an error, I call a function that takes the error code as an argument, then returns an object to the client with the error code and the respective error message from the errors.js file.
as an example, a user trying to register with an email that already exists in the database, here is how I try to do it:
// userService.js -- where my register function is
const { errorThrower } = require('../../utils/errorHandlers');
...
static async registerNewUser(body) {
const exists = await User.where({ email: body.email }).fetch();
if(exists) {
errorThrower('400_2');
}
...
}
errorHandlers.js file:
exports.errorThrower = (errCode) => {
throw Object.assign(new Error(errors[errorCode]), { errorCode })
}
exports.errorHandler = (err, req, res, next) => {
if(!err.status && err.errorCode) {
err.status = parseInt(err.errorCode.toString().substring(0, 3), 10);
}
let status, message
if (err.status) {
status = err.status
message = err.message
} else {
status = 500;
message = 'unexpected behavior, Kindly contact our support team!'
}
res.status(status).json({
errorCode: err.errorCode,
message
})
}
errors.js
module.exports = {
'400_1': 'JSON payload is not valid',
'400_2': 'user already registered',
...
}
...
const user = require('./routes/user');
const { errorHandler } = require('../utils/errors');
...
app.use('/user' , user);
app.use(errorHandler);
...
now with this setup, when hitting the register endpoint by postman, I only get the following in the console
UnhandledPromiseRejectionWarning: Error: user already registered
could someone please tell me what am I missing here?
thanks in advance!
You're not catching the error which you throw inside your errorThrower, thus getting the error UnhandledPromiseRejectionWarning. What you need to do is catch the error and pass it on the the next middleware, in order for the errorHandler-middleware to be able to actually handle the error. Something like this:
exports.register = async(req, res) => {
try {
await registerNewUser(req.body);
} catch(err) {
next(err);
}
};
If you don't want to do this for every middleware, you could create a "base"-middleware which handles this:
const middlewareExecutor = async (req, res, next, fn) => {
try {
return await fn.call(fn, req, res, next);
} catch (err) {
next(err);
}
};
Now you can pass your middlewares as an argument and delegate handling the error to the executor:
app.use('/user' , async (req, res, next) => middlewareExecutor(req, res, next, user));

ReactJS + MongoDB + NodeJS/ExpressJS: What is process.nextTick(function() { throw err; });?

In my ReactJS project, I am currently running the server with NodeJS and ExpressJS, and connecting to the MongoDB using MongoClient. I have a login API endpoint set up that accepts a request with user's username and password. And if a user is not found, should catch the error and respond with an error (status(500)) to the front-end.
But rather than responding to the front-end with an json error, the server gets crashed. I have tried everything to figure out why but still no luck.
How can I fix the following error? Any guidance or insight would be greatly appreciated, and will upvote and accept the answer.
I intentionally made a request with a username and a password ({ username: 'iopsert', password: 'vser'}) that does not exist in the database.
Here is the login endpoint:
//login endpoint
app.post('/api/login/', function(req, res) {
console.log('Req body in login ', req.body)
console.log('THIS IS WHAT WAS PASSED IN+++++', req._id)
db.collection('users').findOne({username: req.body.username}, function(err, user) {
console.log('User found ')
if(err) {
console.log('THIS IS ERROR RESPONSE')
// Would like to send this json as an error response to the front-end
res.status(500).send({
error: 'This is error response',
success: false,
})
}
if(user.password === req.body.password) {
console.log('Username and password are correct')
res.status(500).send({
username: req.body.username,
success: true,
user: user,
})
} else {
res.status(500).send({
error: 'Credentials are wrong',
success: false,
})
}
})
And here is the terminal error log:
Req body in login { username: 'iopsert', password: 'vset' }
THIS IS WHAT WAS PASSED IN+++++ undefined
User found
/Users/John/practice-project/node_modules/mongodb/lib/utils.js:98
process.nextTick(function() { throw err; });
^
TypeError: Cannot read property 'password' of null
at /Users/John/practice-project/server/server.js:58:12
at handleCallback (/Users/John/practice-project/node_modules/mongodb/lib/utils.js:96:12)
at /Users/John/practice-project/node_modules/mongodb/lib/collection.js:1395:5
at handleCallback (/Users/John/practice-project/node_modules/mongodb/lib/utils.js:96:12)
at /Users/John/practice-project/node_modules/mongodb/lib/cursor.js:675:5
at handleCallback (/Users/John/practice-project/node_modules/mongodb-core/lib/cursor.js:165:5)
at setCursorNotified (/Users/John/practice-project/node_modules/mongodb-core/lib/cursor.js:505:3)
at /Users/John/practice-project/node_modules/mongodb-core/lib/cursor.js:578:16
at queryCallback (/Users/John/practice-project/node_modules/mongodb-core/lib/cursor.js:226:18)
at /Users/John/practice-project/node_modules/mongodb-core/lib/connection/pool.js:430:18
And /Users/John/practice-project/node_modules/mongodb/lib/utils.js:98 is referring to the following:
var handleCallback = function(callback, err, value1, value2) {
try {
if(callback == null) return;
if(value2) return callback(err, value1, value2);
return callback(err, value1);
} catch(err) {
process.nextTick(function() { throw err; });
return false;
}
return true;
}
EDIT
Here are everything that's being imported to the server:
"use strict"
var express = require('express');
var path = require('path');
var config = require('../webpack.config.js');
var webpack = require('webpack');
var webpackDevMiddleware = require('webpack-dev-middleware');
var webpackHotMiddleware = require('webpack-hot-middleware');
var bodyParser = require('body-parser');
var MongoClient = require('mongodb').MongoClient;
var ObjectId = require('mongodb').ObjectID;
const jwt = require('jsonwebtoken')
var app = express();
var db;
var compiler = webpack(config);
app.use(webpackDevMiddleware(compiler, {noInfo: true, publicPath: config.output.publicPath}));
app.use(webpackHotMiddleware(compiler));
app.use(express.static('dist'));
app.use(bodyParser.json());
And this is how the request is made and error is caught:
loginUser(creds) {
var request = {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(creds),
}
fetch(`http://localhost:3000/api/login`, request)
.then(res => res.json())
.then(user => {
console.log(user);
console.log('Successful')
})
.catch(err => {
console.log('Error is', err)
})
},
It looks to me like the error is being thrown on this line because user is not defined.
if(user.password === req.body.password) {...}
Take a harder look at your console statements.
1. Req body in login { username: 'iopsert', password: 'vset' }
2. THIS IS WHAT WAS PASSED IN+++++ undefined
3. User found
4. /Users/John/practice-project/node_modules/mongodb/lib/utils.js:98
5. process.nextTick(function() { throw err; });
^
6. TypeError: Cannot read property 'password' of null
7. at /Users/John/practice-project/server/server.js:58:12
Line 2 shows that req._id is undefined
Your User found statement is printed before you check if there is an error or if the user actually exists, so it isn't representative of there actually being a user.
Line 6 shows that the error is being thrown because you're trying to read a property of password from a null object.
I'd recommend modifying your login logic to look more like this:
//login endpoint
app.post('/api/login/', function(req, res) {
console.log('Performing login with req.body=');
console.log(JSON.stringify(req.body, null, 4));
// check for username
if (!req.body.username) {
return res.status(401).send({message: 'No username'});
}
// find user with username
db.collection('users').findOne({username: req.body.username}, function(err, user) {
// handle error
if(err) {
console.log('Error finding user.');
return res.status(500).send({message: 'Error finding user.'});
}
// check for user
if (!user) {
console.log('No user.');
return res.status(500).send({message: 'No user.'});
}
console.log('User found.');
// check password
if(user.password !== req.body.password) {
console.log('Wrong password.');
return res.status(401).send({message: 'Wrong password.'});
}
// return user info
return res.status(200).send(user);
});
Some final thoughts:
Make sure to handle the error (if it exists) and check that user exists before proceeding.
Always include return in your return res.status(...).send(...) statements, otherwise the subsequent code will execute.
It's generally not a good idea to save passwords as simple strings. Work toward encrypting them. Look at passport or bcrypt.
Hope this helps.

Categories