그저 내가 되었고

항해99) 4주차:: 주특기 숙련; 📚MySQL → Sequelize 본문

개발/항해99 9기

항해99) 4주차:: 주특기 숙련; 📚MySQL → Sequelize

hyuunii 2022. 10. 12. 06:44

00. 선행 지식

1) 관계형 디비(SQL)와 비관계형 디비(NoSQL)의 개념

- 그동안 썼던 MongoDB는 비관계형 디비. 지금부터는 MySQL을 쓸건데, 얘는 관계형 디비임. 데이터 형식이 자유로운 비관계형 db와는 달리, 관계형 db는 "스키마", "테이블"(스키마 하위 개념)등의 개념이 있음. 여기서 스키마가 MySQL 서버의 가상 개념인 "데이터베이스"와 동일함. 

MySQL은 테이블이라는 개념. 사진처럼 엑셀과 유사한 형태로 데이터를 관리함. 이 테이블에 내가 원하는 데이터를 차곡차곡 쌓아 나가는 것.

 

2) mongoose라는 라이브러리의 개념(몽구스로 몽고db 사용)

- 몽구스는 JS로부터 몽고db에 데이터를 읽고 쓰기 쉽게 해주는 라이브러리. 이걸 ODM(Object Document Mapper)이라고도 부르는데, JS의 Object와 mongodb의 document를 서로 mapping해주는 도구라고 보면 됨.

- 몽구스가 ODM의 기능을 충실하게 잘 해주고 있기 때문에, 우리는 몽고디비에 쉽게 데이터를 넣고, 쓰고, 관리할 수 있는 것.

 

3) sequalize라는 라이브러리를 사용해 MySQL에 데이터 읽고 쓰기(씨퀄라이즈로 MySQL 사용)

- 관계형 db인 MySQL을 이용할 때는 ODM 말고 ORM(Object Relational Mapper)을 사용함

- ORM중 가장 유명한 Sequalize라는 라이브러리 써봅시당


01. SQL(Structured Query Language)

1) SQL?

- 데이터를.. 아무리 많이 모아도, 쓰지 않으면 가치 없쥬. 조건에 맞는 데이터를 활용할 수 있어야 하고, 끊임없이 변하는 내용을 지속적으로 삽입, 수정, 삭제, 조회할 수 있어야 함.

- 그런 상황에서 데이터를 어떻게 관리해야 할 지 고민한 결과, 모든 데이터들을 공통적으로 관리할 수 있는 표준 언어인 sql이 탄생함. 

- 즉, 생성, 삽입, 조회 명령문을 SQL이라고 함.

 

SQL의 종류:

DDL(Data Definition Language)

DML(Data Manipulational Language)

DCL(Data Control Language)

 

2) DDL(Data Definition Language)

- 데이터 정의. how? 테이블이나 db 생성, 수정, 삭제 같은 것

CREATE: db, table, view, index 등을 생성 시

DROP: db, table, view, index 등을 삭제 시

ALTER: table의 속성 변경 시

 

3) DML(Data Manipulational Language)

- 데이터 조작. how? 데이터 저장, 삭제, 수정, 조회 같은 것

SELECT: 일반적으로 table에서 원하는 데이터 조회할 때. db 관리시 가장 많이 씀~!

INSERT: table에 새로운 data 삽입할 때

DELETE: 테이블에서 특정 조건 맞는 데이터 삭제할 때

UPDATE: 테이블서 특정 조건 맞는 데이터들 수정할 때

 

4) DCL(Data Control Language)

- 디비에 대한 권한과 관련된 문법. 특정 유저가 디비에 접근할 수 있는 권한 설정시 사용.

COMMIT: db의 작업이 정상적으로 완료되었음을 관리자에게 알려줌. 

ROLLBACK: db의 작업이 비정상적으로 완료되었음을 관리자에게 알려줌

GRANT: db의 특정한 유저에게 사용 권한을 부여함

REVOKE: db의 특정한 유저에게 사용 권한을 취소함

 

5) Webstorm에서 SQL 사용해보기

+ 기본 개념
DB는 매우 고도화된 엑셀임.

