Node

[Node.js] MySQL Sequelize 사용해보기

YaluStar 2023. 3. 12. 00:25

안녕하세요.

이번에는 MySQL의 Sequelize를 사용해 보겠습니다.

 

1. Sequelize

  • 자바스크립트 구문을 알아서 SQL로 변환해 준다.
  • DB 작업을 쉽게 할 수 있도록 도와주는 ORM 라이브러리 중 하나이다.
    • ORM ( Object – Relation Mapping )

 

2. 설치

  • sequelize : 시퀄라이즈 패키지
  • sequelize-cli : 시퀄라이즈 명령어 실행
  • mysql2 : mysql과 시퀄라이즈를 연결하는 드라이버
npm install sequelize sequelize-cli mysql2

 

3. config 만들기

  • 데이터베이스 정보 저장 파일

/config/config.json

  • development : 개발 환경일 때
  • production : 서버에서 실행할 때
{
    "development": {
        "host": "localhost",
        "database": "test",
        "username": "test",
        "password": "qwer1234",
        "dialect": "mysql"
    },
    "production": {
        "host": "localhost",
        "database": "test",
        "username": "test",
        "password": "qwer1234",
        "dialect": "mysql"
    }
}

 

4. Sequelize 모델 정의

/model/Visitor.js

- Sequelize.define(param1, param2, param3)

- param1 : 모델(테이블) 이름 설정 // param2 : 컬럼 정의 // param3 : 모델 옵션 정의

 

4-1. 모델옵션

- 시퀄라이즈가 script문을 sql로 변환할 때 복수형으로 바뀌는 특징이 있는데, 이를 막기 위한 것이 freezeTableName 속성이다.

- 예시로 select * from visitor; 문이 되어야 하지만 select * from visitors; 식으로 바뀌는 특징이 있어  freezeTableName 속성을 사용하면 이러한 변화를 막을 수 있다.

- tableName의 경우 대소문자 상관없이 로컬에서는 이상 없지만, 실제 배포 서버에서는 안 되는 경우가 있으므로 대소문자를 꼭 맞춰주는 것이 좋다.

- default true => createAt modifyAt 시간을 저장하는 기능이 있는데, 컬럼이 설정되어 있어야 한다.

- collate, charset: "UTF8" // 데이터베이스가 기본적으로 utf8 설정으로 되어 있으면 안 해도 된다.

// Sequelize.define(param1, param2, param3);
// param1 : 모델(테이블) 이름 설정
// param2 : 컬럼 정의
// param3 : 모델 옵션 정의

// DB에서 작성 시 명령어
// create table visitor (
//     id int not null primary key auto_increment,
//     name varchar(10) not null
//     comment mediumtext
// )

// comment에서 allowNull: true가 기본 값이라서 굳이 설정안해도 된다.
const Visitor = (Sequelize, DataTypes) => {
    return Sequelize.define(
        'visitor',
        {
            id: {
                type: DataTypes.INTEGER,
                allowNull: false,
                primaryKey: true,
                autoIncrement: true
            },
            name: {
                type: DataTypes.STRING(10),
                allowNull: false
            },
            comment: {
                type: DataTypes.TEXT('medium')
            }
        },
        // 모델옵션
        // 시퀄라이즈가 script문을 sql로 변환할 때 복수형으로 바뀌는 특징이 있는데, 이를 막기위한 것이 freezeTableName 속성이다.
        // 예시로 select * from visitor; 문이되어야 하지만 select * from visitors; 식으로 바뀌는 특징이 있어서
        // freezeTableName 속성을 사용하면 이러한 변화를 막을 수 있다.
        {
            tableName: 'visitor',
            freezeTableName: true,
            timestamps: false
            // tableName의 경우 대소문자에 따라서 로컬에서는 되지만, 실제 서버에서는 안되는 경우가 있으므로 맞춰주는 것이 좋다.
            // default true  => createAt modifyAt 시간을 저장하는 기능이 있는데, 컬럼이 설정되어 있어야 한다고 한다.
            // collate, charset: "UTF8"
            // 데이터베이스가 기본적으로 utf8 설정으로 되어 있어서 굳이 안해도 된다고 한다.
        }
    )
}

