Home > Back-end >  I am trying to update my User schema in a "create profile" page after registering/logging
I am trying to update my User schema in a "create profile" page after registering/logging

Time:01-19

Still learning MERN stack, so I followed along a YouTube tutorial and figured I'd add some additional features that wasn't covered.

After registering, I want the user to be able to update information (like their first and last names, address, etc). For the sake of testing, I figured I'd first get the first name part working and the rest would come easily once I understood how to do that.

Here's my User Model, I will add additional attributes like last name and address later:

const mongoose = require("mongoose");
const userSchema = new mongoose.Schema({
    email: {type: String, required: true},
    passwordHash: {type:String, required: true},
    firstName: {type:String, default:""},
    });

const User = mongoose.model("user", userSchema);

module.exports = User;

Here is the relevant part of my server-side router, where I think the main issue lies since I really have no idea what I'm doing:

router.post("/createProfile", (req,res) => {
    var _id = req.body._id;
    var profile = {
        firstName: req.body.firstName,
    }
    User.findByIdAndUpdate(_id, profile, {new:true}, function(
        err,
        profile
    ) {
        if (err) {
            console.log("err", err);
            res.status(500).send(err);
        } else {
            console.log("success");
            res.send(profile);
        }
    });
});

I will post the rest of my router at the very end if it's relevant.

On the client-side, here is how I'd submit the first name data:

import React, { useContext, useState } from "react";
import axios from "axios";
import AuthContext from "../context/AuthContext";
import { useNavigate } from "react-router-dom";

function CreateProfile() {

    const [firstName, setFirstName] = useState("");

    const {getLoggedIn} = useContext(AuthContext);
    const navigate = useNavigate();

    async function createProfile(e) {
        e.preventDefault();

        try {
            const profileData = {
                firstName,
            };
            console.log(profileData)
            await axios.post("http://localhost:5000/auth/createProfile", profileData);
            await getLoggedIn();
            navigate("/");
        } catch (error) {
            console.error(error);
        }
    }

    return (
        <div>
            <h1>Create your profile</h1>
            <form onSubmit={createProfile}>
                <input type="text" 
                placeholder="First Name"
                onChange={(e) => setFirstName(e.target.value)}
                value={firstName} 
                />
                <button type="submit">Create Profile</button>
            </form>
        </div>
    );
}

export default CreateProfile;

And finally, where I'd place the in the front end:

import React, { useContext } from "react";
import { BrowserRouter, Routes, Route} from "react-router-dom";
import Login from "./components/auth/Login";
import Register from "./components/auth/Register";
import CreateProfile from "./components/CreateProfile";

import Navbar from "./components/layout/Navbar";
import AuthContext from "./context/AuthContext";

function Router() {

    const {loggedIn} = useContext(AuthContext);

    return (
        <BrowserRouter>
        <Navbar />
            <Routes>
                <Route exact path="/" element={<div>Home</div>} />
                {
                    loggedIn === false && (
                        <>
                            <Route exact path="/register" element={<Register />} />
                            <Route exact path="/login" element={<Login />} />
                        </>
                    )
                }
                {
                    loggedIn === true && (
                        <>
                            <Route exact path="/createProfile" element={<CreateProfile/>} />
                        </>
                    )
                }
            </Routes>
        </BrowserRouter>
    );
};

The create profile function should only be able to be accessed via a Navbar when the user is logged in--this feature works as intended.

And of course, here is my entire server-side router code:

const router = require("express").Router();
const User = require("../models/userModel");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");

