보라코딩

Day 28, Node.js + React 본문

개발자가 되었다?

Day 28, Node.js + React

new 보라 2023. 9. 22. 19:06

인프런 무료 강의

노트랑 리액트 공부하기에 매우 좋고 재밌음 :)

 

 

 

 

[무료] 따라하며 배우는 노드, 리액트 시리즈 - 기본 강의 - 인프런 | 강의

이 강의를 통해서 리액트와 노드를 어떻게 사용하는지 기본적인 내용들을 배울 수 있습니다., 리액트와 노드의 기본을 학습하세요! 📝 강의 소개 안녕하세요 ^ ^ 이 강의에서는 리액트와 노드

www.inflearn.com

 

 

 


Node + React

1. Node.js, Express.js 설치

 

2. 몽고 DB 연결 (Atlas로 Cluster 만들기)

 

3. Mongoose 다운 및 DB 연결

const express = require("express");
const app = express();
const port = 3000;

app.listen(port, () => console.log(`포트 연결 중.. ${port}!`));

const mongoose = require("mongoose");

mongoose
  .connect(
    "본인코드!!!",
    {
      useNewUrlParser: true,
      useUnifiedTopology: true,
      //   useCreateIndex: true,
      //   useFindAndModify: false,
    }
  )
  .then(() => console.log("몽고DB connected!"))
  .catch((err) => console.log(err));

app.get("/", (req, res) => res.send("Hello world~~"));

4. MongoDB Model&Schema

const mongoose = require("mongoose");

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

// 스키마를 모델로 감싸주기
const User = mongoose.model("User", userSchema);

// 모델을 다른 파일에서도 사용할 수 있게 export
module.exports = { User };

5. Git 연결 (SSH로 안전하게 연결)

// SSH 설정하기
github generating ssh 검색
ssh key 생성 후 github 설정가서 ssh key 등록하기

<윈도우 SSH 확인방법>
cd ~/.ssh
ls

6. Body-parser 설치

// 클라이언트에서 오는 정보를 서버에서 분석해서 가져올 수 있게
const bodyParser = require("body-parser");

// application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: true }));

// application/json
app.use(bodyParser.json());

7. Rest Client 사용 (test.rest 파일 만들어서 test하면 postman 대체 가능)

GET http://localhost:3000/ HTTP/1.1


### 
POST http://localhost:3000/register/ HTTP/1.1
Content-Type: application/json; charset=UTF-8

{
  "name": "gaeun",
  "email": "gaeun@gaeun.com",
  "password": "1234abcd"  
}

8. NODE MON 다운로드

  • 코드 변경했을 때 서버 내리고 재시작하지 않아도 변경된 부분 바로 반영됨
  • npm i nodemon --save-dev (develop 일때만 쓰겠다는 의미)
  • package.json에서 내용 추가하고 이제부터 시작할 땐 npm run backend

현재 package.json

{
  "name": "node_react",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "backend" : "nodemon index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.20.2",
    "express": "^4.18.2",
    "mongoose": "^7.5.2"
  },
  "devDependencies": {
    "nodemon": "^3.0.1"
  }
}

9. 비밀 설정 정보 관리

  • mongoDB URI 관리를 위해 config 폴더 아래 dev.js key.js prod.js 파일을 생성함
// key.js
if (process.env.NODE_ENV === "production") {
  module.exports = require("./prod");
} else {
  module.exports = require("./dev");
}
// dev.js
module.exports = {
  mongoURI:
    "본인꺼!!",
};
// prod.js
module.exports = {
  mongoURI: process.env.MONGO_URI,
};

10. Bcrypt로 비밀번호 암호화 (아래 순서로 진행)

  • npm i bcrypt 로 설치
  • saltRounds = 10 (10 자리인 salt)
  • salt 생성
  • salt 이용해서 비밀번호 암호화

User.js 코드 변경

const mongoose = require("mongoose");
const bcrypt = require("bcrypt");
const saltRounds = 10; // 10자리인 salt 만들기

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

// save 하기 전에 이 코드를 수행함
userSchema.pre("save", function (next) {
  var user = this; // this는 userSchema 의미함

  if (user.isModified("password")) {
    // 비밀번호 암호화
    bcrypt.genSalt(saltRounds, function (err, salt) {
      if (err) return next(err);

      bcrypt.hash(user.password, salt, function (err, hash) {
        // user.password는 순수한 비밀번호이고 hash는 암호화된 비밀번호
        if (err) return next(err);

        user.password = hash;
        next();
      });
    });
  } else {
    next();
  }
});

// 스키마를 모델로 감싸주기
// mongoDB에 users 테이블 생성됨
const User = mongoose.model("User", userSchema);

// 모델을 다른 파일에서도 사용할 수 있게 export
module.exports = { User };

11. 로그인하기 (+ 토큰 생성)

User.js

const mongoose = require("mongoose");
const bcrypt = require("bcrypt");
const saltRounds = 10; // 10자리인 salt 만들기
const jwt = require("jsonwebtoken");

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

// save 하기 전에 이 코드를 수행함
userSchema.pre("save", function (next) {
  var user = this; // this는 userSchema 의미함

  if (user.isModified("password")) {
    // 비밀번호 암호화
    bcrypt.genSalt(saltRounds, function (err, salt) {
      if (err) return next(err);

      bcrypt.hash(user.password, salt, function (err, hash) {
        // user.password는 순수한 비밀번호이고 hash는 암호화된 비밀번호
        if (err) return next(err);

        user.password = hash;
        next();
      });
    });
  } else {
    next();
  }
});