엑셀 파일 하나가 "DB",
엑셀 시트 하나는 "TABLE",
엑셀 행 하나는 "DATA"임.

그러므로, 테이블 먼저 만들고 거기에 데이터 삽입하고 조회해보자~!

(0) SQL 사용 전 준비

- 연결된 MySQL을 우클릭하여 새로작성, 쿼리콘솔 열자(이제 여기서 원하는 SQL 작성 가능~!)

 

(1) db 생성(파일 이름이 4W_indivAssgn인 엑셀 파일을 하나 만드는 것과 같음!!)

CREATE DATABASE 4W_indivAssgn;

 

(2) 테이블 생성(시트 이름이 courses인 엑셀 시트 하나 만드는 것과 같음. 수많은 행 삽입 가능)

CREATE TABLE IF NOT EXISTS courses (
    id bigint(5) NOT NULL AUTO_INCREMENT, 
    title varchar(255) NOT NULL,
    tutor varchar(255) NOT NULL,
    PRIMARY KEY (id)
);

 

(3) 데이터 삽입(행 두개를 삽입해봅시다!~)

INSERT INTO courses (title, tutor) VALUES
    ('Node.js 숙련반', '이용우'), ('웹개발 종합반', '이범규');

여기까지 하면

 

이렇게 잘 들어가고용

 

(4) 데이터 조회

SELECT * FROM courses;

courses에서 모든 데이터 불러오기~! 적으면 db출력창에 (3)과 똑같은 테이블 출력됨당

 

 

5) 음식 주문앱 DB 설계 예제

 

  • 회원 1명은 주문 N개를 할 수 있다.
    • 회원 : 주문 = 1 : N 관계
  • 음식 1개는 주문 N개에 포함될 수 있다.
    • 음식 : 주문 = 1 : N 관계
  • 결론적으로
    • 회원 : 음식 = N : N 관계

 

MySQL Create Table Query👇🏻

CREATE TABLE User(
    userId int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name varchar(255) NOT NULL UNIQUE
);

CREATE TABLE Food(
    foodId int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name varchar(255),
    price int(11)
);

CREATE TABLE Order(
    orderId int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    userId int(11) NOT NULL, 
    foodId int(11) NOT NULL, 
    createdAt datetime NOT NULL DEFAULT NOW(),
    FOREIGN KEY (foodId) REFERENCES Food(foodId) ON DELETE CASCADE,
    FOREIGN KEY (userId) REFERENCES User(userId) ON DELETE CASCADE
);

 

SQL 연습) name, age 라는 열을 가진 users 라는 테이블을 만들고, 나의 이름과 나이 데이터를 삽입

// 테이블 생성
CREATE TABLE IF NOT EXISTS users (
    id bigint(5) NOT NULL AUTO_INCREMENT,
    name varchar(255) NOT NULL,
    age bigint(5) NOT NULL,
    PRIMARY KEY (id)
);

// 데이터 삽입
INSERT INTO users (name, age) VALUES
    ('이용우', 28);

// 데이터 조회
SELECT * FROM users;

02. Sequalize 설정

1) 모듈 설치

npm i sequelize mysql2 -S
npm i sequelize-cli -D

- sequelize: 노드 코드에서 MySQL을 사용할 수 있게 해주는 라이브러리

- mysql2: 노드에서 씨퀄라이즈를 이용해 MySQL을 좀 더 쉽게 조작하도록 해주는 패키지

- sequelize-cli: 씨퀄라이즈를 좀 더 쉽게! 쓰도록 도와주는 도구를 설치하는 명령어... 복잡네(뒤의 d는 devDependency. 개발할 때 필요한 모듈 설치할 때 붙이는 플래그)

 

2) 씨퀄라이즈 사용 준비(with sequelize-cli)

npx sequelize init

 

3) 모가 바뀌엇져? 하위폴더들 확인 ㄱㄱ

※주의!! 여기서 생성된 폴더나 파일들은 절대로 임의로 옮기믄 안된당!!!! sequelize-cli는 정해진 경로에 있는 파일을 사용하고 저장하기 때문에 임의로 옮기면 오동작함~~~~!

 

