Notice
Recent Posts
Archives
Today
Total
«   2024/06   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30
Recent Comments
관리 메뉴

우당탕탕 개발일지

NodeJS Express + MongoDB 웹서버 (2) 본문

Nodejs

NodeJS Express + MongoDB 웹서버 (2)

devchop 2024. 5. 12. 20:56

 

index.js에서 정의했던 users.main.js router를 살펴보자. 우선 index.js 의 router 정의 부분은 다음과 같았다.

const userRouter = require("./src/users/users.main");
app.use("/api/users", userRouter);

 

app.use() 부분에서 경로를 /api/users로 설정했었기 때문에, 만약 user.main.js에서 라우터 경로를 /login 으로 설정하면 최종 경로는 /api/users/login 이 된다.

 

스크립트는 총 2개로, 함수를 담당하는 uesrs.main.js 와  모델을 담당하는 users.models.js 이다.

더보기

users.models.js

const mongoose = require("mongoose");
const bcrypt = require("bcrypt");
const saltRounds = 10;
const jsonwebtoken = require("jsonwebtoken");

const token_key = "secretToken";
const userSchema = mongoose.Schema({
  name: {
    type: String,
    maxlength: 50,
    require: true,
  },
  email: {
    type: String,
    trim: true,
    unique: 1,
  },
  password: {
    type: String,
    minlength: 5,
  },
  lastname: {
    type: String,
    maxlength: 50,
  },
  role: {
    type: Number,
    default: 0,
  },
  image: {
    type: String,
  },
  token: {
    type: String,
  },
  tokenExp: {
    type: Number,
  },
});

//encrypt user password before save
userSchema.pre("save", function (next) {
  let user = this;

  if (!user.isModified("password")) return next();

  bcrypt.genSalt(saltRounds, function (err, salt) {
    if (err) return next(err);
    bcrypt.hash(user.password, salt, function (err, hash) {
      if (err) return next(err);
      user.password = hash;
      return next();
    });
  });
});

userSchema.methods.comparePassword = function (plainPassword, callback) {
  bcrypt.compare(plainPassword, this.password, function (err, isMatch) {
    if (err) return callback(err);
    return callback(null, isMatch);
  });
};

userSchema.methods.generateToken = function (callback) {
  //use jsonwebtoken
  let user = this;
  let token = jsonwebtoken.sign(user._id.toHexString(), token_key);
  user.token = token;

  user.save().then(function (err, user) {
    if (err) return callback(err);
    return callback(null, token);
  });
};

userSchema.statics.findByToken = function (token, callback) {
  let user = this;

  jsonwebtoken.verify(token, token_key, function (err, decoded) {
    //get userId from token, and compare with real userId
    user.findOne({ _id: decoded, token: token }).then(async (err, user) => {
      if (err) return callback(err);
      return callback(null, user);
    });
  });
};
const User = mongoose.model("User", userSchema);

module.exports = { User };

 

 

users.main.js

 

const express = require("express");
const router = express.Router();

const { auth } = require("../../middleware/auth");
const { User } = require("./users.models");

router.post("/register", async (req, res) => {
  const user = new User(req.body);
  try {
    await user.save();
    return res.status(200).json({ success: true });
  } catch (err) {
    return res.json({ success: false, err });
  }
});

router.post("/login", async (req, res) => {
  try {
    console.log("try login :" + req.body.email);
    User.findOne({ email: req.body.email }).then(async (user) => {
      if (!user) {
        console.log(req.body.email + " not exist");
        return res.json({
          loginSuccess: false,
          message: "no user match with email",
        });
      }

      user.comparePassword(req.body.password, (err, isMatch) => {
        if (!isMatch) {
          console.log("password not match");
          return res.json({
            loginSuccess: false,
            message: "not match password",
          });
        }

        user.generateToken((err, user) => {
          if (err) return res.status(400).send(err);
          //save cookie
          res
            .cookie("x_auth", user.token)
            .status(200)
            .json({ loginSuccess: true, userId: user._id });
        });
      });
    });
  } catch (err) {
    console.log("err:" + err);
    return res.json({ success: false, err });
  }
});

router.get("/auth", auth, (req, res) => {
  //middleware auth is successfully finished.
  res.status(200).json({
    id: req.user._id,
    email: req.user.email,
    isAdmin: req.user.role == 0,
    isAuth: true,
    name: req.user.name,
    last_name: req.user.lastname,
    role: req.user.role,
    image: req.user.image,
  });
});

router.get("/logout", auth, (req, res) => {
  User.findOneAndUpdate(
    { _id: req.user.id },
    {
      token: "",
    }
  ).then((err, user) => {
    if (err) {
      console.log("logout err. " + err);
      return res.json({ success: false, err });
    }
    return res.status(200).send({ success: true });
  });
});

module.exports = router;

 

 

1. Users 모델 정의하기

 models.js 는 총 3가지 부분으로 이루어져있다. 간단히 말하면, 스키마를 생성하고 함수를 정의한다. java로 따지면 클래스를 만드는 느낌이다. 