module.exports = Visitor;

 

4-2 model DB 따로 분리하기

- 기존에는 model 상단에 데이터베이스를 정의해서 사용했지만, model 파일이 2개 이상이 되는 경우에 파일마다 상단에 데이터베이스를 정의해서 사용해야 하는 번거로움이 있고, 정보가 변경되면 전부 수정해야 하는 문제가 발생하기 때문에 따로 관리해서 사용한다.

/model/index.js

*대소문자 주의*

const Sequelize = require('sequelize');
const config = require('../config/config.json')['development'];
// config.json 파일을 접근해서 development만 불러오겠다.

const db = {};

// db connection 하는 방식 - db정보, db username, db password, config => 기본 양식
const sequelize = new Sequelize(
    config.database,
    config.username,
    config.password,
    config
);

db.sequelize = sequelize;
db.Sequelize = Sequelize;
// 아래와 같은 과정이라고 보면 되며, 대소문자 구분에 주의해야한다.
// db = {
//     "Sequelize": Sequelize,
//     "sequelize": sequelize
// }

// 이 상태면 함수를 불러오는 과정만 한거라고 보면 된다.
// db.Visitor = require("./Visitor");

// 뒤에 ()를 추가하면 함수를 불러와서 실행하는 과정까지 진행한다.
db.Visitor = require("./Visitor")(sequelize, Sequelize);

// 최종 예상 결과물
// db = {
//     "Sequelize": Sequelize,
//     "sequelize": sequelize,
//     "Visitor": "Visitor.js에서 return 받은 값"
// }

module.exports = db;

 

5. Sequelize 쿼리문

  • findAll() - select - 전체 검색
  • findOne() - select - 1개만 검색
  • create() - insert - 데이터 생성(삽입)
  • update() - update - 데이터 갱신
  • destroy() - delete - 데이터 삭제

/contorller/Cvisitor.js

- async + await와 일반 문법이 2개 존재해서 코드가 길게 보입니다.

// const { Visitor } = require('../model/index');
// db = {
//     "Sequelize": Sequelize,
//     "sequelize": sequelize,
//     "Visitor": "Visitor.js에서 return 받은 값"
// }

// model 폴더의 index.js에서 Visitor을 가져온다.
const { Visitor } = require('../model');
// 파일 이름이 index이면, model 까지 작성해도 된다.

exports.visitor = async (req, res) => {
    // async와 await 문법을 이용하는 방법
    // 만약 findAll에서 오래 걸리지 않았더라면 이러한 방식으로 사용할 수 있지만, 그렇지가 않다.
    // 이 상태로 실행하면 result에 결과가 담겨지기 전에 render 실행될 수 있는 상황이 있다.
    // let result = Visitor.findAll();
    // res.render('visitor', {data: result});

    // 그래서 async, await를 이용하는 방식으로 사용한다.
    // await는 뒤에 promise 객체가 종료될 때까지 기다린다.
    // promise 객체의 then 보다 가독성이 더 좋다.
    // 그러나 async await를 사용하는 경우에는 then을 사용하면 안된다.
    // result에 결과가 저장되면 다음 함수를 실행한다.
    let result = await Visitor.findAll();
    res.render('visitor', {data: result});


    // // findAll() 함수는 시퀄라이즈 내장 함수이며, promise 이다.
    // Visitor.findAll()
    // .then((result) => {
    //     console.log(result);
    //     // result에서 첫번째 값의 id를 가져오고 싶을 때 다음 명령어로 확인을 하는데
    //     // 기본값으로 dataValues를 가져오기 때문에 생략하고 사용해도 된다.
    //     // console.log(result[0].dataValues.id);
    //     // console.log(result[0].id);
    //     res.render('visitor', {data: result});
    // });
}

