[Node.js & MySQL] sequelize 라이브러리

Yunhye Park·2023년 11월 8일
0
post-thumbnail

mysql을 모듈로 직접 설치해서 데이터베이스에 접근할 땐 콜백 함수를 사용해야 하고, sql문 작성이 필요하다.

이러한 번거로움을 다소 경감해주는 라이브러리 sequelize를 활용해보았다. 기존에 있던 폴더에서 mysql 작성한 모델과 컨트롤러 파일들만 변경해주었던 터라 그외 파일들은 따로 작성하지 않겠다.

sequelize는?

node.js 기반의 ORM(Object Relational Mapping)이다. 객체와 데이터베이스를 연결해 준다는 의미로, sequelize 라이브러리로 객체를 생성해 데이터베이스와 매핑한다고 볼 수 있다.

JS로 작성한 내용을 sql문으로 변환해주는 것이라 sequelize 사용 시 프로미스를 반환하기 때문에 then 메서드나 async/await로 처리할 수 있다.

sequelize를 터미널에서 명령어로 처리할 수 있게 sequelize-cli 또한 같이 설치해준다.

npm i sequelize sequelize-cli mysql2

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

DB 설정 : .json

json 파일에 mysql에 있는 데이터베이스 설정을 옮겨준다.

{
    "development": {
        "host": "127.0.0.1",
        "database": "db 이름",
        "username": "사용자 이름",
        "password": "mysql 비밀번호",
        "dialect": "mysql"
    },
    "production": {
        "host": "",
        "database": "",
        "username": "",
        "password": "",
        "dialect": ""
    }
}

개발과 배포 두 버전으로 나눠 작성할 수 있는데 지금은 개발만 해볼 것이라 production은 따로 작성하지 않았다.

npx sequelize init을 입력하면 config, seeders, models, migrations 등 폴더가 자동 생성되어서 이를 이용해도 좋다.

MVC 패턴으로 작성할 생각이라서 위 명령어 대신 model, controller, routes로 폴더를 직접 나눴다.

DB 테이블 가져오려면 : model

sequelize를 사용해서 mysql에 접근하려면 sequelize 객체를 생성하여 모델을 정의해야 한다.

model/index.js

const Sequelize = require("sequelize");
const config = require("../config/config.json")["development"];

const db = {};
const sequelize = new Sequelize(
  config.database,
  config.username,
  config.password,
  config
);

db.sequelize = sequelize;
db.Sequelize = Sequelize;

프로젝트에서 DB는 하나이고, table은 하나 이상이 생긴다. 그럼 공통 부분인 DB 정의를 한 곳에서 처리하여 테이블이 생길 때마다 추가적으로 객체를 생성하는 게 코드 효율성과 기능에 따른 파일 분리에 좋을 것이다.

index 코드 설명

1) sequelize 모듈로 데이터타입을 정의할 수 있다. ex.sequelize.INTERGER
테이블마다 어떤 데이터타입을 사용할지 컬럼별로 정의할 테니까 이 모듈을 인자로 넘겨주기 위해서 불러왔다.

2) 앞서 연결한 mysql DB에서 development로 정의한 부분을 불러와 config 변수에 담았다.

3) 빈 객체를 생성해 생성자 함수로 sequelize 모듈을 호출해 DB의 각 요소(데이터베이스명, 사용자명, 비밀번호 등) 값을 가져와 새 변수에 담았다.

4) 이제 db 객체에 두 개의 key가 존재한다. sequelize 모듈(Sequelize)과 mysql DB를 정의한 객체.

테이블마다 모델을 정의하고, 이를 db 객체에 담은 채로 호출해 주면 된다. 우선 Visitor이라는 모델부터 정의해주자.

model/Visitor.js

function Visitor (Sequelize, DataTypes) {
    return Sequelize.define(
        "visitor", 
        {
            id: {
                type: DataTypes.INTEGER,
                allowNull: false,
                primaryKey: true,
                autoIncrement: true
            },
            username: {
                type: DataTypes.STRING(10)
            },
            comment: {
                type: DataTypes.TEXT("medium")
            },
        },
        {
            tableName: "visitor",
            freezeTableName: true,
            timestamps: false

        }
    )
}

module.exports = Visitor

Visitor 코드 설명

1) Visitor라는 함수를 만들어 모델을 정의한다. sequelize로 만든 db 객체를 정의(define) 해주는 메서드를 사용한다. 이 결과를 return 해줌으로써 index.js에서 해당 함수 호출 시 이 테이블 객체를 db 객체에 key값으로 담는다.

2) .define()에는 세 인자가 들어간다. 테이블명, 컬럼 정의, 옵션

3) 테이블명은 visitor

4) id, username, comment라는 3가지 컬럼을 객체의 key로 넣고 각각의 타입 등을 또 객체로 작성한다(속성의 이름이 필요하니까 어찌보면 객체를 사용하는 게 자연스럽다). 이때 sequelize 모듈에 있던 DataTypes를 사용해 정의한다.

