[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

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도 프로미스로 반환 하게 된다

이렇게 된다면 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를 잘 걸어주면 된다

좋은 웹페이지 즐겨찾기