웹 개발

[포스코X코딩온] MVC - 로그인 만들기

끊임없이 성장중인 개발자 2023. 12. 4. 19:22
728x90
반응형

포스팅 주제

  • MVC패턴을 사용하여 로그인 만들기 ( MySQL연동)

MVC패터을 이용하여 로그인 기능 구현 ( MySQL 연동 )

준비물

  • npm 설치 ( express, ejs, mysql2 )
  • MySQL WorkStation 데이터베이스

 

1) 기초 공사

먼저 기초 파일(폴더 들)을 만들어 준다. (이전 포스팅에서 다룬 폴더 구조와 비슷하다.)

 

 

페이지는 총 5개로 구성되어 있습니다!

  1. 메인페이지
  2. 가입 페이지
  3. 로그인페이지
  4. 정보 수정페이지
  5. 404 오류 페이지

 

각 페이지 화면은 아래처럼 만들었다.

 

 

 

** 규칙)

  1. 메인 페이지에서는 회원가입, 로그인 페이지로 이동 가능하다. (회원정보 수정 페이지 접근시 에러)
  2. 회원정보는 로그인시에만 접근 가능
  3. 로그인시 이미 존재하는 ID는 사용불가능
  4. 회원가입시 로그인 페이지로 이동
  5. 로그인 성공 시 성공 팝업 후 회원정보 페이지로 이동
  6. 로그인 실패 시 로그인 실패 팝업
  7. 회원정보 수정 페이지 접근시 ID는 수정불가, password, name은 수정 가능
  8. 회원정보 삭제시 홈페이지로 이동

 

가장 먼저 MySQL에서 DB를 생성해줘야 한다!!

 

ID를 Primary Key로 주었기 때문에, ID중복이 일어나면 안된다. (고유키)

 

axios를 사용할거기 때문에 미리 head에 넣어 놓자

    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>

 

=> 메인페이지

<body>
    <h1>실습. 회원가입과 로그인 DB연동</h1>
    <a href="/user/singup">회원가입</a>
    <a href="/user/singin">로그인</a>
    <a href="/user/profile">회원정보수정</a>
</body>

 

=> 회원가입 페이지

<h1>회원가입</h1>
    <form name="newUser">
        <input type="text" id="id" name="id" required>
        <label for="">ID</label><br>
        <input type="password" id="pw" name="pw" required>
        <label for="">Password</label><br>
        <input type="text" id="name" name="name" required>
        <label for="">Name</label><br>
        <button type="button" onclick="singUP()">Register</button><br>
    </form>
    <a href="/user/singin">Login</a>

 

=> 로그인 페이지

<body>
    <h1>로그인</h1>
    <form name="logIn">
        <input type="text" id="id" name="id" required>
        <label for="">ID</label><br>
        <input type="password" id="pw" name="pw" required>
        <label for="">Password</label><br>
        <button type="button" onclick="login()">Login</button><br>
    </form>

    <!-- 로그인 버튼 클릭 시(성공), profile.ejs에서 특정 유저 정보를 보여줘야 하기 때문에 id정보를 value에 저장해서 넘겨줌 -->
    <!-- 로그인 성공 시, 프로필 페이지로 이동 -->
    <form name="hidden-form" action="/user/profile" method="post">
        <!-- type: hidden 안보임 -->
        <input type="hidden" name="id" />
    </form>
    <a href="/user/singup">Register</a>
</body>

 

=> 회원정보 수정 페이지

<body>
    <h1>회원정보</h1>
    <a href="/user">home</a><br>
    <p>ID 수정 불가능, Password, Name 수정 가능</p><br>
    <form name="PROFILE">
        <input type="text" id="id" name="id" value="<%= data.id %>" readonly>
        <label for="">ID</label><br>
        <input type="password" id="pw" name="pw" value="<%= data.pw %>">
        <label for="">Password</label><br>
        <input type="text" id="name" name="name" value="<%= data.name %>">
        <label for="">Name</label><br>
        <button type="button" onclick="Edit()">Edit</button><br>
        <button type="button" onclick="Delete()">Delete</button>
    </form>
</body>

 

=> 404 페이지

