본문 바로가기

Node.js

Node.js(19) - N:M 관계(sequelize 활용), Board의 write/view 구현

728x90
반응형

1) Node.js

   1-1) N:M 관계 구현 - sequelize 활용

   1-2) Board의 write, view 구현

 

 

 

 

 

 

1) Node.js

 

 

1-1) N:M 관계 구현 - sequelize 활용

Sequelize

  • User
  • Board
  • Comment
  • Liked: N:M 관계 --> User(부모)-Liked(자식) => 1:N 관계  /  Board(부모)-Liked(자식) => 1:N 관계
  • Hash
  • Hashtag: N:M 관계 --> Board(부모)-Hashtag(자식) => 1:N 관계  /  Hash(부모)-Hashtag(자식) => 1:N 관계
CREATE TABLE person(
  first_name VARCHAR(30) NOT NULL,
  last_name VARCHAR(30) NOT NULL,
  PRIMARY KEY(first_name, last_name) --> 2개 합쳐서 하나의 기본키로 지정 가능함!
);


"PRIMARY KEY"는 기본키인데, 2개 합쳐서 하나의 기본키가 된다!

 

User

userid (Primary Key)
userpw
username
provider
-- provider enum(데이터 타입) => 'local', 'kakao'(사이트 회원이 local 로그인했는지 아니면 카카오를 통해 외부 로그인을 했는지 판별하는 field가 "provider"이다!)
snsId null

 


Board

id (Primary Key)
subject
content
userid
createdAt
hit

 


Comment

id (Primary Key)
boardid (Foreign Key)
userid (Foreign Key)

 


Liked

userid (Foreign Key)
boardid (Foreign Key)

 


hashtag

boardid (Foreign Key)
tag (Foreign Key)

 


hash

tag (Primary Key)

 

 

기본세팅

$ npm init -y
$ npm install express mysql2 sequelize



부모 테이블에는 "hasMany", 자식 테이블에는 "belongsTo"를 사용하여 "Foreign Key(FK)"를 설정한다!(1:N 관계의 경우)

 

Foreign Key(FK)는 기본적으로 field 명을 "Table 명 + PK"로 지정한다!!
ex) User + Userid = UserUserid
Board + id = Boardid

 

"sequelize"의 경우, "belongsToMany"를 사용하여 N:M 관계의 테이블들을 중간에서 "1:N 관계"로 이어줄 테이블을

하나 새로 생성하여 "N:M 관계"를 구현함!

 

 

 

 

config.js

const config = {
  db: {
    development: {
      username: "root",
      password: "본인 MySQL 비밀번호",
      database: "practice_board",
      host: "127.0.0.1",
      port: "3306",
      dialect: "mysql",
      define: {
        freezeTableName: true,
        timestamps: false,
      },
    },
  },
};

module.exports = config;

 

 

models/index.js

// 1. Connection
const fs = require("fs");
const path = require("path");

const Sequelize = require("sequelize");
const { db } = require("../config");
const env = process.env.NODE_ENV || "development";
const config = db[env];

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

// user.model
// board.model
// comment.model
// hash.model

// fs(파일 시스템: Node.js의 내장 모듈)
// __dirname: 파일을 실행한 "절대경로"를 불러와주는 Node.js의 내장 객체
// console.log(__dirname); // /Users/hwangsangbeom/Documents/workspace/Nodejs/(practice) 230116/models
// const dir = fs.readdirSync(__dirname); // fs.readdirSync(): 인자값에 넣은 경로의 파일들 혹은 폴더들을 배열로 반환해줌!
// console.log(dir); // ['board.model.js', 'comment.model.js', 'hash.model.js', 'index.js', 'user.model.js']

// const dir = fs.readdirSync(__dirname).filter((v) => v.indexOf("model") !== -1); // index.js 파일이 아니면 true, 맞으면 false를 반환하여 filter 메서드를 실행시킨다는 의미이다!
// console.log(dir); // ['board.model.js', 'comment.model.js', 'hash.model.js', 'user.model.js']

fs.readdirSync(__dirname)
  .filter((v) => v.indexOf("model") !== -1)
  .forEach((filename) => {
    // ex) require("./user.model.js")(sequelize, Sequelize);
    require(path.join(__dirname, filename))(sequelize, Sequelize);
  });

// console.log(sequelize.models); // {User, Board, ...}

const { models } = sequelize;
for (const key in models) {
  console.log(models[key]); // class
  // if (typeof models[key].associate === "undefined") continue;
  if (typeof models[key].associate !== "function") continue;
  models[key].associate(models);
}

(async () => {
  await sequelize.sync({ force: true });
})();

module.exports = {
  sequelize,
  Sequelize,
};

 

 

models/user.model.js