// Register async + await 문법
exports.register = async (req, res) => {
    let data = {
        name: req.body.name,
        comment: req.body.comment
    }
    let result = await Visitor.create(data);
    res.send(String(result.id));
}

// Register 기존에 사용하던 문법
// exports.register = (req, res) => {
//     // insert 문을 할 때는 create() 함수를 사용하면 된다.
//     let data = {
//         name: req.body.name,
//         comment: req.body.comment
//     }
//     Visitor.create(data)
//     .then((result) => {
//         console.log(result);
//         res.send(String(result.id));
//     });
// }



// Delete async + await 문법
exports.delete = async (req, res) => {
    let result = await Visitor.destroy({
        where: {
            id: req.body.id
        }
    });
    console.log(result);
    res.send(String(result));
}

// Delete 기존에 사용하던 문법
// exports.delete = (req, res) => {
//     // 삭제할 때는 destroy() 함수를 사용한다.
//     Visitor.destroy({
//         where: {
//             id: req.body.id
//         }
//     })
//     .then((result) => {
//         console.log(result);
//         res.send(result);
//     });
// }



// async + await 문법
exports.get_visitor_by_id = async (req, res) => {
    let result = await Visitor.findOne({
        where: {
            id: req.query.id
        }
    });
    res.send(result);
}

// 기존 promise + then 문법
// exports.get_visitor_by_id = (req, res) => {
//     // req.query.id 에 해당하는 데이터를 조회
//     // 서버 응답 > 조회한 데이터
//     // findOne() 함수는 1개만 찾는 함수
//     // findAll() 함수에서도 동일하게 사용가능, 차이는 findOne() 함수에는 limit 1 설정이 있다고 보면 된다.
//     Visitor.findOne({
//         where: {
//             id: req.query.id
//         }
//     })
//     .then((result) => {
//         res.send(result);
//     });
// }



// Update async + await 문법
exports.update_visitor = async (req, res) => {
    let data = {
        name: req.body.name,
        comment: req.body.comment
    }
    let result = await Visitor.update(data, {
        where: {
            id: req.body.id
        }
    });
    res.send(result);
}

// Update 기존에 사용하던 문법
// exports.update_visitor = (req, res) => {
//     // update() 함수는 정보를 변경할 때 사용한다.
//     let data = {
//         name: req.body.name,
//         comment: req.body.comment
//     }
//     Visitor.update(data, {
//         where: {
//             id: req.body.id
//         }
//     })
//     .then((result) => {
//         console.log(result);
//         res.send(result);
//     });
// }

 

/routes/index.js

const express = require("express");
const controller = require("../controller/Cvisitor");
const router = express.Router();

router.get("/", controller.visitor);

router.post("/register", controller.register);

router.delete("/delete", controller.delete);

router.get("/get_visitor", controller.get_visitor_by_id);
router.patch("/update", controller.update_visitor);

module.exports = router;

 

index.js

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

app.set("view engine", "ejs");

app.use("/static", express.static(__dirname+"/static"));
app.use(express.urlencoded({ extended: false }));
app.use(express.json());

const router = require("./routes");
app.use('/visitor', router);


app.get('*', (req, res)=>{
    res.send("주소가 존재하지 않습니다. 다시 한 번 확인해주세요.");
});

app.listen(port, ()=>{
    console.log("server open: ", port);
});

 

/views/visitor.ejs

