Backend ๐Ÿ“š/Node.js

[Node]Instagram Clone - 2. mongoDB๋ฅผ ํ™œ์šฉํ•œ ํšŒ์› ๊ฐ€์ž… API

leejaejae 2024. 10. 17. 21:33

1. mongoDB ์„ค์ •

- ์ด์ „ ํฌ์ŠคํŒ… ์ฐธ๊ณ ํ•จ.

 

[Node] Mongo DB ์—ฐ๊ฒฐ

1. CLUSTER(ํด๋Ÿฌ์Šคํ„ฐ) ๋งŒ๋“ค๊ธฐ1) ๋ชฝ๊ณ  DB ์‚ฌ์ดํŠธ ๊ฐ€์„œ ํšŒ์› ๊ฐ€์ž…2) CLUSTER(ํด๋Ÿฌ์Šคํ„ฐ) ๋งŒ๋“ค๊ธฐ - ๋ฌด๋ฃŒ ๋ฒ„์ „ ์„ ํƒ 2. ๋ชฝ๊ณ DB ์œ ์ € ์ƒ์„ฑ1) ๋ชฝ๊ณ DB ์œ ์ € ์ƒ์„ฑ- ์ด๋•Œ Username๊ณผ Password๋Š” ๊ธฐ์–ตํ•ด๋‘˜๊ฒƒ!2) ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜

jaejae-sosp.tistory.com

- ์ด๋ฒˆ์—๋Š” DB ์ด๋ฆ„์„ instagram_clone ์œผ๋กœ ๋งŒ๋“ค์–ด ์ฃผ์—ˆ์Œ.

/* index.js */

const mongoose = require("mongoose");

// MongoDB ์—ฐ๊ฒฐ ์‹œ dbName ์ง€์ •
mongoose
  .connect(config.mongoURI, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
    dbName: "instagram_clone", // ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ด๋ฆ„ ์„ค์ •
  })
  .then(() => console.log("MongoDB connected.."))
  .catch((err) => console.log(err));

 

2. ์Šคํ‚ค๋งˆ ์ž‘์„ฑ

1) user DB์— ๋„ฃ๊ธฐ ์œ„ํ•ด ์Šคํ‚ค๋งˆ ์ž‘์„ฑ

/* user.js */

const userSchema = mongoose.Schema({
  user_id: {
    type: String,
  },
  email: {
    type: String,
    trim: true,
    unique: 1,
  },
  password: {
    type: String,
    minlength: 5,
  },
  phone: {
    type: String,
    maxlength: 11,
  },
  gender: {
    type: String,
  },
  birth: {
    type: String,
    Timestamp: true,
  },
  name: {
    type: String,
    maxlength: 50,
  },
  introduce: {
    type: String,
    maxlength: 100,
  },
  image: String,
  token: {
    type: String,
  },
  role: {
    // user๋Š” ๊ด€๋ฆฌ์ž ๋˜๋Š” ์ผ๋ฐ˜์ธ
    type: Number, // ์˜ˆ๋ฅผ ๋“ค์–ด, number๊ฐ€ 1์ด๋ฉด ๊ด€๋ฆฌ์ž, 0์ด๋ฉด ์ผ๋ฐ˜์œ ์ €
    default: 0,
  },
  tokenExp: {
    // token์ด ์œ ํšจํ•˜๋Š” ๊ธฐ๊ฐ„
    type: Number,
  },
  emailVerificationCode: {
    type: String, // ์ธ์ฆ ์ฝ”๋“œ๋ฅผ ์ €์žฅํ•  ํ•„๋“œ
  },
  emailVerificationCodeExpires: {
    type: Date, // ์ธ์ฆ์ฝ”๋“œ ์œ ํšจ๊ธฐ๊ฐ„
  },
  isEmailVerified: {
    type: Boolean, // ์ด๋ฉ”์ผ ์ธ์ฆ ์—ฌ๋ถ€
    default: false, // ๊ธฐ๋ณธ๊ฐ’: ์ธ์ฆ๋˜์ง€ ์•Š์Œ
  },
});


2) ๋น„๋ฐ€๋ฒˆํ˜ธ ์•”ํ˜ธํ™” ์„ค์ •

