I'm developing a very simple social media application where users can register, log in, create posts, edit their profile and posts, delete posts and delete themselves completely.
Tokens via jwt should be created and used appropriately and the user uploads all relevant files to cloudinary. Where profile pictures are stored in profile_pictures folder and posts are stored in user_posts folder.
All of this worked when tested last Friday. Now, however, as I sit down to fix the tokens on the frontend, all of a sudden I can no longer register users. So I went to the backend, checked the code, went to Postman, tested the routing and it wasn't populating the request body.
Now there are more issues, for example, file upload to cloudinary no longer works either, I don't know if this is due to the recent API update, although their page says "last updated" in June 15th", my last registration and login test was on Friday, June 16th, which made me think that if it worked, it should still work now. But it doesn't.
However, all of this is not why I asked this question and asked for help. I have disabled all file upload related code in the application to test the creation of mongo db atlas user objects.
This is where my problem lies. For some reason that is beyond my knowledge and understanding, Postman won't populate anything with the form data, even though everything appears to be in order, the content type is correct, and the fields of the user object being created are correct.
If I use raw input in Postman it just populates it. I'm at my wits end...
If anyone knows about this problem, I would really appreciate your help.
The following is the relevant code, please ignore the commented out file-related code lines.
// AUTH CONTROLLER: // controller for user signup module.exports.signup = async (req, res, next) => { try { console.log(req.body, "req body"); // retrieve user from db by email provided in request body const foundUser = await User.findOne({ email: req.body.email }); console.log(foundUser, "found user"); // check if foundUser already exists in db if (foundUser) { res.status(409).json({ message: `Email already in use. Please choose another.` }); } // generate salt and hash for the user password const salt = bcrypt.genSaltSync(Number(process.env.SALT_ROUND)); const hash = bcrypt.hashSync(req.body.password, salt); // create a new user object from user model const newUser = new User({ username: req.body.username, name: req.body.name, email: req.body.email, password: hash, //profilePicture: req.file.path }); // console.log(req.file, "req file"); await newUser.save(); // save the new user object to the database // upload the profile picture to cloudinary storage // const result = await cloudinary.uploader.upload(req.file.path, { // public_id: `profile_pictures/${newUser._id}`, // }); //newUser.profilePicture = result.secure_url; //await newUser.save(); // update newUser with the profilePicture URL console.log(newUser, "new user"); // generate a signup token // const token = jwt.sign({ newUserId: newUser._id, newUserName: newUser.username, newUserProfilePicture: newUser.profilePicture }, process.env.JWT_SECRET, { // expiresIn: '5m' // expires in 5 minutes (in case signup was erroneous) // }); // store the token as a cookie // res.cookie('jwt', token, { // httpOnly: true // }); // respond with the newly created user object and the created token res.status(201).json({ message: `User created successfully!`, user: newUser }); } catch (err) { next(err); }; };
// AUTH ROUTE: // import dependencies const express = require('express') const authController = require('../controllers/authController') //const authHandler = require('../middlewares/authHandler') // create new router object const router = express.Router() // import controllers router.post('/signup', authController.signup) //router.post('/login', authController.login) //router.get('/logout', authHandler.checkUser, authController.logout) module.exports = router
// USER MODEL: const mongoose = require("mongoose") const bcrypt = require("bcrypt") const UserSchema = new mongoose.Schema({ email: { type: String, required: true, unique: true }, password: { type: String, required: true }, name: { type: String, required: true }, username: { type: String, unique: true }, //profilePicture: { type: String }, }) // usually you hash/salt inside the appropriate model; like our userModel here. // // but we save the user-object twice for following two reasons: // - the regular data of the user-object gets saved to our mongoDB atlas database. // - and the profile picture gets uploaded and saved in an appropriate folder to cloudinary. // // this triggers the password hash twice, which in return falsifies the user password on login attempt. // therefore we ignore the code snippet below and directly salt and hash in our authController. // // hash and salt user password before saving to database // UserSchema.pre("save", async function (next) { // const salt = await bcrypt.genSalt(); // this.password = await bcrypt.hash(this.password, salt); // next(); // }); // pre-hook for updating password field UserSchema.pre('findOneAndUpdate', function (next) { const update = this.getUpdate() if (update.password) { bcrypt.hash(update.password, Number(process.env.SALT_ROUND), function (err, hash) { if (err) return next(err) update.password = hash next() }) } }) // compare the password entered by the user with the hashed password in the database UserSchema.methods.comparePassword = function (candidatePassword, cb) { bcrypt.compare(candidatePassword, this.password, function (err, isMatch) { if (err) return cb(err) //cb(err) passes errors down to error handler the same way next(err) does cb(null, isMatch) }) } const User = mongoose.model("User", UserSchema) module.exports = User
// SERVER: // import dependencies const cors = require('cors'); const express = require('express'); const cookieParser = require('cookie-parser'); // import routes (modules) const authRoute = require('./routes/authRoute'); const userRoute = require('./routes/userRoute'); const postRoute = require('./routes/postRoute'); const app = express(); // initialize express // import middlewares const { connectMongoDB } = require('./lib/mongoose'); // destruct mongoDB connector const { errorHandler } = require('./middlewares/errorHandler'); // destruct errorHandler // allow requests from specified origins with specific methods const whitelist = [process.env.FRONTEND_URL, 'https://www.arii.me']; // add cors options const corsOptions = { origin: (origin, callback) => { if (whitelist.indexOf(origin) !== -1 || !origin) { callback(null, true); } else { callback(new Error('CORS issues')); }; }, credentials: true, }; // enable cross-origin resource sharing with specified options for the express app app.use(cors(corsOptions)); // parse incoming cookies and make them accessible in req.cookies app.use(cookieParser()); // enable parsing of incoming JSON data in the request body by the express app app.use(express.json()); app.use(express.urlencoded({ extended: false })); // define routes app.use('/auth', authRoute); app.use('/users', userRoute); app.use('/posts', postRoute); // define middlewares connectMongoDB(); app.use(errorHandler); // error handler must be last invoked middleware // listen to server app.listen(process.env.PORT || 3003, () => { console.log(`Server up and running at ${process.env.PORT}`); });
// MONGOOSE: // import dependencies const mongoose = require('mongoose'); require('dotenv').config(); // destruct envs const { DB_USER, DB_PASS, DB_HOST, DB_NAME } = process.env; // atlas connection string const mongoURI = `mongodb+srv://${DB_USER}:${DB_PASS}@${DB_HOST}/${DB_NAME}?retryWrites=true&w=majority`; // middleware function for handling connections to the mongoDB atlas database module.exports.connectMongoDB = async () =>{ try { await mongoose.connect(mongoURI, { useNewUrlParser: true, useUnifiedTopology: true, }); console.log(`Connected to MongoDB Atlas!`); } catch (err) { console.log(`Couldn't Connect to MongoDB Atlas!`); next(err); } }
Sorry if this is too much, but this is all the relevant code and I'm losing my mind. I know it's not wise to disclose ENV, but this is just an exercise for myself, so I have no qualms about sharing ENV.
I greatly appreciate all input on this matter.
renew:
I have fixed the issue where the request body was not populated with the following code:
// In the auth route I imported multer: const multer = require('multer') const upload = multer() // Then I added a method to the auth route before signup is called: router.post('/signup', upload.none(), authController.signup) // I have also forfeited any file upload during signup // and replaced them with a default image in my cloudinary assets. // Here's the updated userModel profilePicture field: profilePicture: { type: String, default: 'res.cloudinary.com/ddb2abwuu/image/upload/v1687256495/…' }
By following these steps it now correctly populates the request body and I can also use Postman form data and create the user again on the frontend.
Users can choose to change the default profile picture later by uploading their own profile through a patch. Other websites handle user acquisition in a similar way.
Don't ask me why I suddenly have to do these mental gymnastics, even though everything is working just as well as it did a few days ago, here we are.
I strongly suspect that at least one of the packages and dependencies I use had some kind of API update that completely messed me up.
As of now, this ticket is now resolved. Hope this is useful to anyone who encounters the same strange problem in the future.
So far I've found a solution that I can't believe: in my authRoute I have to explicitly import multer and then create
Then specify in the authentication route:
I also gave up on any file uploads during the registration process and just manually added the default profile picture to my cloudinary assets and added it to my user model:
So registration and login will work again.
By following these steps it now correctly populates the request body and I can also use Postman form data and create the user again on the frontend.
Users can choose to change the default profile picture later by uploading their own profile through a patch. Other websites handle user acquisition in a similar way.
Don't ask me why I suddenly have to do these mental gymnastics, even though everything is working just as well as it did a few days ago, here we are.
I strongly suspect that at least one of the packages and dependencies I use had some kind of API update that completely messed me up.
As of now, this issue is now resolved. Hope this is useful to anyone who encounters the same strange problem in the future.