*내 프로젝트 폴더 이름*

├── models

│      └── index.js

├── config

│      └── config.json

├── migrations

├── seeders

├── package-lock.json

└── package.json

(1) models 폴더 안에 index.js 생성됨: 우리가 구현할 씨퀄라이즈 모델을 편리하게 사용할 수 있게 해줌. 절대 지움 ㄴㄴ

(2) config 폴더 안에 config.json 파일 생성됨: 이 파일 열어보면 디비에 연결하기 위한 설정 데이터가 json 형식으로 들어가있음

(3) migrations 폴더가 생성됨: 빈 폴더가 맞음!!!

(4) seeder 폴더가 생성됨: 빈 폴더가 맞음!!!

 

4) config/config.json 파일 수정하기: 이 파일은... 디비에 연결하기 위한 설정 데이터가 json 형식으로 들어가 있슴당. 이걸 열어서 내 컴터에 띄워둔 디비 정보를 입력해보장.

++ development, test, production 3개의 키가 존재할텐데 우린 지금은 development 안에 있는 내용만 수정ㄱㄱ

// config/config.json
{
  "development": {
    "username": "내 유저네임",
    "password": "내 비번",
    "database": "database_development",
    "host": "내 엔드포인트",
    "dialect": "mysql"
  },
  "test": {
    "username": "root",
    "password": null,
    "database": "database_test",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  "production": {
    "username": "root",
    "password": null,
    "database": "database_production",
    "host": "127.0.0.1",
    "dialect": "mysql"
  }
}

 

5) 데이터베이스 생성하기

npx sequelize db:create

왜 디비 생성함...? 지금부터 언급할 디비는 일반적인 디비 서버와는 또 다른 "데이터베이스"라고 부르는 MySQL 서버와는 다른 MySQL이 정의해둔 가상의 개념. 하나의 서버에 여러개의 가상의 데이터베이스를, 데이터베이스 안에 여러개의 테이블을 생성해서 관리하는 방식.

즉,,, 1개의 MySQL 서버 안에 n개의 데이터베이스가 존재할 수 있고, 개개의 데이터베이스 안에 또다시 n개의 테이블 존재 가능쓰~!

 

데이터베이스 잘 만들어졌는지 쳌쳌?! refresh 궈궈~


03. 로그인/회원가입 기능을 sequelize로 구현하기(1)

1) models/user.js 파일 제거: 지금 존재하는 models 폴더의 user.js 파일은 mongoose로 구현한 모델임. 이걸 지우고 sequalize로 다시 만들어보쟝........ 지웟!!!

 

 

2) 사용자 모델 생성

npx sequelize model:generate --name User --attributes email:string,nickname:string,password:string

: 터미널에 이거 고대로 입력하면 models 폴더에 user.js 파일이 생기고, migrations 폴더에 숫자-create-user.js같은 이름으로 파일이 하나 생길것.

 

➤models/user.js의 역할? 

db에 읽고 쓰고 변경하는 데이터를 모델이라는 형태로 구현해서 정해둔 형식대로 다루게 해주는 역할

 

➤migrations 안의 파일의 역할? 

db에 테이블(엑셀의 시트)을 생성하고 제거할 때 여기에 있는 파일을 사용

 

더보기

3) 모델과 마이그레이션 파일 수정하기

우리는 명령어 한줄로 간단하게 사용자 모델을 만들었지만, 두 파일 모두 한가지씩 수정할거예요! Sequelize는 데이터의 고유 ID를 기본적으로 "id" 로 저장하도록 하는데, 저희가 준비한 프론트엔드 코드는 이 고유 ID의 이름이 "id"가 아닌 "userId"여야 제대로 동작하도록 짜여 있답니다!

그렇기 때문에 id 대신 userId라는 이름으로 ID를 다루도록 수정해볼게요!

 

models/user.js

'use strict';
const {
  Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
  class User extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      // define association here
    }
  };
  User.init({
    userId: {
      primaryKey: true,
      type: DataTypes.INTEGER,
    },
    email: DataTypes.STRING,
    nickname: DataTypes.STRING,
    password: DataTypes.STRING
  }, {
    sequelize,
    modelName: 'User',
  });
  return User;
};

 

