코딩/expressJs

Sequelize를 적용해보자(진행중)

김 숨 2022. 4. 4. 23:53

✔ 사전적 의미 : 속편화 하다. 후속작을 만들다.

 

✔ 공식 홈페이지의 설명(https://sequelize.org/)

Sequelize는 Postgres, MySQL, MariaDB, SQLite 및 SQL Server 등을 위한 최신 TypeScript 및 Node.js ORM입니다. 
견고한 트랜잭션 지원, 관계, 열망 및 지연 로딩, 읽기 복제 등을 제공합니다.

ORM(Object Relational Mapping)

- 객체 지향 프로그래밍 언어 를 사용하여 호환되지 않는 유형 시스템 간에 데이터를 변환 하는 프로그래밍 기술
- 여기서는 객체와 RDBMS의 데이터를 자동으로 연결해주는 것 즉, ORM을 통해 객체 간의 관계를 바탕으로 SQL을 자동으로 생성하여 객체 지향적인 코드를 생산할 수 있다.

 

✔ Sequlize 왜 적용하려고 하는데?

- 여러가지 API를 만들면서 코드가 길어지고 쿼리문도 많이 늘어났다. user에 대한 model 폴더를 만들어 쿼리문만 따로 분류 했는데 분류를 해도 뭐가 뭔지 모르겠다. 이에 좀 더 직관적인 코드를 구현할 방법이 필요했다. 

지저분한 변수명들

 

 - 장점 : 실제로 Sequelize를 이용해보니 쿼리를 메서드와 값으로 표현할 수 있어 텍스트로 쿼리를 직접 작성하는 것 보다 코드를 작성하거나 읽기 더 쉬워졌다.

 - 단점 : 쿼리가 복잡해지는 부분(서브쿼리) 적용하기가 어려웠다. 프로젝트가 복잡해질 경우 오히려 사용하는 난이도가 올라걸거 같다.

 

 어떻게 적용 했는데?

1. 설치

npm i --save sequelize

2. 설정파일 적용하기(기존에 mysql connection 하기 위해 썼던 설정을 그대로 썼다)

//setting_config.js 

require('dotenv').config();

const config = {
    development: { //로컬 db를 바라본다.
        host: process.env.DB_HOST,
        user: process.env.DB_USER,
        password: process.env.DB_PW,
        port: process.env.DB_PORT,
        database: process.env.DB_NAME,
        multipleStatements: true,
        dateStrings: 'date' //mysql의 data 타입을 깔끔하게 바꿔줌
    },
    /*
    ...
    test 환경의 DB 설정(생략)
    ...
    */
    };
// 생성한 객체를 모듈화 하여 외부 파일에서 불러와 사용 할 수 있도록
module.exports = config;

 

3. 모델 생성하기

//User모델 user.js
module.exports = (sequelize, Sequelize) => {
    return sequelize.define("users", {
        idx: {
            type: Sequelize.INTEGER,
            primaryKey: true,
            autoIncrement: true,
            allowNull: false
        },
        id: {
            type: Sequelize.STRING(20),
            allowNull: false,  // == not null 
            // primaryKey: true,
        },
        password: {
            type: Sequelize.STRING(255),
            allowNull: false,
        },
        salt: {
            type: Sequelize.STRING(255),
            allowNull: false,
        },
        name: {
            type: Sequelize.STRING(10),
            allowNull: false
        },
        email: {    //char(50)
            type: Sequelize.STRING(50),
            allowNull: false
        },
        user_photo: { //varchar(200)
            type: Sequelize.STRING(200),
            allowNull: true
        },

    }, {
        charset: "utf8", // 한국어 설정  utf8mb4 쓰면 이모티콘도 가능 
        collate: "utf8_general_ci", // 한국어 설정
        tableName: "user_table", // 테이블 이름 정의
        timestamps: true, // createAt, updateAt 활성화
        paranoid: true, // deleteAt 옵션
    });

};
//Post 모델 post.js

module.exports = (sequelize, Sequelize) => {

    return sequelize.define("posts", {
        idx: {
            type: Sequelize.INTEGER,
            primaryKey: true,
            unique: true,
            autoIncrement: true,
            allowNull: false
        },
        user_idx: {

            type: Sequelize.INTEGER, // The data type defined here and 
            allowNull: false,
            references: {
                model: 'users',
                key: 'idx'
            },

        },
        title: {
            type: Sequelize.STRING(255),
            allowNull: false,
        },
        content: {
            type: Sequelize.TEXT,
            allowNull: false
        },
        view_post_count: {
            type: Sequelize.INTEGER,
            allowNull: false,
            defaultValue: 0
        },

    }, {
        charset: "utf8mb4", // 한국어 설정  utf8mb4 쓰면 이모티콘도 가능 
        collate: "utf8mb4_general_ci", // 한국어 설정
        tableName: "post_table", // 테이블 이름 정의
        timestamps: true, // createAt, updateAt 활성화
        paranoid: true, // deleteAt 옵션
    });


};

 

4. /model/index.js에 모델 선언 관계 적용하기

const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'development';
const config = require('../config/setting_config.js')[env];
//env 설정되어있는 폴더

const db = {};

let sequelize;
//DB연결
if (config.use_env_variable) {
  sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
  sequelize = new Sequelize(config.database, config.user, config.password,
    {
      host: config.host,
      port: config.port,
      dialect: config.dialect,
      operatorsAliases: 0,
      timezone: "+09:00", // DB에 저장할 때 시간 설정
      dialectOptions: {
        timezone: "+09:00", // DB에서 가져올 때 시간 설정
      }
    }
  );
}

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

db.user = require('./user.js')(sequelize, Sequelize);
db.post = require('./post.js')(sequelize, Sequelize);

//테이블의 관계 지정
//User와 Post의 관계 하나의 유저가 여러 post(게시글)을 가진다. 
db.user.hasMany(db.post, {
  foreignKey: 'user_idx',
  sourceKey: 'idx'
});
db.post.belongsTo(db.user, {
  foreignKey: 'user_idx',
  targetKey: 'idx'
});

 

5. app.js에서sequelize sync 코드 추가

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

app.listen(port1, function () {
   console.log('app listening on port : ' + port1);
});

const { sequelize } = require('./models')


// Sequelize가 초기화 될 때 DB에 필요한 테이블을 생성하는 함수
sequelize.sync()
  .catch((error) => {
    console.log(error);
  });
  
  /*
  ...
  이하 필요한 코드
  ...
  */

 

6. 생성확인

6. service에서 CRUD해보기 (코드 일부만 갖고 왔다.)

//user_service.js 일부만
signup: async (signup_info) => { //가입
     try {
            //회원가입 전송
            let result = await user.create(signup_info)
                .catch(err => {
                    console.log(err);
                    return false;
                });
            if (result) {
                return result.idx;
            }//오류가 있는경우
        } catch (error) {
            console.log(error);
        }
    },
    
    login: async (uesr_id, password) => { //로그인
        //user_id로 정보찾기
        try {
            let result = await user.findOne({
                attributes: ['idx', 'id', 'name', 'password', 'salt', 'user_photo', 'deletedAt'],
                where: {
                    id: uesr_id
                }
            })
                .catch(error => {
                    console.log(error);
                });
            //결과가 있으면

            if (!result) { return false; } //없는 id
            if (result) {
                const user = result.dataValues;
                if (user.deletedAt) { return ({ message: 'resign_user' }); } //탈퇴한 id : 탈퇴후 3개월 동안 같은 id 생성 불가
                //비밀번호 비교
                const db_password = user.password;
                let hash_password = crypto.createHash("sha512").update(password + user.salt).digest("hex");
                if (db_password === hash_password) {
                    return user;
                } //세션에 유저정보를 저장하기 위함
                if (db_password !== hash_password) { return ({ message: 'login_fail' }); } //비밀번호 틀림
            }

        } catch (error) {
            console.log(error);
        }

    },
    delete_user: async (user_idx) => { //탈퇴
        try {
            let result = await user.destroy({
            }, {
                where: {
                    idx: user_idx
                }
            }).catch(error => {
                console.log(error);
            });
            return result;
        } catch (error) {
            console.log(error);
        }
    },

 

 

 

 

 

+ 그동한 발생했던 에러들

더보기

1. Dialect needs to be explicitly supplied as of v4.0.0

해결 1) 환경변수 재설정하기 set NODE_ENV=production 아니면 development

 

2. Cannot add foreign key constraint 혹은 ER_FK_NO_INDEX_PARENT

해결 1) 다른 테이블과의 fk가 어떻게 설정되어있는지 점검해보기

(필자는 mysql과 연결해서 쿼리문으로 데이터를 주고 받았었다. 시퀄라이즈 적용을 하기 위해 기존 post/user테이블을 삭제했는데 이둘과 FK로 연결된 다른 테이블(comment) 때문에 새로운 테이블 생성이 되지 않았었음)