module.exports = (sequelize, Sequelize) => {
  // class 선언
  class User extends Sequelize.Model {
    static initialize() {
      return this.init(
        {
          userid: {
            type: Sequelize.STRING(60),
            primaryKey: true,
          },
          userpw: {
            type: Sequelize.STRING(64),
            allowNull: false,
          },
          username: {
            type: Sequelize.STRING(30),
            allowNull: false,
          },
          provider: {
            type: Sequelize.ENUM("local", "kakao"), // "ENUM"은 각 인자값에 어떤 타입을 쓸 것인지 정하는데 해당 코드는 'local'과 'kakao'라는 String 타입만 받겠다는 의미이다!
            allowNull: false,
            defaultValue: "local",
          },
          snsId: {
            type: Sequelize.STRING(30),
            allowNull: true,
          },
        },
        {
          sequelize, // 해당 코드는 "sequelize: sequelize"를 의미함!
        }
      );
    }

    static associate(models) {
      this.hasMany(models.Board, {
        foreignKey: "userid",
      });

      this.hasMany(models.Comment, {
        foreignKey: "userid",
      });

      this.belongsToMany(models.Board, {
        through: "Liked", // M:N 관계 테이블들의 중간에 낄 (새로 생성할) 테이블명을 지정해주는 코드
        foreignKey: "userid",
      });
    }
  }

  // class 사용
  User.initialize();
};

 

 

models/board.model.js

module.exports = (sequelize, Sequelize) => {
  class Board extends Sequelize.Model {
    static initialize() {
      this.init(
        {
          subject: {
            type: Sequelize.STRING(100),
            allowNull: false,
          },
          content: {
            type: Sequelize.TEXT,
            allowNull: true,
          },
          createdAt: {
            type: Sequelize.DATE,
            defaultValue: sequelize.fn("now"),
          },
          hit: {
            type: Sequelize.INTEGER,
            defaultValue: 0,
          },
        },
        {
          sequelize,
        }
      );
    }

    static associate(models) {
      this.belongsTo(models.User, {
        foreignKey: "userid",
      });

      this.hasMany(models.Comment, {
        foreignKey: "boardid",
      });

      this.belongsToMany(models.User, {
        through: "Liked",
        foreignKey: "boardid",
      });

      this.belongsToMany(models.Hash, {
        through: "Hashtag",
        foreignKey: "boardid",
      });
    }
  }

  Board.initialize();
};

 

 

models/comment.model.js

module.exports = (sequelize, Sequelize) => {
  class Comment extends Sequelize.Model {
    static initialize() {
      this.init(
        {
          content: {
            type: Sequelize.TEXT,
            allowNull: false,
          },
          createAt: {
            type: Sequelize.DATE,
            allowNull: false,
            defaultValue: sequelize.fn("now"),
          },
        },
        {
          sequelize,
        }
      );
    }

    static associate(models) {
      this.belongsTo(models.Board, {
        foreignKey: "boardid",
      });

      this.belongsTo(models.User, {
        foreignKey: "userid",
      });
    }
  }

  Comment.initialize();
};

 

 

models/hash.model.js

module.exports = (sequelize, Sequelize) => {
  class Hash extends Sequelize.Model {
    static initialize() {
      this.init(
        {
          tag: {
            type: Sequelize.STRING(30),
            allowNull: false,
            primaryKey: true,
          },
        },
        {
          sequelize,
        }
      );
    }

    static associate(models) {
      this.belongsToMany(models.Board, {
        through: "Hashtag",
        foreignKey: "tag",
      });
    }
  }

  Hash.initialize();
};

 

 

 

 

 

1-2) Board의 write, view 구현

주요 내용 요약(새로운 내용)

// 다 성공했는지를 알기 위해 사용하는 것이 "Promise.all"이다!!
// --> Promise.all([Promise, Promise, Promise])

console.log(board.__proto__); // __proto__: board에 있는 상속받은 메서드들을 볼 수 있음!

 

 

※ index.js 파일을 제외한 나머지 코드는 위와 동일함!

 

models/index.js

// 1. Connection
const fs = require("fs");
const path = require("path");

const Sequelize = require("sequelize");
const { db } = require("../config");
const env = process.env.NODE_ENV || "development";
const config = db[env];

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

// user.model
// board.model
// comment.model
// hash.model

// fs(파일 시스템: Node.js의 내장 모듈)
// __dirname: 파일을 실행한 "절대경로"를 불러와주는 Node.js의 내장 객체
// console.log(__dirname); // /Users/hwangsangbeom/Documents/workspace/Nodejs/(practice) 230116/models
// const dir = fs.readdirSync(__dirname); // fs.readdirSync(): 인자값에 넣은 경로의 파일들 혹은 폴더들을 배열로 반환해줌!
// console.log(dir); // ['board.model.js', 'comment.model.js', 'hash.model.js', 'index.js', 'user.model.js']

// const dir = fs.readdirSync(__dirname).filter((v) => v.indexOf("model") !== -1); // index.js 파일이 아니면 true, 맞으면 false를 반환하여 filter 메서드를 실행시킨다는 의미이다!
// console.log(dir); // ['board.model.js', 'comment.model.js', 'hash.model.js', 'user.model.js']