## 1. user가 어떤 데이터를 담고있는지를 정의하는 스키마를 만든다.

const userSchema = mongoose.Schema({...});

## 2. user 스키마에 호출할 수 있는 함수를 미리 정의한다. 
## user 스키마의 경우 비밀번호 대조하기, 토큰생성하기,
userSchema.methods.comparePassword = function (plainPassword, callback) {...}
userSchema.methods.generateToken = function (callback){...}

## 2-1. 인스턴스가 없어도 호출할 수 있는 스태틱 함수도 만들 수 있다. 여기선  토큰을 이용해 유저 찾는 함수를 정의한다.
userSchema.statics.findByToken = function (token, callback){...}

## 3. (1)에서 만들었던 user스키마를 이용하여 몽구스의 모델을 만든다. 
## 외부에서 이 모델을 가져다 사용할 수 있다.
const User = mongoose.model("User", userSchema);
module.exports = { User };

 

 

API 1. 가입하기

router.post("/register", async (req, res) => {
  const user = new User(req.body);
  try {
    await user.save();
    return res.status(200).json({ success: true });
  } catch (err) {
    return res.json({ success: false, err });
  }
});

 

 

post 형식에, api 주소는 /api/users/register 이된다. 여기서 User이라는 모델을 사용했는데, 이 모델은 users.models.js 에서 정의한 User 이다.  users.save()는 몽구스에서 제공하는 함수이다. 클라이언트에서 가입을 위해 필요한 정보들과 함께 통신을 하면, 그 데이터가 req에 담겨서 호출된다. 해당 데이터로 User 모델을 만들어서 몽구스를 이용해 save() 하는 함수이다.

 

 

API 2. 로그인하기

router.post("/login", async (req, res) => {
  try {
    console.log("try login :" + req.body.email);
    User.findOne({ email: req.body.email }).then(async (user) => {
      if (!user) {
        console.log(req.body.email + " not exist");
        return res.json({
          loginSuccess: false,
          message: "no user match with email",
        });
      }

      user.comparePassword(req.body.password, (err, isMatch) => {
        if (!isMatch) {
          console.log("password not match");
          return res.json({
            loginSuccess: false,
            message: "not match password",
          });
        }

        user.generateToken((err, user) => {
          if (err) return res.status(400).send(err);
          //save cookie
          res
            .cookie("x_auth", user.token)
            .status(200)
            .json({ loginSuccess: true, userId: user._id });
        });
      });
    });
  } catch (err) {
    console.log("err:" + err);
    return res.json({ success: false, err });
  }
});

 

로그인에서 User.findOne() 함수는 몽구스에서 제공하는 함수이다. 이메일이 매칭되는 유저를 찾는 함수이다.

user.comparePassword() 함수와 generateToken() 함수는 users.models.js 에서 정의한 함수이다. 정의한 함수를 사용할 때는 인자를 잘 넘기고, 리턴값을 정의한대로 출력하도록 해야한다.

 

API 3. 인증하기(토큰)

router.get("/auth", auth, (req, res) => {
  //middleware auth is successfully finished.
  res.status(200).json({
    id: req.user._id,
    email: req.user.email,
    isAdmin: req.user.role == 0,
    isAuth: true,
    name: req.user.name,
    last_name: req.user.lastname,
    role: req.user.role,
    image: req.user.image,
  });
});

 

인증하기에서 중요한 기능은 auth 함수이다. 중간에 들어간 이 함수는 "/auth" 로 요청이 들어왔을때 우선적으로 auth함수를 실행하고, 성공했을 경우 함수블록을 들어가게된다. 이런 함수를 middleware 함수라고 하는데, middleware/auth.js 파일에 정의되어있다.

 

const { User } = require("../src/users/users.models");

let auth = (req, res, next) => {
  //get cookie from client
  let token = req.cookies.x_auth;

  User.findByToken(token, (err, user) => {
    if (err) throw err;
    if (!user) return res.json({ isAuth: false, error: true });
    req.token = token;
    req.user = user;
    next();
  });

  //decoding and get token
  //find user, match with cookie
  //return result
};

module.exports = { auth };

위는 auth.js 스크립트이다. 인자로 3가지를 받고있는데, 주목해야할 것은 next()함수이다.

auth.js를 실행하다가 함수가 종료되어야할 경우 res를 사용하고, 마지막에 성공했을 경우에 next(); 를 호출하는것을 알 수 있다.

return res.json()~~ 을 호출하면 router의 "/auth" 함수블록이 아예 실행되지 않는다. next()를 호출할 때 비로소 메인 함수블록이 실행된다. auth, logout기능은 모두 우선적으로 토큰을 이용해 auth 를 실행해야한다. 재사용 측면에서 굉장하다!

 

'Nodejs' 카테고리의 다른 글

NodeJS Express + MongoDB 웹서버 (1)  (0) 2024.05.12