5) 옵션으로 걸 테이블 이름을 적고, 테이블 이름을 freeze했다. 가끔 sequelize에서 sql문을 처리할 때 테이블명을 강제로 복수형으로 변경하는 일이 발생해 이를 방지하는 용도다. timestamps는 레코드를 생성할 때 자동적으로 createAt updateAt 컬럼을 만들고 생성이나 업데이트를 할 때마다 해당 필드를 채운다. 그래서 데이터 날짜 추적이 필요할 때 사용하면 용이할 듯하다.

이렇게 설정하고 모듈을 exports까지 해주었으니 인자를 넣어 호출해주기만 하면 된다.

model/index.js

db.Visitor = require("./Visitor")(sequelize, Sequelize);

데이터들을 가져왔으니 어떤 조건으로 데이터를 가져올지 컨트롤러를 작성해주면 된다.

controller/CVisitor.js

const Visitor = require("../model") // index.js 가져옴

exports.home = (req, res) => {
  res.render("index");
};

// GET /visitors => 기존 방명록 전체 조회 후 visitor.ejs render
exports.visitor = (req, res) => {

    Visitor.findAll().then((result)=>{
        res.render("visitor", {data: result});

    });
};

// POST /visitor => 방명록 insert
exports.postVisitor = async (req, res) => {
    const data = {
        username: req.body.username,
        comment: req.body.comment
    }
    const createVisitor = 
          await Visitor.create(data).catch((err) => {});
    	  res.send(createVisitor);
}

// DELETE /visitor/:id => 방명록 삭제
exports.deleteVisitor = (req, res) => {
    Visitor.destroy({
        // 조건 걸 땐 객체 안에 where key에 value도 객체로
        where: {
            id: req.params.id
        }
    }).then((result) => {
        console.log("destroy result", result);
        res.send({ result: true });
    });
};

// GET /visitor/:id => 방명록 하나 조회
exports.getVisitorById = (req, res) => {
    // select * from visitor where id = ?? limit 1
    Visitor.findOne({
        where: {
            id: req.params.id
        }
    }).then((result) => {
        console.log("findOne result: ", result);
        res.send(result);
    });
};

// PATCH /visitor/:id => 방명록 수정
exports.patchVisitor = (req, res) => {
    const data = {
        username: req.body.username,
        comment: req.body.comment
    };
    // update visitor set username= '??', comment='??' where id = ??;
    Visitor.update(data, {
        where: {
            id: req.body.id
        }
    }).then((result) => {
        console.log("update result: ", result);
        res.send({ result: true });
    });
};

코드 설명

1) promise를 사용할 수 있어서 async/await 혹은 then 메서드로 작성하면 된다. 이전에는 모델에 콜백함수를 정의하고 컨트롤러에서 콜백함수를 넘겨줬는데, 이젠 컨트롤러에서 모든 작업을 처리할 수 있게 된 셈이다. 조금 더 MVC 패턴에 맞게 파일 정리가 가능한 것 같다.

2) insert into문은 create()와 생성할 data를,
select문은 findAll() 혹은 findOne과 where,
alter문은 update() 할 data와 where,
delete문은 destroy()와 where을 조건으로 사용하면 된다.

3) 모델에서 가져온 함수에 접근해 메서드를 실행하고, 조건이 필요하다면 where 절로 조건을 건다. update는 기존 값에서 새 값으로 변경하기 때문에 기존값을 나타낼 데이터를 인자에 넣는다.

클라이언트에서 받은 요청에 맞는 데이터를 찾고 난 후(then) 정보를 담아 클라이언트에 전달한다. 이때 데이터가 존재한다는 의미를 담아 객체 안에 result: true를 반환했다.

4) 클라이언트 측에서는 result가 true일 때와 그렇지 않은 경우를 나눠서 다른 결과를 보여주면 된다.


덧붙이는 말

  • mysql문에서 sequelize로 옮기려니까 처음엔 더 헷갈렸다. 그래서 데이터가 어디서 어디로 넘어가고 연결되는지 전체 구조를 파악하는 데에 집중했다. 이해하고나니 이 방법이 더 쉽다고 느낀다.

  • 기본기가 중요하다는 건 어느 분야에서든 통하는 말 같다. 기능을 어떻게 표현하는지가 다를 뿐이지 CRUD라는 본질은 같아서 본질은 거의 같은 듯하다. 그래서 무얼 구현하느냐보다는 '무슨 도구를 이용해서?' '왜 그렇게 했는지?'라는 물음에 답을 찾아가는 과정이 더 중요하다고 본다. 그래야 같은 기능도 다르게 구현해 볼 생각이 들 테니까.

profile
일단 해보는 편

0개의 댓글