fs.readdirSync(__dirname)
  .filter((v) => v.indexOf("model") !== -1)
  .forEach((filename) => {
    // ex) require("./user.model.js")(sequelize, Sequelize);
    require(path.join(__dirname, filename))(sequelize, Sequelize);
  });

// console.log(sequelize.models); // {User, Board, ...}

const { models } = sequelize;
for (const key in models) {
  console.log(models[key]); // class
  // if (typeof models[key].associate === "undefined") continue;
  if (typeof models[key].associate !== "function") continue;
  models[key].associate(models);
}

(async () => {
  await sequelize.sync({ force: true });

  const { User, Board, Comment, Hash } = models;

  await User.create({
    userid: "hsb7722",
    userpw: "1234",
    username: "sangbeom",
  });
  await User.create({ userid: "admin", userpw: "1234", username: "관리자" });

  await Board.create({
    subject: "게시글1",
    content: "내용1",
    userid: "hsb7722",
  });
  await Board.create({
    subject: "게시글2",
    content: "내용2",
    userid: "hsb7722",
  });
  await Board.create({
    subject: "게시글3",
    content: "내용3",
    userid: "hsb7722",
  });
  await Board.create({
    subject: "게시글4",
    content: "내용4",
    userid: "hsb7722",
  });
  await Board.create({ subject: "게시글5", content: "내용5", userid: "admin" });
  await Board.create({ subject: "게시글6", content: "내용6", userid: "admin" });

  await Comment.create({ content: "댓글1", boardid: 1, userid: "hsb7722" });
  await Comment.create({ content: "댓글2", boardid: 1, userid: "hsb7722" });
  await Comment.create({ content: "댓글3", boardid: 1, userid: "admin" });
  await Comment.create({ content: "댓글4", boardid: 1, userid: "admin" });

  // 1. Board 글쓰기 --> 해시태그

  // write
  // subject, content, hashtag --> body에 들어감
  // userid --> header(req.cookies)에 들어감
  const body = {
    subject: "새로운 글 등록",
    content: "hi~~~",
    hashtag: ["#javascript", "#helloworld", "#nodejs"],
  };

  const cookies = {
    userid: "hsb7722",
  };

  const req = { body, cookies };
  // console.log(req);

  // Board 테이블과 Hash 테이블의 내용을 구별
  // const {subject, content, hashtag} = req.body;
  const { hashtag, ...rest } = req.body;
  // console.log(rest); // { subject: '새로운 글 등록', content: 'hi~~~' }

  // 1. Board 테이블에 Insert
  const board = await Board.create(rest); // record
  // console.log(board);
  // console.log(hashtag); // [ '#javascript', '#helloworld', '#nodejs' ]

  // SELECT * FROM Hash WHERE tag = "#javascript";
  // INSERT INTO Hash(tag) VALUES("#javascript");

  // create(): Insert해 줌!
  // findOrCreate(): 찾아서 없으면 Insert하고, 있으면 Insert하지 않고 findAll만 수행함!
  // const insert1 = await Hash.findOrCreate({ where: { tag: "#javascript" } });
  // const insert2 = await Hash.findOrCreate({ where: { tag: "#javascript" } });

  // hashtag.forEach(async (v) => {
  //   await Hash.findOrCreate({ where: { tag: v } });
  // });

  // 다 성공했는지를 알기 위해 사용하는 것이 "Promise.all"이다!!
  // Promise.all([Promise, Promise, Promise])

  // Hash.findOrCreate({ where: { tag: v } })
  // [ '#javascript', '#helloworld', '#nodejs' ]
  const hashtags = hashtag.map((tag) => Hash.findOrCreate({ where: { tag } }));
  const tags = await Promise.all(hashtags);
  // console.log(tags); // [[{}], [{}], [{}]] --> [{}, {}, {}]

  // console.log(board.__proto__); // __proto__: board에 있는 상속받은 메서드들을 볼 수 있음!

  // tags.map((v) => v[0]); // [[{}], [{}], [{}]] --> [{}] --> {}

  await board.addHashes(tags.map((v) => v[0])); // [{}, {}, {}]

  // 2. Board view --> comment, 좋아요, 해시태그
  // view
  // GET /board/1
  const id = 7;

  const view = await Board.findOne({
    // raw: true,
    // nest: true,
    where: { id: id },
    include: {
      model: User,
      attributes: ["username"],
    },
  });

  // console.log(view);
  // console.log(view.__proto__);

  // getHashes
  const comments = await view.getComments({ raw: true });
  // const hashes = (await view.getHashes({ raw: true })).map((v) => ({
  //   tag: v.tag,
  // }));
  // result: [ { tag: '#helloworld' }, { tag: '#javascript' }, { tag: '#nodejs' } ]

  const hashes = (await view.getHashes({ raw: true })).map((v) => v.tag); // result: [ '#helloworld', '#javascript', '#nodejs' ]

  // const hashes = (await view.getHashes({ raw: true })).map((v) =>
  //   console.log(v)
  // );

  console.log(comments);
  console.log(hashes);

  // 3. Board list 구현 --> 좋아요
})();

module.exports = {
  sequelize,
  Sequelize,
};