<body>
    <h1>⚠️ 404 Error</h1>
    <p>잘못된 접근입니다.</p>
</body>

 

 

2) app.js 설정 ( 서버 설정 )

const express = require('express');
const app = express();
const PORT = 8000;

app.set('view engine', 'ejs');
app.set('views', './views');

app.use(express.urlencoded({extended:true}));
app.use(express.json());

app.use('/static', express.static(__dirname + '/static'));

const indexRouter = require('./routes/user');
app.use('/user', indexRouter);

app.get('*', (req,res)=>{
    res.render('404');
})

app.listen(PORT, (req,res) => {
    console.log(`localhost:${PORT}`);
})

 

3) 회원가입 기능 !

1. routes >  user.js 설정

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

const controller = require('../controller/Cuser');

// GET /user
router.get('/', controller.main);

// GET /user/singup
router.get('/singup',controller.singup)
//post /user/singup
router.post('/singup', controller.singup_new);

 

 

2. controller > Cuser 설정

const exp = require('constants');
const User = require('../model/User');

exports.main = (req,res) => {
    res.render('index');
}

// 가입화면 표시
exports.singup = (req,res) => {
    res.render('singup');
}
// 가입 post
exports.singup_new = (req,res) => {
    // 뷰(요청) -> 라우터 -> 컨트롤러 -> 모델 -> DB -> 모델 -> 컨트롤러 -> 뷰(응답)
    console.log('req.body >>> ', req.body);
    const{id, pw, name} = req.body;

    User.postDB(req.body, (result) => {
        console.log('psotDB, Cuser.js result >>',result);
        if(result.error){
            res.send({isLogin:false})
        }else{
            res.send({isLogin:true, id: id, pw: pw, name: name});
        }
        // res.send({id: id, pw: pw, name: name});
    })
}

 

모든 요청은 뷰(요청) -> 라우터 -> 컨트롤러 -> 모델 -> DB -> 모델 -> 컨트롤러 -> 뷰(응답)

으로 이루어지는 과정을 머리 속으로 생각하고 설정하면 편합니다.

 

현재 입력한 값이 post로 가기 때문에 req.body로 값을 받아줍니다.

들어오는 값 req.body를 postDB로 보냅니다.

 

반환받은 값 result에 error가 있으면 "isLogin: false"를 전송해 줍니다.

아니라면, isLogin:true와 id,pw,name값을 담아서 보내줍니다.

 

3. model > User.js 설정

이곳에서는 데이터 베이스에 값을 가져오거나 입력하는 장소입니다.

 

const mysql = require('mysql2');

const conn = mysql.createConnection({
    host: 'localhost', // DB가 설치된 호스트 IP주소
    user: 'user', //DB접속 유저이름
    password: '1234', //DB접속 비밀번호
    database: 'kdt' // DB이름
})