- user.js ์— ์ถ”๊ฐ€

/* user.js */

userSchema.pre("save", function (next) {
  // userModel์— user์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ธฐ ์ „์— ์ฒ˜๋ฆฌ๋จ
  var user = this;

  if (user.isModified("password")) {
    // password๊ฐ€ ๋ณ€ํ™˜๋  ๋•Œ๋งŒ ์•”ํ˜ธํ™”
    // ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์•”ํ˜ธํ™” ์‹œํ‚ค๊ธฐ
    bcrypt.genSalt(saltRounds, function (err, salt) {
      // salt ๋งŒ๋“ค๊ธฐ
      if (err) return next(err);

      bcrypt.hash(user.password, salt, function (err, hash) {
        if (err) return next(err);
        user.password = hash; // ์•”ํ˜ธํ™” ํ‚ค ๋งŒ๋“œ๋Š” ๋ฐ ์„ฑ๊ณตํ–ˆ์œผ๋ฉด, ์›๋ž˜ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ž‘ hash ๋ฐ”๊พธ๊ณ 
        next(); // index.js๋กœ ๋Œ์•„๊ฐ€๊ธฐ
      });
    });
  } else {
    // ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ง๊ณ  ๋‹ค๋ฅธ ๊ฑธ ๋ฐ”๊ฟ€ ๊ฒฝ์šฐ
    next(); // next() ์—†์œผ๋ฉด ๊ณ„์† ๋จธ๋ฌผ๊ฒŒ ๋จ
  }
});

 

3. ํšŒ์›๊ฐ€์ž…

1) ์ด๋ฉ”์ผ๋กœ ์ธ์ฆ ์ฝ”๋“œ ์ „์†ก

- ํšŒ์›๊ฐ€์ž…์„ ์œ„ํ•ด ์œ ํšจํ•œ ์ด๋ฉ”์ผ์„ ์‚ฌ์šฉํ–ˆ๋Š”์ง€ ํ™•์ธํ•ด๋ณด๊ณ  ์‹ถ์–ด ๋งŒ๋“  ๊ธฐ๋Šฅ.
- ์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•˜๊ณ  ์ธ์ฆํ•˜๊ธฐ๋ฅผ ๋ˆ„๋ฅด๋ฉด ์ž…๋ ฅํ•œ ์ด๋ฉ”์ผ๋กœ ์ธ์ฆ ์ฝ”๋“œ๊ฐ€ ์ „์†ก๋˜๊ณ  ์œ ํšจ ์‹œ๊ฐ„ ๋‚ด์— ์ธ์ฆ ์ฝ”๋“œ ์ž…๋ ฅ์„ ์„ฑ๊ณตํ•˜๋ฉด ์œ ํšจํ•œ ์ด๋ฉ”์ผ์ด๋ผ๊ณ  ๊ฐ„์ฃผํ•˜๊ณ  ํšŒ์›๊ฐ€์ž…์„ ์ˆ˜ํ–‰ํ•จ.

- nodemailer ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ๋กœ ํ•จ.

/* sendEmail.js */

const nodemailer = require("nodemailer");
require("dotenv").config(); // ํ™˜๊ฒฝ๋ณ€์ˆ˜

const transporter = nodemailer.createTransport({
  service: "gmail", // Gmail ์‚ฌ์šฉ
  auth: {
    user: process.env.GMAIL_USER,
    pass: process.env.GMAIL_PASS,
  },
});

// ์ด๋ฉ”์ผ ์ „์†ก ํ•จ์ˆ˜
const sendVerificationEmail = async (email, verificationCode) => {
  try {
    await transporter.sendMail({
      from: `"(์ž„์‹œ)์ธ์Šคํƒ€๊ทธ๋žจ" <${process.env.GMAIL_USER}>`,
      to: email,
      subject: "(์ž„์‹œ)์ธ์Šคํƒ€๊ทธ๋žจ ํšŒ์›๊ฐ€์ž… ์ด๋ฉ”์ผ ์ธ์ฆ ์ฝ”๋“œ",
      text: `์ธ์ฆ์ฝ”๋“œ: ${verificationCode}`,
    });
    return true; // ์ด๋ฉ”์ผ ์ „์†ก ์„ฑ๊ณต
  } catch (error) {
    console.error("Error sending email:", error);
    return false; // ์ด๋ฉ”์ผ ์ „์†ก ์‹คํŒจ
  }
};