<!DOCTYPE html>
<html lang="en">
<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">
    <title>방명록</title>
    <!-- Axios 사용을 위한 cdn -->
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script src="https://code.jquery.com/jquery-3.6.1.min.js" integrity="sha256-o88AwQnZB+VDvE9tvIXrMQaPlFFSUTR+nldQm1LuPXQ=" crossorigin="anonymous"></script>

    <style>
        table, th, td {
            border: 1px solid black;
        }

        .d-none {
            display: none;
        } 
    </style>

    <script>
        let update_tr;

        function update_visitor(){
            var form = document.getElementById("form_register");
            axios({
                method: 'patch',
                url: "/visitor/update",
                data: {
                    id: form.id.value,
                    name: form.name.value,
                    comment: form.comment.value
                }
            })
            .then((response)=>{
                update_tr.children[1].innerText = form.name.value;
                update_tr.children[2].innerText = form.comment.value;

                form.id.value = "";
                form.name.value = "";
                form.comment.value = "";
                $(".update-btn").addClass("d-none");
                $(".register-btn").removeClass("d-none");
            });
        }

        function update_load(id, element){
            axios({
                method: 'get',
                url : "/visitor/get_visitor",
                params: { id: id }
            })
            .then((response)=>{
                var form = document.getElementById("form_register");
                form.id.value = response.data.id;
                form.name.value = response.data.name;
                form.comment.value = response.data.comment;
                $(".update-btn").removeClass("d-none");
                $(".register-btn").addClass("d-none");

                update_tr = element.parentElement.parentElement;
            });
        }

        function delete_visitor(delete_id, element){
            axios({
                method: 'delete',
                url: '/visitor/delete', 
                data: {id : delete_id}
            })
            .then((response) => {
                if(response.data) element.parentElement.parentElement.remove();
                else alert("알 수 없는 오류 발생");
            });
        }

        function register(){
            let form = document.getElementById("form_register");
            let data = {
                name: form.name.value,
                comment: form.comment.value
            }
            axios({
                method: 'post',
                url: '/visitor/register',
                data: data
            })
            .then((response)=>{
                $("table").append(`
                    <tr>
                        <td>${response.data}</td>
                        <td>${data.name}</td>
                        <td>${data.comment}</td>
                        <td><button type="button" onclick="update_load(${response.data}, this)">수정</button></td>
                        <td><button type="button" onclick="delete_visitor(${response.data}, this)">삭제</button></td>
                    </tr>
                `)
            });
        }

        function cancel_visitor() {
            let form = document.getElementById("form_register");
            form.name.value = '';
            form.comment.value = '';
            $(".update-btn").addClass("d-none");
            $(".register-btn").removeClass("d-none");
        }
    </script>

</head>
<body>
    <form id="form_register">
        <fieldset style="display: inline-block;">
            <legend>방명록 등록</legend>
            <input type="hidden" name="id">
            이름 : <input type="text" name="name" placeholder="사용자 이름"><br>
            방명록 : <input type="text" name="comment" placeholder="방명록"><br>
            <div class="update-btn d-none">
                <button type="button" onclick="update_visitor();">수정</button>
                <button type="button" onclick="cancel_visitor()">취소</button>
            </div>
            <div class="register-btn">
                <button type="button" onclick="register();">등록</button>
            </div>
        </fieldset>
	</form>

    <table cellspacing="0" cellpadding="10" style="margin-top: 30px;">
        <tr>
            <th>ID</th>
            <th>작성자</th>
            <th>방명록</th>
            <th>수정</th>
            <th>삭제</th>
        </tr>
        <% for(let i=0; i<data.length; i++ ) { %>
            <tr>
                <td><%=data[i].id%></td>
                <td><%=data[i].name%></td>
                <td><%=data[i].comment%></td>
                <td><button type="button" onclick="update_load('<%=data[i].id%>', this);">수정</button></td>
                <td><button type="button" onclick="delete_visitor('<%=data[i].id%>', this);">삭제</button></td>
            </tr>
        <% } %>
    </table>
</body>
</html>

 

결과는 이전 글과 동일하지만, 여기서는 수정 및 삭제 기능도 같이 작동합니다.

 

 

이상으로 MySQL의 Sequelize 사용법에 대하여 알아보았습니다.

감사합니다.

 

반응형