userSchema.methods.comparePassword = async function (plainPassword) {
  try {
    // plainPassword 1234567 이런 것     this.password는 암호화된 패스워드
    const isMatch = await bcrypt.compare(plainPassword, this.password);
    return isMatch;
  } catch (err) {
    throw err;
  }
};


userSchema.methods.generateToken = async function () {
  const user = this;

  // jsonwebtoken을 이용해서 token을 생성하기
  const token = jwt.sign(user._id.toHexString(), "secretToken");

  user.token = token;

  try {
    await user.save();
    return user;
  } catch (err) {
    throw err;
  }
};

// 스키마를 모델로 감싸주기
// mongoDB에 users 테이블 생성됨
const User = mongoose.model("User", userSchema);

// 모델을 다른 파일에서도 사용할 수 있게 export
module.exports = { User };

index.js

const express = require("express");
const app = express();
const port = 3000;
const { User } = require("./models/User");
const cookieParser = require("cookie-parser");

// 클라이언트에서 오는 정보를 서버에서 분석해서 가져올 수 있게
const bodyParser = require("body-parser");

// application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: true }));

// application/json
app.use(bodyParser.json());
app.use(cookieParser());

const mongoose = require("mongoose");

const config = require("./config/key");

mongoose
  .connect(config.mongoURI, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
    //   useCreateIndex: true,
    //   useFindAndModify: false,
  })
  .then(() => console.log("몽고DB connected!"))
  .catch((err) => console.log(err));

app.get("/", (req, res) => res.send("Hello world~~"));


// 회원가입
app.post("/register", async (req, res) => {
  // 회원가입 할때 필요한 정보들을 client에서 가져와서
  // 데이터에 넣어준다

  const user = new User(req.body);

  try {
    // save 메서드를 프로미스로 대체하여 사용
    await user.save();
    return res.status(200).json({
      success: true,
    });
  } catch (err) {
    return res.json({ success: false, err });
  }
});



// 로그인
app.post("/login", async (req, res) => {
  try {
    // 요청된 이메일을 데이터베이스에 있는지 찾는다
    const user = await User.findOne({ email: req.body.email });

    if (!user) {
      return res.json({
        loginSuccess: false,
        message: "제공된 이메일에 해당하는 유저가 없습니다.",
      });
    }

    // 요청된 이메일이 데이터베이스에 있다면 비밀번호가 맞는 비밀번호인지 확인
    const isMatch = await user.comparePassword(req.body.password);

    if (!isMatch) {
      return res.json({
        loginSuccess: false,
        message: "비밀번호가 틀렸습니다.",
      });
    }

    // 비밀번호가 맞다면 token 생성하기
    await user.generateToken();

    // 토큰을 저장한다. 어디에? [쿠키] 또는 로컬스토리지 또는 세션
    res
      .cookie("x_auth", user.token)
      .status(200)
      .json({ loginSuccess: true, userId: user._id });
  } catch (err) {
    console.error(err);
    res.status(500).json({ loginSuccess: false, message: "서버 에러" });
  }
});

app.listen(port, () => console.log(`포트 연결 중.. ${port}!`));

12. Auth 기능

 

  • Auth 필요한 이유 : 페이지 이동 시, 로그인 되어 있는지 또는 관리자 유저인지 체크. 글쓸때 권한 있는지 체크
  • 앞에서 user._id + secretToken = token 이였고
  • token을 decode하면 user._id를 알 수 있다!

middleware/auth.js

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

// 인증 처리 하는 곳
// 유저 있으면 인증 OK, 유저 없으면 인증 No
let auth = async (req, res, next) => {
  try {
    // 클라이언트에서 쿠키에서 토큰을 가져온다
    let token = req.cookies.x_auth;

    // 토큰을 복호화 후 유저를 찾는다
    const user = await User.findByToken(token);

    if (!user) {
      return res.json({ isAuth: false, error: true });
    }

    // user가 있는 경우
    req.token = token;
    req.user = user;
    next(); // next 해야 다음으로 넘어갈 수 있게
  } catch (err) {
    console.error(err);
    //res.status(500).json({ isAuth: false, error: true, message: "인증 에러" });
  }
};

module.exports = { auth };

 

index.js 에 추가된 부분

// auth 인증
app.get("/api/users/auth", auth, async (req, res) => {
  // 여기까지 미들웨어 통과했다는 뜻 = Authentication이 True라는 의미
  res.status(200).json({
    _id: req.user._id,
    isAdmin: req.user.role === 0 ? false : true, // role 0 : 일반유저    0이 아니면 관리자
    isAuth: true,
    email: req.user.email,
    lastname: req.user.lastname,
    role: req.user.role,
    image: req.user.image,
  });
});

 

13. 로그아웃

 

 

 

 

index.js 만 바꿔주면 된다


// 로그아웃
app.get("/api/users/logout", auth, async (req, res) => {
  try {
    // auth middleware에서 가져온 req.user를 이용해 로그아웃 처리
    const user = await User.findOneAndUpdate(
      { _id: req.user._id },
      { token: "" }
    );

    if (!user) {
      return res.json({ success: false, message: "로그아웃에 실패했습니다." });
    }

    return res.status(200).send({
      success: true,
      message: "로그아웃에 성공했습니다.",
    });
  } catch (err) {
    console.error(err);
  }
});

 

참고자료

인프런 강의

'개발자가 되었다?' 카테고리의 다른 글

Day 30, React  (0) 2023.09.26
Day 29, React  (0) 2023.09.25
Day 27, Typescript  (0) 2023.09.22
Day 26, Node.js  (0) 2023.09.20
Day 25, JS, React  (0) 2023.09.19