module.exports = { sendVerificationEmail };

โ—๏ธ์ฃผ์˜
- nodemailer ์˜ ๊ฒฝ์šฐ, transporter ํฌ๋งท์—์„œ ๋ฉ”์ผ์˜ ํ˜•์‹์„ Gmail๋กœ ์„ค์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ์ธ์‹ํ•  ์ˆ˜ ์—†์Œ.
- ๊ทธ๋ž˜์„œ ๋์ด ๊ผญ @gmail.com ์œผ๋กœ ๋๋‚˜๊ฑฐ๋‚˜ SMTP ๋ฐฉ์‹์„ ์„ ํƒํ•ด์•ผํ•จ.


2) ํšŒ์›๊ฐ€์ž…

/* signUp.js */

// ํšŒ์›๊ฐ€์ž… ์ฒ˜๋ฆฌ ๋ฐ ์ด๋ฉ”์ผ ์ธ์ฆ ์ฝ”๋“œ ๋ฐœ์†ก
router.post("/", async (req, res) => {
  const { email } = req.body;

  try {
    // ์ด๋ฉ”์ผ์ด ์ด๋ฏธ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ
    const existingUser = await User.findOne({ email });
    if (existingUser) {
      return res
        .status(400)
        .json({ success: false, message: "์ด๋ฏธ ๊ฐ€์ž…๋œ ์‚ฌ์šฉ์ž์ž…๋‹ˆ๋‹ค." });
    }

    // ์ด๋ฉ”์ผ๋กœ ๋ณด๋‚ผ ์ธ์ฆ๋ฒˆํ˜ธ ์ƒ์„ฑ
    const emailVerificationCode = crypto.randomBytes(3).toString("hex"); // 6์ž๋ฆฌ ์ฝ”๋“œ ์ƒ์„ฑ

    const user = new User({
      ...req.body,
      emailVerificationCode, // ์ƒ์„ฑ๋œ ์ธ์ฆ์ฝ”๋“œ ์ €์žฅ
      emailVerificationCodeExpires: Date.now() + 3600000, // ์ธ์ฆ ์ฝ”๋“œ ์œ ํšจ์‹œ๊ฐ„ 1์‹œ๊ฐ„
    });

    // ์ด๋ฉ”์ผ๋กœ ์ธ์ฆ๋ฒˆํ˜ธ ์ „์†ก
    const emailSent = await sendVerificationEmail(email, emailVerificationCode);
    if (!emailSent) {
      return res
        .status(500)
        .json({ success: false, message: "์ด๋ฉ”์ผ ์ „์†ก ์‹คํŒจ" });
    }

    await user.save(); // ์‚ฌ์šฉ์ž ์ •๋ณด ์ €์žฅ
    res.status(200).json({ success: true, message: "์ด๋ฉ”์ผ ์ „์†ก ์„ฑ๊ณต" });
  } catch (err) {
    res.status(500).json({ success: false, err });
  }
});

- ๋จผ์ € ์ด๋ฏธ ๊ฐ€์ž…๋œ ์‚ฌ์šฉ์ž์ธ์ง€ ํ™•์ธํ•˜๊ณ (๊ฐ€์ž…๋˜์–ด ์žˆ๋Š” ์‚ฌ์šฉ์ž๋ฉด ์ง€๊ธˆ์€ 400 ์—๋Ÿฌ๋ฅผ ๋„์šฐ์ง€๋งŒ, ์•ž์œผ๋ก  login ํŽ˜์ด์ง€๋กœ ๋ณด๋‚ผ ์˜ˆ์ •)
- ๊ฐ€์ž…์ด ๋˜์–ด ์žˆ์ง€ ์•Š์€ ์‚ฌ์šฉ์ž๋ผ๋ฉด, ์ธ์ฆ ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ด๋ฉ”์ผ๋กœ ์ƒ์„ฑ๋œ ์ธ์ฆ ์ฝ”๋“œ๋ฅผ ์ „์†กํ•จ.