migrations/숫자-create-user.js

'use strict';
module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.createTable('Users', {
      userId: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      email: {
        type: Sequelize.STRING
      },
      nickname: {
        type: Sequelize.STRING
      },
      password: {
        type: Sequelize.STRING
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  down: async (queryInterface, Sequelize) => {
    await queryInterface.dropTable('Users');
  }
};

 

4) 테이블 생성하기

npx sequelize db:migrate

 

5) 테이블 잘 생성됐는지 확인하기

: "database_development" 테이블 위에서 오른쪽 마우스를 누르면 아래처럼 "Refresh" 메뉴가 뜨고, 이것을 누르면 아래 사진처럼 SequelizeMeta, Users 라는 테이블이 보여야 함


04. 로그인/회원가입 기능을 sequelize로 구현하기(2)

1) 사용자 모델을 이용해 회원가입 기능 수정하기

- how to?

+ 이번에 구현한 사용자 모델을 이용해서 몽고디비 말고 mysql에 사용자 데이터를 저장하도록 바꿀 것

사용자 모델 불러오기 예시)

const { User } = require("./models");

+ 더불어, 이메일과 닉네임 중복 확인하는 코드도 제대로 동작하는지 쳌쳌

 

- 회원가입 API 수정 예시

더보기
// app.js
...
const { Op } = require("sequelize");
const { User } = require("./models");

...

router.post("/users", async (req, res) => {
  const { email, nickname, password, confirmPassword } = req.body;

  if (password !== confirmPassword) {
    res.status(400).send({
      errorMessage: "패스워드가 패스워드 확인란과 다릅니다.",
    });
    return;
  }

  // email or nickname이 동일한게 이미 있는지 확인하기 위해 가져온다.
  const existsUsers = await User.findAll({
    where: {
      [Op.or]: [{ email }, { nickname }],
    },
  });
  if (existsUsers.length) {
    res.status(400).send({
      errorMessage: "이메일 또는 닉네임이 이미 사용중입니다.",
    });
    return;
  }

  await User.create({ email, nickname, password });
  res.status(201).send({});
});

 

 

2) 사용자 모델을 이용해 로그인 기능 수정하기

- how to?

+ 사용자 정보를 데이터베이스에서 가져오는 부분만 수정

+ 나머지 코드는 db에 의존하는 코드가 아니기 때문~!

 

- 로그인 API 수정 예시

더보기
// app.js
router.post("/auth", async (req, res) => {
  const { email, password } = req.body;

  const user = await User.findOne({
    where: {
      email,
    },
  });

  // NOTE: 인증 메세지는 자세히 설명하지 않는것을 원칙으로 한다: https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html#authentication-responses
  if (!user || password !== user.password) {
    res.status(400).send({
      errorMessage: "이메일 또는 패스워드가 틀렸습니다.",
    });
    return;
  }

  res.send({
    token: jwt.sign({ userId: user.userId }, "customized-secret-key"),
  });
});

 

 

3) 사용자 모델을 이용해 로그인 확인 미들웨어 수정하기

- how to?

+ userId를 가지고 db에서 사용자 정보를 불러오게끔 해야함. findByPk 쓰자

 

- 로그인 확인 미들웨어 수정 예시

더보기
// middlewares/auth-middleware.js

const jwt = require("jsonwebtoken");
const { User } = require("../models");

module.exports = (req, res, next) => {
  const { authorization } = req.headers;
  const [authType, authToken] = (authorization || "").split(" ");

  if (!authToken || authType !== "Bearer") {
    res.status(401).send({
      errorMessage: "로그인 후 이용 가능한 기능입니다.",
    });
    return;
  }

  try {
    const { userId } = jwt.verify(authToken, "customized-secret-key");
    User.findByPk(userId).then((user) => {
      res.locals.user = user;
      next();
    });
  } catch (err) {
    res.status(401).send({
      errorMessage: "로그인 후 이용 가능한 기능입니다.",
    });
  }
};