// 데이터 베이스 값 입력
exports.postDB = (data,cb) =>{
    console.log('postDB > ',data);

    const sql = "INSERT INTO user (id, pw, name) value( ?, ?, ?)";
    const values = [data.id, data.pw, data.name];
    
    // id 중복 체크
    const sql2 = "SELECT * FROM user where id = ?";
    const values2 = [data.id];

    conn.query(sql2,values2,(err,rows) =>{
        if(err) throw err;

        console.log(rows);
        if(rows.length > 0 ){
            cb({error: "이미 존재하는 ID입니다. 값을 다시 입력해주세요"});
        }else{
            conn.query(sql,values, (err, rows) => {
                if(err) throw err;
        
                console.log('User.js (데이터베이스) > ' ,rows);
        
                cb(rows);
            })
        }
    })

 

ID는 데이터베이스에서 기본값 KEY이기 때문에 하나만 존재해야 합니다.

그러기 위해서는 DB에 값이 존재하는지 체크하고 존재하면 오류 메시지를 보내고 존재하지 않다면 DB에 유저가 입력한 값을 INSERT해줘야 합니다.

 

값이 존재하면 rows에 값이 나오기 때문에 "rows.length > 0"으로 체크하고 중복된 값이면 error에 메시지를 담아서 CB(콜백)에 감싸서 보 콜백(반환) 해줍니다.

 

만약 중복된 ID값이 없다면 INSERT SQL문을 입력해서 사용자가 입력한 id, pw, name을 등록해주고 이를 cb에 넣어서 반환해 줍니다.

 

4. static > user.js 설정

// 회원가입 버튼
function singUP(){
    const form = document.forms['newUser'];

    if(!form.checkValidity()){
        form.reportValidity();
        return;
    }

    axios({
        method: 'post',
        url: '/user/singup',
        data: {
            id : form.id.value,
            pw : form.pw.value,
            name: form.name.value
        }
    }).then((res) => {
        console.log('res.data >> :', res.data);
        const {data} = res;

        if(!data.isLogin){
            alert('이미 존재하는 회원입니다! 아이디를 확인해주세요 :)');
            form.reset(); // form 초기화
        }else{
            alert('회원가입 성공');
            document.location.href = '/user/singin';
        }
    })
}

 

checkValidity()로 폼이 유효한지 검사한다. 값이 fales가 들어오면 안들어오면 reportValidity()를 실행하여 클라이언트에게 오류 메시지를 보여준다.

 

axios를 사용하여 값을 서버로 전송할 것이다. id와 비밀번호 같은 민감한 내용을 다루기 때문에 method는 post방식으로 할 것이다.

값이 입력되면 controller로 값이 전달 -> DB로 값이 전달 -> DB체크 -> controller로 반환 -> 다시 function singUp에 then에 값이 반환될 것이다(성공했다면)

 

ID값이 중복되었다면 isLogin이 false로 전달 됐을것이다. 그럼 "이미 값이 존재합니다." alert가 실행되고 폼에 값들이 리셋된다.

반대로 isLogin에 true로 값이 오면 회원가입 성공 팝업이 뜨고, /user/singin으로 사이트가 이동될 것이다.

 

 

4) 로그인 기능!

1 . routes > user.js

// GET /user/singin => post
router.get('/singin', controller.singin);
// POST /user/singin
router.post('/singin', controller.sigin_check);

 

2. controller > Cuser 설정

 

// 로그인
exports.singin = (req,res) => {
    res.render('singin');
}
exports.sigin_check = (req, res) => {
    console.log('req.body >' , req.body)
    User.postCheck(req.body, (result) => {
        // result:rows
        if(result.length > 0 ) res.send({isLogin:true,userInfo:result[0]});
        else(res.send({isLogin:false}))
    })
}

 

사용자에게 값을 받은 req.body를 model에 있는 postCheck 으로 보낸다.

 

3. model > User.js

// ID와 PW 데이터베이스 체크
exports.postCheck = (data,cb) => {
    const sql = "SELECT id,pw FROM user WHERE id = ? AND pw = ?";
    const values = [data.id, data.pw];

    conn.query(sql,values, (err, rows) => {
        if(err) throw err;
        console.log('아이디와 비밀번호는 ? > ', rows);
        cb(rows);
    })
}

 

로그인 페이지에서 입력한 값이 DB에 존재해야 하는지 확인해야 하기 때문에 SQL문으로 DB를 확인해야한다.

값이 있으면 rows를 통해 반환

 

그럼 이 반환값(rows)를 가지고 controller에서 사용한다.

 

4. static > user.js

// 로그인 버튼
async function login(){
    const form = document.forms['logIn'];
    
    if (!form.checkValidity()) {
        form.reportValidity();
        return;
    }
    try{
        let res = await axios({
            method: 'post',
            url: '/user/singin',
            data: {
                id : form.id.value,
                pw: form.pw.value
            }
        })
        console.log(res.data)
        const { data } = res;

        if(data.isLogin){
            alert('로그인 성공')
            
            //프로필 페이지 요청 보내기
            const form_info = document.forms['hidden-form'];
            form_info.id.value = form.id.value;
            form_info.submit()
        } else{
            alert('로그인 실패!');
            form.reset();
        }
    }
    catch(err){
        console.log(err);
    }
}

 

이번에는 async-wait을 사용해보았다.

 

만약 DB를 체크했을 때 값이 존재하다면 isLogin:true가 반환 되었을 것이고, 그럼 if(data.isLogin)이 실행된다.