/* signUp.js */

// ์ด๋ฉ”์ผ ์ธ์ฆ ์ฒ˜๋ฆฌ
router.post("/verify-email", async (req, res) => {
  const { email, verificationCode } = req.body;

  try {
    const user = await User.findOne({ email });

    if (!user)
      return res
        .status(400)
        .json({ success: false, message: "์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค." });

    // ์ด๋ฉ”์ผ์ด ์ด๋ฏธ ์ธ์ฆ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ
    if (user.isEmailVerified) {
      return res.status(400).json({
        success: false,
        message: "์ด๋ฏธ ์ด๋ฉ”์ผ ์ธ์ฆ์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.",
      });
    }

    if (user.emailVerificationCode !== verificationCode) {
      return res
        .status(400)
        .json({ success: false, message: "์ธ์ฆ์ฝ”๋“œ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค." });
    }

    // ์ธ์ฆ ์ฝ”๋“œ๊ฐ€ ๋งŒ๋ฃŒ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ
    if (user.emailVerificationCodeExpires < Date.now()) {
      return res
        .status(400)
        .json({ success: false, message: "์ธ์ฆ์ฝ”๋“œ๊ฐ€ ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." });
    }

    user.isEmailVerified = true; // ์ด๋ฉ”์ผ ์ธ์ฆ ์™„๋ฃŒ

    // ์ด๋ฉ”์ผ ์ธ์ฆ ์—ฌ๋ถ€๋งŒ true๋กœ ๋ณ€๊ฒฝ
    user.emailVerificationCode = undefined; // ์ธ์ฆ์ฝ”๋“œ ์‚ญ์ œ
    user.emailVerificationCodeExpires = undefined; // ์ธ์ฆ ์ฝ”๋“œ ๋งŒ๋ฃŒ ์‹œ๊ฐ„ ์‚ญ์ œ

    await user.save();

    res
      .status(200)
      .json({ success: true, message: "์ด๋ฉ”์ผ ์ธ์ฆ์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." });
  } catch (err) {
    res.status(500).json({ success: false, err });
  }
});

- 1์‹œ๊ฐ„ ์ด๋‚ด๋กœ ์ธ์ฆ ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•˜๊ฒŒ๋” ๊ตฌํ˜„ํ•จ.

- ์ด๋ฉ”์ผ ์ธ์ฆ์ด ์™„๋ฃŒ๋˜๋ฉด ์ธ์ฆ์ฝ”๋“œ์™€ ์ธ์ฆ ์ฝ”๋“œ ๋งŒ๋ฃŒ ์‹œ๊ฐ„์€ ์‚ญ์ œ๋˜๊ณ  ์ธ์ฆ ์—ฌ๋ถ€๋งŒ DB์— ๋‚จ์Œ.

 

4. ๊ฒฐ๊ณผ ๐ŸŽ‰

- ํ”„๋ก ํŠธ๊ฐ€ ์•„์ง ์—†๋Š” ๊ด€๊ณ„๋กœ postman์„ ํ™œ์šฉ

1) ์ด๋ฉ”์ผ๋กœ ์ธ์ฆ ์ฝ”๋“œ ์ „์†ก ๋ฐ ํšŒ์›๊ฐ€์ž… ์‹œ๋„

- ์ด๋ฉ”์ผ ์ธ์ฆ์ด ์™„๋ฃŒ๋˜๊ธฐ ์ „์— isEmailVerified ๊ฐ€ false์ž„.


2) ์ด๋ฉ”์ผ ์ธ์ฆ ์™„๋ฃŒ ๋ฐ ํšŒ์› ๊ฐ€์ž… ์™„๋ฃŒ

- ์ด๋ฉ”์ผ ์ธ์ฆ์ด ์™„๋ฃŒ๋˜๋ฉด isEmailVerified ๊ฐ€ true ๋กœ ๋ฐ”๋€Œ๊ณ 
- emailVerificationCode ํ•„๋“œ์™€ emailVerificationCodeExpires ํ•„๋“œ๊ฐ€ DB์—์„œ ์‚ญ์ œ๋จ!