우당탕탕 개발일지
NodeJS Express + MongoDB 웹서버 (2) 본문
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' 카테고리의 다른 글
[JavaScript] 얕은복사 vs 깊은복사 (0) | 2024.08.11 |
---|---|
Web 기초 (0) | 2024.08.01 |
NodeJS Express + MongoDB 웹서버 (1) (0) | 2024.05.12 |