화면에 "로그인 성공" 팝업을 표시하고, hidden-form에 id값을 주고 해당 폼으로 submit한다.

(submit시 mathod에 작성한 경로로 이동)

 

5 ) 회원정보 수정페이지 ( 수정 , 삭제 )

1.  routes > user.js

// POST /user/profile
router.post('/profile', controller.profile);
// PATCH /user/profile/edit
router.patch('/profile/edit', controller.profileEdit);
// DELETE /user/profile/delete
router.delete('/profile/delete', controller.profileDelete);

 

2. controller > Cuser.js

// 프로필 수정(id값 가져오기)
exports.profile = (req,res) => {
    console.log(req.body);
    User.get_user(req.body.id, (result) =>{
        if(result.length > 0) res.render('profile', {data: result[0]})
    })
    // console.log(req.body.id);
    // res.render('profile', {fixid:req.body.id});
}

// 프로필 수정 2 (회원 정보 수정)
exports.profileEdit = (req,res) => {
    User.profileChange(req.body, (result) => {
        console.log('바꿀 정보 > ', req.body);
        console.log('화원 정보 수정 > ', result);
        res.send('수정 성공');
    })
}

// 프로필 삭제
exports.profileDelete = (req, res) => {
    console.log(req.body);

    User.delete_profile(req.body.id, (result) => {
        res.send({deletedId : req.body});
    })
}

 

id값 가져오기

= > "result[0]"으로 data에 값을 주어서 해당 DB를 전송한다.

 

정보 수정

= > 입력 값을 req.body로 넣어서 DB로 보내면 해당 값을 가지고 get_user에서 SQL문으로 값을 변경할것이다.

 

계정 삭제

= > 변경 불가능한( 입력 되어 있는 ) id값을  delete_profile에 값을 전달해서 SQL문으로 해당 id값이 존재하는 열을 지운다.

 

3. model > user.js

// id값 가져오기
exports.get_user = (data, cb) => {
    const sql = "SELECT * FROM user WHERE id = ?";
    const value = [data];
    conn.query(sql,value,(err,rows) => {
        if(err) throw err;
        console.log('profile rows >>>', rows);
        cb(rows)
    })
}

// 회원 수정
exports.profileChange = (data, cb) => {
    const sql = "UPDATE user SET pw = ?, name = ? WHERE id = ?";
    const value = [data.pw, data.name, data.id];
    conn.query(sql, value, (err, rows) => {
        if(err) throw err;
        console.log('New Profile >> ', rows);
        cb(rows);
    })
}

// 회원 탈퇴
exports.delete_profile = (id, cb) =>{
    const sql = "Delete FROM user where id = ? ";
    conn.query(sql, [id], (err,rows) => {
        if(err) throw err;

        console.log(rows);
        cb(rows)
    })
}

 

id값 찾기

=> id값을 찾는 SQL문을 통해 해당 배열을 찾아 값들을 rows로 반환

 

회원 수정

=> UPDATE SQL문을 사용하여 해당하는 id값의 pw,name을 수정한 값을 rows에 넣어서 반환

 

회원 삭제

=> DELETE SQL문을 사용하여 해당하는 id값의 열을 삭제 후 반환

 

4. static > user.js

// 회원정보 변경
function Edit(){
    const form = document.forms['PROFILE'];

    if(!form.checkValidity()){
        form.reportValidity();
        return;
    }
    axios({
        method: 'patch',
        url: '/user/profile/edit',
        data : {
            id: form.id.value,
            pw: form.pw.value,
            name: form.name.value
        }
    }).then((res) => {
        console.log(res);
    })
}

//아이디 삭제
function Delete(){
    const form = document.forms['PROFILE'];

    if(!form.checkValidity()){
        form.reportValidity();
        return;
    }

    axios({
        method: 'delete',
        url: '/user/profile/delete',
        data:{
            id: form.id.value
        }
    }).then((res) => {
        console.log(res);
        alert('회원 탈퇴 성공!');

        //회원 탈퇴 했으면 프로필 페이지 x => 메인 페이지로 이동
        document.location.href = '/user';
    }).catch((err) => {
        console.log(err);
    })
}

 

 

 

반응형