//register
router.post("/", async (req, res) => {
    try {
      const {email, password, passwordVerify} = req.body;

      //validation
      if (!email || !password || !passwordVerify)
        return res
        .status(400)
        .json({ errorMessage: "Please enter all required fields." });

    if (password.length < 6)
        return res
        .status(400)
        .json({
            errorMessage: "Please enter a password of at least 6 characters."
        });

    if (password !== passwordVerify)
        return res
        .status(400)
        .json({
            errorMessage: "Please make sure the passwords match."
        });

    const existingUser = await User.findOne({email});
    if (existingUser)
        return res
        .status(400)
        .json({
            errorMessage: "An account with this email already exists."
        });

    //hash pswrd
    const salt = await bcrypt.genSalt();
    const passwordHash = await bcrypt.hash(password, salt);

    // save a new user account to the database
    const newUser = new User({
        email, passwordHash
    });

    const savedUser = await newUser.save();

    //sign the token
    const token = jwt.sign({
        user: savedUser._id
    }, process.env.JWT_SECRET);

    //send the token in a HTTP-only cookie
    res.cookie("token", token, {
        httpOnly: true,
    }).send();

    } catch (error) {
        console.error(error);
        res.status(500).send();
    }
});

//login
router.post("/login", async (req,res) => {
    try {
        const { email, password } = req.body;

        //validate
        if (!email || !password)
            return res
            .status(400)
            .json({ errorMessage: "Please enter all required fields." });

        const existingUser = await User.findOne({email});
        if (!existingUser)
            return res
            .status(401)
            .json({ errorMessage: "The email or password is incorrect." });

        const passwordCorrect = await bcrypt.compare(password, existingUser.passwordHash);
        if(!passwordCorrect)
            return res
            .status(401)
            .json({ errorMessage: "The email or password is incorrect." });

        //sign the token
        const token = jwt.sign({
            user: existingUser._id
        }, process.env.JWT_SECRET);

        //send the token in a HTTP-only cookie
        res.cookie("token", token, {
            httpOnly: true,
        }).send();


    } catch (error) {
        console.error(error);
        res.status(500).send();
    }
});

router.get("/logout", (req, res) => {
    res
    .cookie("token", "", {
        httpOnly: true,
        expires: new Date(0),
    })
    .send();
});

router.get("/loggedIn", (req, res) => {
    try {
        const token = req.cookies.token;
        if(!token) return res.json(false);
        
        jwt.verify(token, process.env.JWT_SECRET);

        res.send(true);
    } catch (error) {
        console.error(error);
        res.json(false);
    }
});

router.post("/createProfile", (req,res) => {
    var _id = req.body._id;
    var profile = {
        firstName: req.body.firstName,
    }
    User.findByIdAndUpdate(_id, profile, {new:true}, function(
        err,
        profile
    ) {
        if (err) {
            console.log("err", err);
            res.status(500).send(err);
        } else {
            console.log("success");
            res.send(profile);
        }
    });
});




module.exports = router;

I'm pretty sure the problems are coming from the router because I'm least fluent with that aspect of MERN stack. When inspect what's going on, there's no errors anywhere, the only issue is that my User isn't updating in MongoDB.

I've tried silly things like res.send(req.body.firstName) or res.json(req.body.firstName) mainly because, as I said before, I'm not really sure what I'm doing and I haven't found anything online that pointed me in the right direction so far.

If registering the first name was implemented during the signup process, I'd have no issues, but I'm a bit stuck since I'm trying to have it on a separate createProfile page apart from signing up.

Apologies in advance for a newbie question, and any help would be appreciated.

UPDATE: been playing around some more with some console logs and realized that when I do console.log(_id) I get "undefined". How would I properly grab the id of the User I'm currently logged in as?

CodePudding user response:

Updates are generally done with put request where you would pass in a certain id to the url like

router.put("/:id", (req,res) => { 
      "do something..."
}

You would have the do the same with axios if I remember correctly (axios.put())

I would also probably separate creating and updating functionality on the front end. So just like your have firstName and setFirstName, create nameUpdate setNameUpdate and handle like so

CodePudding user response:

You are retrieving the _id with req.body._id but you are not passing the value in profileData.
If you want to create a new User you should just do:

router.post("/createProfile", (req,res) => {
    try {
        const profile = await User.create(req.body);
        console.log("success");
        res.send(profile);
    } catch (e) {
        console.log("err", err);
        res.status(500).send(err);
    }
});
  • Related