[Node.js 찍먹하기] Chapter 06 : 리펙토링
🔙 유저를 검증하는 로직을 좀더 좋은 코드로 리팩토링하자
Ctrl에 유저 데이터 로직을 사용하다 보니 너무 가시성이 떨어진다
ctrl에서 user.login()만 해도 실행되게 하고 싶다
⇒ 모듈화를 하자
그러기 위해선 User를 이용해 ⇒ user라는 인스턴스를 만들고
사용자가 넘긴 body 데이터를 기본적으로 가지고 있게 만들것이다
즉, 이 User에는 사용자의 특성을 갖게 만들것이다
User.js
"use strict"
const UserStorage = require("./UserStorage");
class User {
constructor(body) {
this.body = body; // home.ctrl 에서 new User(req.body) // 이렇게 보내면 User에 body변수에 req.body가 바인딩됨
}
login() {
//
}
}
module.exports = User;
이게 뭔말이냐면 로그인할때 생성되는 데이터는 User의 인스턴스가 갖게 되야한다(로직을 해야하니깐)
그렇게 되면 User의 인스턴스가 응답을 반환하고, ctrl은 그 응답을 json으로 다시 보내주면된다
- ctrl에서 생성자 호출할때 req.body 부분을 보내주면 데이터가 User가 생성한 인스턴스로 간다
로직의 구조
id, password만 인증 받고 싶지만
데이터가 리스트 통채로 날라오기 때문에 조건으로 처리할수가 없다
=> 요청한 데이터들만 가져오는 메서드를 만들자(UserStorage에서)
User.js
"use strict"
const UserStorage = require("./UserStorage");
class User {
constructor(body) {
this.body = body; // home.ctrl 에서 new User(req.body) // 이렇게 보내면 User에 body변수에 req.body가 바인딩됨
}
login() {
const body = this.body // 겹치니깐 묶자
const { id, password } = UserStorage.getUserInfo(body.id);
if (id) {
if (id === body.id && password === body.password) {
return { success: true };
}
return { success: false, msg: "비밀번호가 틀렸습니다" }
}
return { success: false, msg: "존재하지 않는 아이디 입니다." }
}
}
module.exports = User;
UserStorage.js
"use strict";
class UserStorage {
static #users = {
id: ["양상우", "김개발", "박부장"],
password: ["1234", "1234", "123456"],
name: ['양땅우', '땡땡이', '곱창먹고싶다'] /
}
static getUsers(...fields) {
const users = this.#users;
const newUsers = fields.reduce((newUsers, field) => {
if (users.hasOwnProperty(field)) {
newUsers[field] = users[field]
}
return newUsers
}, {});
return newUsers
}
static getUserInfo(id) {
const users = this.#users;
const idx = users.id.indexOf(id); // id의 인덱스를 뽑음
const usersKeys = Object.keys(users); // user를 받아오고 그 key 값 들만 배열로 만들거임 => [id, password, name]
const userInfo = usersKeys.reduce((newUser, info) => {
newUser[info] = users[info][idx];
return newUser;
}, {});
return userInfo;
//
}
}
module.exports = UserStorage;
UserStorage.getUserInfo("id") 라 하면 그에 해당하는 데이터만 보내게 하자
userkeys 가 순차적으로 들어감 id이면 ["양상우", "김개발", "박부장"] 이렇게 그렇고 그 id (양상우)에서 idx(0), 즉 해당 하는 (0) 의 user 키의 값들 id[0], password[0], name[o]을 newUser에 순차적으로 넣음
home.ctrl
const procces = {
login: (req, res) => {
const user = new User(req.body)
const response = user.login();
return res.json(response);
},
}
이렇게 하면 UserStorage에서 정제한 데이터를 ⇒ User에서 인증을 하고 인증 결과를 ⇒ ctrl에서 json으로 응답 ⇒ login 패치로 응답을 받아서 ⇒ 표시한다
➬ 이제 로그인 화면을 꾸며보자
codepen
템플릿을 제공해준다
- html
- css
를 변경해보자
회원 가입 화면도 만들자
register.ejs 와 register를 라우트 해주자(라우트 + 컨트롤)
<!DOCTYPE html>
<html lang="ko">
<!--브라우저가 언어 인식-->
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="css/home/login.css">
<script src="/js/home/login.js" defer></script>
<title>Document</title>
</head>
<body>
<div class="login-page">
<div class="form">
<form class="login-form">
<input id="id" type="text" placeholder="아이디" />
<input id="name" type="text" placeholder="이름" />
<input id="password" type="password" placeholder="비밀번호" />
<input id="confirm-password" type="password" placeholder="비밀번호 확인" />
<button>SIGN UP</button>
<p class="message">Already registered? <a href="/login">login</a></p>
</form>
</div>
</div>
</body>
</html>
- 기존 로그인 창에서 몇개만 변경하면 된다
회원가입시 입력한 데이터를 API를 통해 처리 해야한다
기존 login이랑 다를게 없다
하지만 몇개만 수정하자
1. form > buttom 일때 buttom이 submit 처럼 됨
form 으로 감싸져 있음 그렇다, buttom을 p 태그로 바꾸고, id에 buttom을 주자
<p id="button">SIGN UP</p>
이렇게 하면 css도 변경해줘야한다
.form #button {
font-family: "Roboto", sans-serif;
text-transform: uppercase;
outline: 0;
background: #4CAF50;
width: 80%; 이것도 바꾸자
border: 0;
/* 가운데 정렬 0 auto */
margin: 0 auto;
padding: 15px;
color: #FFFFFF;
font-size: 14px;
-webkit-transition: all 0.3 ease;
transition: all 0.3 ease;
cursor: pointer;
}
2. register.js 변경
"use strict";
const id = document.querySelector("#id"),
name = document.querySelector("#name"),
password = document.querySelector("#password"),
confirmPassword = document.querySelector("#confirm-password"),
registerBtn = document.querySelector("#button");
registerBtn.addEventListener("click", register);
if (!id.value) return alert("아이디를 입력해주십시오")
if (password.value !== confirmPassword.value) {
return alert("비밀번호가 일치하지 않습니다.")
}
function register() {
const req = {
id: id.value,
name: name.value,
password: password.value,
};
console.log(req)
// 지금은 register API가 없다, 다음 강의에 만든다
fetch("/register", {
method: "POST",
headers: {
"Content-Type": "application/json" //
},
body: JSON.stringify(req)
})
.then((res) => res.json())
.then((res) => {
if (res.success) {
location.href = "/login";
} else {
alert(res.msg)
}
})
.catch((err) => {
console.error(new Error("회원가입중 에러 발생"))
})
}
로그인과 비슷하지만, 변수할당 제대로 해주자. 이 다음 API를 만들자
🔙 register API 구현하자
get은 저번에 했으니 post을 구현할것 index.js에 라우트 설정 → home.ctrl에서 post설정 → User에서 데이터 저장 요청 → UserStorage에서 데이터 저장하는 로직 설정
//index.js
router.post("/register", ctrl.procces.register); // 레지스터 post 추가
//home.ctrl.js
const procces = {
login: (req, res) => {
const user = new User(req.body)
const response = user.login();
return res.json(response);
},
// 추가
register: (req, res) => {
const user = new User(req.body)
const response = user.register();
return res.json(response);
},
}
//User.js
register() { // 단순하게 데이터 저장되게
const client = this.body
const response = UserStorage.save(client)
return response; // body를 좀더 직관적이게 client로 변경
}
//UserStorage.js
static save(userInfo) {
const users = this.#users
users.id.push(userInfo.id);
users.name.push(userInfo.name);
users.password.push(userInfo.password);
return { success: true }
}
하지만 문제점?
= 단순하게 UserStorage에 있는 user 객체에 push를 해봤자, 서버가 꺼지면 사라지는 데이터가 된다, 진짜 파일에 저장할수있게 해야한다
일단 파일로 DB를 생성하자
src/databases/users.json
으로 DB를 만들자
{
"id": [
"양상우",
"김개발",
"박부장"
],
"password": [
"1234",
"1234",
"123456"
],
"name": [
"양땅우",
"땡땡이",
"곱창먹고싶다"
]
}
UserStorage에 있던 users를 여기로 옮겨 분리하자
그럼 기존에 사용하던 코드들은..? 저 DB 어떻게 사용해..?
사용 방법은 fs를 읽어오면된다, fs는 node.js의 모듈인데, 파일 시스템 처리를 담당한다
const fs = require("fs")
현재 기존 users 데이터를 사용하는 UserStorage에서 fs로 user을 가져오 변수 user에 넣으면 기존에 사용하던 코드들을 사용할수 있다
"use strict";
// 파일을 접근하기위해 파일 가져옴
const fs = require("fs").promises // 이렇게 하면 fs도 프로미스로 반환 하ㅔ 된다
class UserStorage {
static getUsers(...fields) {
// const users = this.#users;
const newUsers = fields.reduce((newUsers, field) => {
if (users.hasOwnProperty(field)) {
newUsers[field] = users[field]
}
return newUsers
}, {});
return newUsers
}
static getUserInfo(id) {
// 현재 경로 루트는 app.js이다
// 리드파일도 프로미스를 반환한다.
fs.readFile("./src/databases/users.json", (err, data) => { // 첫번쨰 인자에 경로, 두번째 인자에 에러 처리와 data를 선언
if (err) throw err;
//console.log(JSON.parse(data)) // 이런식으로 파싱 안하면 16진수로 나온다
const users = JSON.parse(data) // data는 fs.readFile의 콜백함수의 지역변수 이기 때문에 사용할려면, 스코프 범위를 잘맞춰야 한다
const idx = users.id.indexOf(id); // 하지만 맞춰도, userInfo가 리턴하는것을 전역으로도 가지고 있어야 User가 사용할수 있다. -> 프로미스의 async await를 사용하자
const usersKeys = Object.keys(users);
const userInfo = usersKeys.reduce((newUser, info) => {
newUser[info] = users[info][idx];
return newUser;
}, {});
return userInfo;
})
}
// 단순하게 여기서 push 로 users에 넣어도 당장은 되지만 서버가 꺼지면 해당 데이터가 사라져 버린다.
static save(userInfo) {
// const users = this.#users
users.id.push(userInfo.id);
users.name.push(userInfo.name);
users.password.push(userInfo.password);
return { success: true }
}
}
module.exports = UserStorage;
- 일단은 getUserInfo에서 로그인에 사용된 users를 사용해보자, 파일을 읽을때는 readFile함수를 사용하는데 첫번째 인자에 파일 경로, 두번째 인자에는 err와 data를 가져오면 된다
- data는 16진수로 받아오는데 이걸 JSON.parse해줘야 읽을수가 있다
- 하지만 이렇게 가져와도 return되는 userInfo는 정상적인 값을 반환하지 않는다 왤까
readFile함수가 파일을 읽어오기 전에 return되기 때문! ⇒ 프로미스, async await를 사용하자
readFile을 promise로 반환하게 만들자
const fs = require("fs").promises
// 이렇게 하면 fs도 프로미스로 반환 하게 된다
const fs = require("fs").promises
// 이렇게 하면 fs도 프로미스로 반환 하게 된다
이렇게 된다면 then, catch로 후속처리가 가능하다
추가로 안에 로직은 은닉화해서 만들자
.. 생략...
static getUserInfo(id) {
// 프로미스르 반환하는 fs
return fs.readFile("./src/databases/users.json")
.then((data) => {
return this.#getUserInfo(data, id) // UserStorage에 this 바인딩
})
.catch(console.error);
... 생략 ...
"use strict";
// 파일을 접근하기위해 파일 가져옴
const fs = require("fs").promises // 이렇게 하면 fs도 프로미스로 반환 하ㅔ 된다
class UserStorage {
// 가독성을 위해서 분리함, 암묵적인 값은 되도록이면 최상단으로 가자
static #getUserInfo(data, id) {
const users = JSON.parse(data)
const idx = users.id.indexOf(id);
const usersKeys = Object.keys(users);
const userInfo = usersKeys.reduce((newUser, info) => {
newUser[info] = users[info][idx];
return newUser;
}, {});
// 프로미스의 pending은 아직 데이터를 다 읽어오지 못했다는 뜻 => fs가 못읽음 => userInfo는 잘못된 값이 들어감 => await를 이용하자
return userInfo;
}
static getUsers(...fields) {
// const users = this.#users;
const newUsers = fields.reduce((newUsers, field) => {
if (users.hasOwnProperty(field)) {
newUsers[field] = users[field]
}
return newUsers
}, {});
return newUsers
}
static getUserInfo(id) {
// 프로미스르 반환하는 fs
return fs.readFile("./src/databases/users.json")
.then((data) => {
return this.#getUserInfo(data, id) // UserStorage에 this 바인딩
})
.catch(console.error);
}
static save(userInfo) {
// const users = this.#users
users.id.push(userInfo.id);
users.name.push(userInfo.name);
users.password.push(userInfo.password);
return { success: true }
}
}
module.exports = UserStorage;
하지만 이렇게 해도 undefind
가 계속 생긴다 왤까 ⇒ getUserInfo를 호출 하는 User.js에 login 함수는 기다리지 않고 바로 반환 받기 때문이다
⇒ login함수 를 asycn로 만들어 호출할때 await를 사용할수 있게 만들자
User.js
.. 생략
async login() {
const client = this.body
const { id, password } = await UserStorage.getUserInfo(client.id); // fs를 읽어서 userInfo를 생성하는데. userInfo값이 제대로 올떄까지 기다려
// 하지만 ctrl에서 기다려 해줘야함
if (id) {
if (id === client.id && password === client.password) {
return { success: true };
}
return { success: false, msg: "비밀번호가 틀렸습니다" }
}
return { success: false, msg: "존재하지 않는 아이디 입니다." }
}
.. 생략
마찬가지로 User.js에 login 값을 받아 오는 home.ctrl의 process또한 마찬가지로..
home.ctrl
.. 생략
const procces = {
login: async (req, res) => {
const user = new User(req.body)
const response = await user.login();
return res.json(response);
},
register: (req, res) => {
const user = new User(req.body)
const response = user.register();
return res.json(response);
},
}
.. 생략
이제 잘된다
프론트 에서 데이터를 파일에 저장하는 방법은?
POST로 오는 과정을 봐보자
register.js 에서 post → index.js 에서 라우트 → home.ctrl에서 procces 를 통해 reponse를 받아옴 → 이 respones는 user.register 함수를 이용해 받아오는데 → User.js 클래스를 통해 받아오고, 해당 로직은 UserStorage.js에 명시되어있다
UserStorage.js
.. 생략
//로직 분리
static #getUsers(data, isAll, fields) {
const users = JSON.parse(data)
// 싹다
if (isAll) return users; // isAll이 true이면 user를 반환한다
// fields 해당하는것만
const newUsers = fields.reduce((newUsers, field) => {
if (users.hasOwnProperty(field)) {
newUsers[field] = users[field]
}
return newUsers
}, {});
return newUsers
}
// 파일 시스템 형태로 바꿔주자
static getUsers(isAll, ...fields) { // isall그후 id, password.... 등등ㄷ으
return fs.
readFile("./src/databases/users.json")
.then((data) => {
return this.#getUsers(data, isAll, fields)
})
.catch(console.error);
}
.. 생략
static async save(userInfo) {
const users = await this.getUsers(true) // DB에 있는 사용자 데이터를 가져와서 users에 가지고 있음
//const data = "a"; // 덮어 씌우짐, 그래서 읽어온 다음에 우리가 추가하고 싶은 데이터를 넣어야한다\
if (users.id.includes(userInfo.id)) { // 등록되어있지 않는 id 만 가입할수 있게
throw "이미 존재하는 아이디 입니다." // 하지만 이 에러는 reponse 가게 된다, 이 함수의 호출자가 reponse에 있기에.. 그래서 return => throw 해줘야함 //Error 형태로 보내면 object로 가니 문자열만 보내자
}
users.id.push(userInfo.id);
users.name.push(userInfo.name);
users.password.push(userInfo.password)
// 이렇게 임시적으로 추가된 데이터를 -> 찐으로 넣음
fs.writeFile("./src/databases/users.json", JSON.stringify(users)) // 문자열 타입으로 data를 다시 보내야하기때문에..
return { success: true }
}
-
그냥 fs.writeFile을 이용하면 파이썬 처럼 추가 모드가 없기때문에, 기존 데이터를 덮어씌우는 형태가 된다
-
그래서 기존 파일에 있는데이터를 저장한뒤에, 사용자가 입력한 데이터를 push하고 해당 데이터를 넣으면 된다
-
기존 파일에 있는 user는 getusers를 통해 가져오는데, isAll을 추가해서, true일 경우 filed로 하나하나 넣을 필요 없이 전체를 다 가져올수있게 해주자
-
그리고 id가 이미 있으면 문자열을 리턴해주자
⇒ 에러 메세지로 보내면 object로 보내지기 때문에 문자열로 주자,
⇒ object가 err인데 err로 받으면 좋겠지만 해당 에러 객체는 respones로 반환이 되기 때문이다.
User.js
.. 생략
async register() {
try {
const client = this.body
const response = await UserStorage.save(client)
return response;
} catch (err) {
return { success: false, msg: err } // 이렇게 msg로 보내야하는데, err가 object로 넘어오기때문에 응답을 alert로 보여줘야하는 JS에서는 걍 object만 나온다 그래서 그냥 던지지 말고. UserStoage에서 thorw로만 보내자
}
}
이제 마찬가지로 ctrl까지 await를 잘 걸어주면 된다
Author And Source
이 문제에 관하여([Node.js 찍먹하기] Chapter 06 : 리펙토링), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@dndb3599/Node.js-찍먹하기-Chapter-06-리펙토링저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)