상세 컨텐츠

본문 제목

[Node.js] 7장 MySQL

21-22/21-22 Node.js

by Kimpeep 2021. 11. 29. 13:00

본문

728x90

1. 데이터베이스란?

  • 데이터베이스란?
데이터베이스 : 관련성을 가지며 중복이 없는 데이터들의 집합

출처: Node.js 교과서 개정2판(조현영 저) 279페이지

  • DBMS란?
DBMS(Database Management System) : 데이터베이스를 관리하는 시스템

RDBMS(Relational DBMS) : 관계형 DBMS -> 많이 사용됨
         -  대표적인 RDBMS : Oracle, MySQL, MSSQL => SQL 언어를 사용해 데이터를 관리

 

 


 

 

2. MySQL 설치하기

(1) 윈도

출처 : Node.js 교과서 개정 2판(조현영 저) 280페이지

  • No thanks, just start my download 클릭하여 다운로드 시작하기

출처 : Node.js 교과서 개정 2판(조현영 저) 280페이지

  • 내려받은 파일 실행하여 MySQL Installer 띄우기
  • Choosing a Setup Type 부분에서 Custom 선택한 후 Next 버튼 누르기

출처 : Node.js 교과서 개정 2판(조현영 저) 281페이지

  • Select Products and Features에서 Available Products: 에서 X86과 X64 중 자신의 운영체제에 맞는 MySQL Server와 MySQL Workbench를 골라 Products/Features To Be Installed:로 옮긴 후 Next 버튼 누르기

출처 : Node.js 교과서 개정 2판(조현영 저) 281페이지

  • Installation에서 Execute 버튼을 눌러 설치하기 (중간에 다른 파일들 설치할 것인지 물으면 모두 설치)

출처 : Node.js 교과서 개정 2판(조현영 저) 282페이지

  • 설치 완료 후 Next 버튼 누르기

출처 : Node.js 교과서 개정 2판(조현영 저) 282페이지

  • Account Roles에서 Root 비밀번호 설정하고 계속 Next 버튼 누르기

출처 : Node.js 교과서 개정 2판(조현영 저) 283페이지

  • 마지막 설정 화면인 Apply Configuration이 나오면 Execute 버튼 누르기

출처 : Node.js 교과서 개정 2판(조현영 저) 284페이지

  • Finish 버튼 눌러 설정 완료하기

출처 : Node.js 교과서 개정 2판(조현영 저) 284페이지

  • Next 버튼 누르고 Finish 버튼 눌러서 설치 완료하기

출처 : Node.js 교과서 개정 2판(조현영 저) 286페이지

  • MySQL이 설치된 폴더로 이동한 후, 명령 프롬프트를 통해 MySQL에 접속
  • 명령 프롬프트에서 mysql -h localhost -u root -p 입력하고 비밀번호 입력하기

  • 프롬프트가 mysql>로 바뀌었다면 접속 완료

  • 명령 프롬프트에서 exit 입력하여 콘솔로 돌아가기

 

 

(2) 맥

  • 다음 명령어로 Homebrew 설치
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
  • Homebrew를 통해 MySQL 설치하기
$ brew install mysql
$ brew services start mysql
$ mysql_secure_installation
  • brew services start mysql 명령어로 MySQL 시작
  • mysql_secure_intallation 명령어로 root 비밀번호 설정
  • validate_password 플러그인 설치 여부 물으면 모두 n 입력
  • Enter 눌러 건너뛰기
  • 콘솔에 mysql -h localhost -u root -p 명령어 입력하고 비밀번호 입력하기
  • 프롬프트가 mysql>로 바뀌었다면 접속 완료

 

 

(3) 리눅스(우분투)

  • 다음 명령어를 순서대로 입력하여 MySQL 설치
$ sudo apt-get update
$ sudo apt-get install -y mysql-server
$ sudo mysql_secure_installation
  • sudo mysql_secure_installation을 입력하여 설정한 비밀번호 입력
  • VALIDATE PASSWORD 플러그인 설치 여부 물으면 모두 n 입력
  • Enter 누르고 건너뛰기
  • 콘솔에 mysql -h localhost -u root -p 명령어 입력하고 비밀번호 입력하기
  • 프롬프트가 mysql>로 바뀌었다면 접속 완료

 

 


 

 

3. 워크벤치 설치하기

(1) 윈도

MySQL과 함께 워크벤치 설치했으므로 생략

 

 

(2) 맥

  • Homebrew를 통해 설치
$ brew cask install mysqlworkbench
  • Launchpad에 들어가면 워크벤치가 설치되어 있음
  • 실행 후 경고창이 뜨면 열기 버튼 누르기

출처 : Node.js 교과서 개정 2판(조현영 저) 291페이지

 

(3) 리눅스(우분투)

GUI를 사용하지 않으므로 워크벤치 설치하지 않음

 

 

(4) 커넥션 생성하기

  • 워크벤치 실행하기
  • MySQL Connections 옆의 + 버튼 누르기

출처 : Node.js 교과서 개정 2판(조현영 저) 291페이지

  • 커넥션 생성 화면에서 Connection Name에 localhost를 입력하고 Password에서 Store in Vault... 버튼 누르기

출처 : Node.js 교과서 개정 2판(조현영 저) 292페이지

  • MySQL 설치 시 설정했던 비밀번호 입력하고 OK 누르기

출처 : Node.js 교과서 개정 2판(조현영 저) 292페이지

  • Setup New Connection 화면에서도 OK 누르면 커넥션 생성됨
  • MySQL Connections에 새로 생성된 localhost 누르기

출처 : Node.js 교과서 개정 2판(조현영 저) 293페이지

  • 커넥션 성공 화면

출처 : Node.js 교과서 개정 2판(조현영 저) 293페이지

 

 


 

 

4. 데이터베이스 및 테이블 생성하기

(1) 데이터베이스 생성하기

  • 주요 명령어
CREATE SCHEMA [데이터베이스명] : 데이터베이스를 생성         
       +) DEFAULT CHARACTER SET utf8 : 한글 사용할 수 있게 만들고 싶을 때
use [데이터베이스명] : 앞으로 해당 데이터베이스를 사용하겠다는 것을 MySQL에 알림

예약어 : MySQL이 기본적으로 알고 있는 구문 ex) CREATE SCHEMA         
         - 소문자로 써도 되지만, 대문자 권장

예시 코드 (콘솔)

 

 

(2) 테이블 생성하기

  • 테이블이란?
테이블 : 데이터가 들어갈 수 있는  -> 테이블에 맞는 데이터만 들어갈 수 있음
  • 주요 명령어
CREATE TABLE [데이터베이스명.테이블명] (컬럼명 컬럼자료형 컬럼옵션, ...) : 테이블 생성
                   - use 데이터베이스명; 명령어를 실행하였다면 데이터베이스명은 생략 가능

컬럼 자료형
1) INT : 정수 / FLOAT, DOUBLE : 소수까지 저장
2) VARCHAR(자릿수) : 가변 길이 문자열
3) CHAR(자릿수) : 고정길이 문자열
4) TEXT : 긴 글을 저장 (VARCHAR 보다 길 때)
5) TINYINT : -128~127까지의 정수, 1 또는 0만 저장하면 Boolean과 같은 역할
6) DATETIME : 날짜와 시간   
    DATE : 날짜 정보, TIME : 시간 정보

컬럼 옵션

1) NULL/NOT NULL : 빈칸 허용 여부
2) AUTO_INCREMENT : 숫자 자동 부여
3) UNSIGNED : 음수가 나올 수 없음
4) ZEROFILL : 자릿수 중에 비어있는 자리에 모두 0을 넣음
5) DEFAULT : 데이터베이스에 저장 시 해당 컬럼에 값이 없다면 MySQL이 기본값을 대신 넣음
            - CURRENT_TIMESTAMP / now() : 현재 시각을 넣음
6) PRIMARY KEY(컬럼명) : 해당 컬럼을 기본키(로우를 대표하는 고유한 값)로 설정함
7) UNIQUE INDEX  인덱스 이름 (컬럼명 조건): 해당 컬럼은 고유한 값이어야 함

테이블 자체 설정
1) COMMENT : 테이블에 대한 보충 설명
2) DEFAULT CHARACTER SET = utf8; : 한글이 입력될 수 있게 설정
3) ENGINE [MyISAM or InnoDB] : 엔진 지정

제약 조건
1)  CONSTRAINT FOREIGN KEY(컬럼명) REFERENCES 참고할 테이블 명(참고할 컬럼 명)                                                                                      : 해당 컬럼에 다른 테이블의 기본키를 저장 -> 외래키                             
                              - ON UPDATE CASCADE : 정보가 수정되면 연결된 정보도 같이 수정됨                             
                              - ON DELETE CASCADE : 정보가 삭제되면 연결된 정보도 같이 삭제됨
DESC [테이블명] : 만들어진 테이블을 확인

DROP TABLE [테이블명] : 테이블 삭제

SHOW TABLES테이블 목록 확인

 

 

예시 코드(콘솔) 

사용자 테이블 생성
댓글 테이블 생성
테이블 확인
테이블 삭제
테이블 목록 확인

 

 


 

 

5. CRUD 작업하기

  • CRUD란?
CRUD : Create, Read, Update, Delete의 첫 글자를 모은 두문자어 / 데이터베이스에서 많이 수행하는 네 가지 작업

출처 : Node.js 교과서 개정 2판(조현영 저) 306페이지

 

(1) Create(생성)

  • Create란?
Create(생성) : 데이터를 생성해서 데이터베이스에 넣는 작업
  • 주요명령어
INSERT INTO 데이터베이스명.테이블명 (컬럼1, 컬럼2, ...) VALUES (컬럼1값, 컬럼2값, ...);
                                                                                                          : 해당 테이블에 데이터 삽입
                                      - use 데이터베이스명; 명령어를 사용했다면 데이터베이스명 생략 가능

예시 코드(콘솔)

데이터 삽입

 

 

(2) Read(조회)

  • Read란?
Read(조회) : 데이터베이스에 있는 데이터를 조회하는 작업
  • 주요 명령어
SELECT * FROM [테이블명] : 테이블의 모든 데이터를 조회
SELECT [특정컬럼] FROM [테이블명] WHERE [조건] ORDER BY [컬럼명] [ASC|DESC]
                  : 테이블에서 조건에 해당하는 컬럼만 조회하는데 특정 컬럼의 오름차순 혹은 내림차순으로 정렬
                - LIMIT [숫자] : 조회할 로우 개수 설정
                - OFFSET [건너뛸 숫자] : 건너뛸 로우 개수 설정

예시 코드 (콘솔)

users, comments 테이블의 모든 데이터 조회
특정 컬럼만 조회
WHERE 절 사용
ORDER BY 사용
LIMIT 사용
OFFSET 사용

 

 

(3) Update(수정)

  • Update란?
Update(수정) : 데이터베이스에 있는 데이터를 수정하는 작업
  • 주요 명령어
UPDATE [테이블명] SET [컬럼명=바꿀 값] WHERE [조건]
                                                         : 테이블에서 조건에 해당하는 로우의 컬럼 값만 원하는 값으로 수정

예시 코드 (콘솔)

UPDATE 사용

 

 

(4) Delete(삭제)

  • Delete란?
Delete(삭제) : 데이터베이스에 있는 데이터를 삭제하는 작업
  • 주요 명령어
DELETE FROM [테이블명] WHERE [조건] : 테이블에서 조건에 해당하는 로우를 제거

예시 코드 (콘솔)

DELETE FROM 사용

 

 


 

 

6. 시퀄라이즈 사용하기

  • 시퀄라이즈란?
시퀄라이즈 : 노드와 MySQL을 연동해주고 SQL문을 작성하는 것을 도와주는 라이브러리
  • 시퀄라이즈 특징
ORM(Object-relational Mapping)으로 분류
   -> ORM : 자바스크립트 객체와 데이터베이스의 릴레이션을 매핑해주는 도구
- 다른 데이터베이스도 같이 쓸 수 있어서, 프로젝트를 다른 SQL 데이터베이스로 전환할 때도 편리
  • 시퀄라이즈 사용 이유
=> 자바스크립트 구문을 알아서 SQL로 바꿔주기 때문
  • 시퀄라이즈 사용하기 위한 필수 단계
1) sequelize와 sequelize-cli, mysql2 패키지 설치
$ npm i express morgan nunjucks sequelize sequelize-cli mysql2
$ npm i -D nodemon

2) 설치 완료 후 sequelize init 명령어 호출
$ npx sequelize init

3) models 폴더 안의 index.js 코드 수정

models/index.js

 

(1) MySQL 연결하기

  • 시퀄라이즈 통해 익스프레스 앱과 MySQL 연결
const express = require("express");
const path = require("path");
const morgan = require("morgan");
const nunjucks = require("nunjucks");

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

const app = express();
app.set("port", process.env.PORT || 3001);
app.set("view engine", "html");
nunjucks.configure("views", {
  express: app,
  watch: true,
});
sequelize
  .sync({ force: false })
  .then(() => {
    console.log("데이터베이스 연결 성공");
  })
  .catch((err) => {
    console.error(err);
  });

app.use(morgan("dev"));
app.use(express.static(path.join(__dirname, "public")));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));

app.use((req, res, next) => {
  const error = new Error(`${req.method} ${req.url} 라우터가 없습니다.`);
  error.status = 404;
  next(error);
});

app.use((err, req, res, next) => {
  res.locals.message = err.message;
  res.locals.error = process.env.NODE_ENV !== "production" ? err : {};
  res.status(err.status || 500);
  res.render("error");
});

app.listen(app.get("port"), () => {
  console.log(app.get("port"), "번 포트에서 대기 중");
});

 

  • MySQL 연동할 때 사용되는 config 폴더 안의 config.json 정보 수정 (process.env.NODE_ENV가 development)
{
  "development": {
    "username": "root",
    "password": "[root 비밀번호]",
    "database": "nodejs",
    "host": "127.0.0.1",
    "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"
  }
}

=> 배포 환경(process.env.NODE_ENV가 production)을 위해 데이터베이스 설정할 때는 config/config.json의 production 속성을 수정

=> 테스트 환경(process.env.NODE_ENV가 test) 일 때는 test 속성을 수정

 

  • 서버를 실행 - 데이터베이스 연결 성공이 뜨면 연결이 성공한 것

 

 

(2) 모델 정의하기

  • MySQL에서 정의한 테이블을 시퀄라이즈에서도 정의 -> 모델과 테이블 연결
시퀄라이즈의 역할 : 시퀄라이즈의 모델과 MySQL의 테이블을 연결
                                                                             => MySQL의 테이블이 시퀄라이즈의 모델과 대응됨

시퀄라이즈 모델의 메서드
1) static init 메서드 : 테이블에 대한 설정
                - super.init 메서드
                       1. 첫 번째 인수 : 테이블 컬럼에 대한 설정
                                                                   -> MySQL 테이블과 컬럼 내용이 일치해야 정확하게 대응됨
                       2. 두 번째 인수 : 테이블 자체에 대한 설정
2) static associate 메서드 : 다른 모델과의 관계 기재

시퀄라이즈의 컬럼 자료형
1) INTEGER : MySQL의 INT
2) STRING : MySQL의 VARCHAR
3) BOOLEAN : MySQL의 TINYINT
4) DATE : MySQL의 DATETIME

시퀄라이즈의 컬럼 옵션
1) 자료형.UNSIGNED : UNSIGNED 옵션이 적용된 자료형
2) 자료형.ZEROFILL : ZEROFILL 옵션이 적용된 자료형
3) allowNull : NOT NULL 옵션과 동일
      - false : NOT NULL
      - true : NULL 허용
4) unique : UNIQUE 옵션
      - true : UNIQUE
5) defaultValue : 기본값(DEFAULT)

시퀄라이즈의 테이블 옵션
1) sequelize : static init 메서드의 매개변수와 연결되는 옵션, db.sequelize 객체를 넣어야 함 
2) timestamps 
                - true : 시퀄라이즈에서 createdAt, updatedAt 컬럼을 추가 (시간이 자동으로 입력됨)
                - false : 자동으로 날짜 컬럼을 추가하는 기능 해제
3) underscored
                - true : 테이블명과 컬럼명을 스네이크 케이스로 바꿈
                - false : 테이블명과 컬럼명을 캐멀 케이스로 만듦
4) modelName : 모델 이름 설정 -> 노드 프로젝트에서 사용
5) tableName : 실제 데이터베이스의 테이블 이름
6) charset, collate : utf8과 utf8_general_ci로 설정해서 한글이 입력되게 함

 

예시 코드

const Sequelize = require("sequelize");

module.exports = class User extends Sequelize.Model {
  static init(sequelize) {
    return super.init(
      {
        name: {
          type: Sequelize.STRING(20),
          allowNull: false,
          unique: true,
        },
        age: {
          type: Sequelize.INTEGER.UNSIGNED,
          allowNull: false,
        },
        married: {
          type: Sequelize.BOOLEAN,
          allowNull: false,
        },
        comment: {
          type: Sequelize.TEXT,
          allowNull: true,
        },
        created_at: {
          type: Sequelize.DATE,
          allowNull: false,
          defaultValue: Sequelize.NOW,
        },
      },
      {
        sequelize,
        timestamps: false,
        underscored: false,
        modelName: "User",
        tableName: "users",
        paranoid: false,
        charset: "utf8",
        collate: "utf8_general_ci",
      }
    );
  }
  static associate(db) {}
};
const Sequelize = require("sequelize");

module.exports = class Comment extends Sequelize.Model {
  static init(sequelize) {
    return super.init(
      {
        comment: {
          type: Sequelize.STRING(100),
          allowNull: false,
        },
        created_at: {
          type: Sequelize.DATE,
          allowNull: true,
          defaultValue: Sequelize.NOW,
        },
      },
      {
        sequelize,
        timestamps: false,
        modelName: "Comment",
        tableName: "comments",
        paranoid: false,
        charset: "utf8mb4",
        collate: "utf8mb4_general_ci",
      }
    );
  }

  static associate(db) {}
};

 

  • 생성한 모델을 models/index.js와 연결 -> 모델을 테이블과 연결
const Sequelize = require("sequelize");
const User = require("./user");
const Comment = require("./comment");

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

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

db.User = User;
db.Comment = Comment;

User.init(sequelize);
Comment.init(sequelize);

User.associate(db);
Comment.associate(db);

module.exports = db;

 

 

(3) 관계 정의하기

시퀄라이즈에 테이블 간 관계를 알려주어서 JOIN 기능으로 여러 테이블 간의 관계를 파악하게 함 

1. 1:N

예시
사용자 한 명은 댓글을 여러 개 작성할 수 있으나, 댓글 하나에 사용자(작성자)가 여러 명일 수는 없음.
이때, 사용자가 1이고, 댓글이 N

(in 시퀄라이즈 모델 각각의 static associate 메서드)
=> hasMany 메서드로 표현 : users 테이블의 로우 하나를 불러올 때 연결된 comments 테이블의 로우들 불러옴
=> belongsTo 메서드 : comments 테이블의 로우를 불러올 때 연결된 users 테이블의 로우를 가져옴                                                       (다른 모델의 정보가 들어가는 테이블에 사용)

출처 : Node.js 교과서 개정 2판(조현영 저) 325페이지

예시 코드

// models/user.js
static associate(db) {
    db.User.hasMany(db.Comment, { foreignKey: "commenter", sourceKey: "id" });
}
// models/comment.js
static associate(db) {
    db.Comment.belongsTo(db.User, { foreignKey: "commenter", targetKey: "id" });
}

서버 시작

 

2. 1:1

예시
사용자 한 명은 자신의 정보를 담고 있는 테이블과만 관계가 있고, 정보 테이블도 한 사람만을 가리킴.

(in 시퀄라이즈 모델 각각의 static associate 메서드)
=> hasOne 메서드를 사용
=> belongsTo 메서드 : 다른 모델의 정보가 들어가는 테이블에 사용

출처 : Node.js 교과서 개정 2판(조현영 저) 325페이지

 

예시 코드

db.User.hasOne(db.Info, { foreignKey: 'UserId', sourceKey: 'id' });
db.Info.belongsTo(db.User, { foreignKey: 'UserId', targetKey: 'id' });

 

 

3. N:M

예시
한 게시글에는 해시태그가 여러 개 달릴 수 있고, 한 해시태그도 여러 게시글에 달릴 수 있음.

(in 시퀄라이즈 모델 각각의 static associate 메서드)
=> belongsToMany 메서드 사용 : 양쪽 모델에 모두 사용
                         - through 속성 : 생성되는 새로운 모델의 이름

예시 코드

db.Post.belongsToMany(db.Hashtag, { through: 'PostHashtag' });
db.Hashtag.belongsToMany(db.Post, { through: 'PostHashtag' });
db.sequelize.models.PostHashtag

 

 

(4) 쿼리 알아보기

  • 로우 생성
// 시퀄라이즈 쿼리
const { User } = require('../models');
User.create({
  name: 'zero',
  age: 24,
  married: false,
  comment: '자기소개1',
});

 

  • 테이블의 모든 데이터 조회
// 시퀄라이즈 쿼리
User.findAll({});

 

  • 테이블의 데이터 하나만 조회
// 시퀄라이즈 쿼리
User.findOne({});

 

  • 원하는 컬럼만 조회
// 시퀄라이즈 쿼리
User.findAll({
  attributes: ['name', 'married'],
});

 

  • 조건을 지정한 조회
// 시퀄라이즈 쿼리
const { Op } = require('sequelize');
const { User } = require('../models');
User.findAll({
  attributes: ['name', 'age'],
  where: {
    married: 1,
    age: { [Op.gt]: 30 },
  },
});
시퀄라이즈 연산자 종류 - Op 객체를 불러와 사용
1) Op.gt : 초과
2) Op.gte : 이상
3) Op.lt : 미만
4) Op.lte : 이하
5) Op.ne : 같지 않음
6) Op.or : 또는
7) Op.in : 배열 요소 중 하나
8) Op.notIn : 배열 요소와 모두 다름

 

  • 정렬하여 조회
// 시퀄라이즈 쿼리
User.findAll({
  attributes: ['id', 'name'],
  order: [['age', 'DESC']],
});

 

  • 조회할 로우 개수 설정하여 조회
// 시퀄라이즈 조회
User.findAll({
  attributes: ['id', 'name'],
  order: [['age', 'DESC']],
  limit: 1,
});

 

  • 건너뛸 로우 개수 설정하여 조회
// 시퀄라이즈 쿼리
User.findAll({
  attributes: ['id', 'name'],
  order: ['age', 'DESC'],
  limit: 1,
  offset: 1,
});

 

  • 로우 수정
// 시퀄라이즈 쿼리
User.update({
  comment: '바꿀 내용',
}, {
  where: { id: 2 },
});

 

  • 로우 삭제
// 시퀄라이즈 쿼리
User.destory({
  where: { id: 2 },
});

 

 

1. 관계 쿼리

  • 관계 설정(JOIN 기능) - 어떤 모델과 관계가 있는지를 include 배열에 넣어줌
const user = await User.findOne({
  include: [{
    model: Comment,
  }]
});
console.log(user.Comments);



// where나 attribute 옵션을 사용한 include
const user = await User.findOne({
  include: [{
    model: Comment,
    where: {
      id: 1,
    },
    attributes: ['id'],
  }]
});

   [관계 설정한 뒤에 사용 가능한 관계 쿼리 메서드]

  • get모델명s : 조회
const comments = await user.getComments();
console.log(comments);

// where나 attributes 같은 옵션을 사용한 관계 쿼리 메서드
const comments = await user.getComments({
  where: {
    id: 1,
  },
  attributes: ['id'],
});
  • set모델명s : 수정
  • add모델명 : 하나 추가
const user = await User.findOne({});
const comment = await Comment.create();
await user.addComment(comment);
// 또는
await user.addComment(comment.id);
  • add모델명s : 여러 개 추가
  • remove모델명s : 삭제

 

2. SQL 쿼리하기

const [result, metadata] = await sequelize.query('SELECT * from comments');
console.log(result);

 

 

(5) 쿼리 수행하기

  • views/sequelize.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>시퀄라이즈 서버</title>
    <style>
      table { border: 1px solid black; border-collapse: collapse; }
      table th, table td { border: 1px solid black; }
    </style>
  </head>
  <body>
    <div>
      <form id="user-form">
        <fieldset>
          <legend>사용자 등록</legend>
          <div><input id="username" type="text" placeholder="이름"></div>
          <div><input id="age" type="number" placeholder="나이"></div>
          <div><input id="married" type="checkbox"><label for="married">결혼 여부</label></div>
          <button type="submit">등록</button>
        </fieldset>
      </form>
    </div>
    <br>
    <table id="user-list">
      <thead>
      <tr>
        <th>아이디</th>
        <th>이름</th>
        <th>나이</th>
        <th>결혼여부</th>
      </tr>
      </thead>
      <tbody>
        {% for user in users %}
        <tr>
          <td>{{user.id}}</td>
          <td>{{user.name}}</td>
          <td>{{user.age}}</td>
          <td>{{ '기혼' if user.married else '미혼'}}</td>
        </tr>
        {% endfor %}
      </tbody>
    </table>
    <br>
    <div>
      <form id="comment-form">
        <fieldset>
          <legend>댓글 등록</legend>
          <div><input id="userid" type="text" placeholder="사용자 아이디"></div>
          <div><input id="comment" type="text" placeholder="댓글"></div>
          <button type="submit">등록</button>
        </fieldset>
      </form>
    </div>
    <br>
    <table id="comment-list">
      <thead>
      <tr>
        <th>아이디</th>
        <th>작성자</th>
        <th>댓글</th>
        <th>수정</th>
        <th>삭제</th>
      </tr>
      </thead>
      <tbody></tbody>
    </table>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script src="/sequelize.js"></script>
  </body>
</html>
  • views/error.html
<h1>{{message}}</h1>
<h2>{{error.status}}</h2>
<pre>{{error.stack}}</pre>

 

  • public/sequelize.js
// 사용자 이름 눌렀을 때 댓글 로딩
document.querySelectorAll('#user-list tr').forEach((el) => {
  el.addEventListener('click', function () {
    const id = el.querySelector('td').textContent;
    getComment(id);
  });
});
// 사용자 로딩
async function getUser() {
  try {
    const res = await axios.get('/users');
    const users = res.data;
    console.log(users);
    const tbody = document.querySelector('#user-list tbody');
    tbody.innerHTML = '';
    users.map(function (user) {
      const row = document.createElement('tr');
      row.addEventListener('click', () => {
        getComment(user.id);
      });
      // 로우 셀 추가
      let td = document.createElement('td');
      td.textContent = user.id;
      row.appendChild(td);
      td = document.createElement('td');
      td.textContent = user.name;
      row.appendChild(td);
      td = document.createElement('td');
      td.textContent = user.age;
      row.appendChild(td);
      td = document.createElement('td');
      td.textContent = user.married ? '기혼' : '미혼';
      row.appendChild(td);
      tbody.appendChild(row);
    });
  } catch (err) {
    console.error(err);
  }
}
// 댓글 로딩
async function getComment(id) {
  try {
    const res = await axios.get(`/users/${id}/comments`);
    const comments = res.data;
    const tbody = document.querySelector('#comment-list tbody');
    tbody.innerHTML = '';
    comments.map(function (comment) {
      // 로우 셀 추가
      const row = document.createElement('tr');
      let td = document.createElement('td');
      td.textContent = comment.id;
      row.appendChild(td);
      td = document.createElement('td');
      td.textContent = comment.User.name;
      row.appendChild(td);
      td = document.createElement('td');
      td.textContent = comment.comment;
      row.appendChild(td);
      const edit = document.createElement('button');
      edit.textContent = '수정';
      edit.addEventListener('click', async () => { // 수정 클릭 시
        const newComment = prompt('바꿀 내용을 입력하세요');
        if (!newComment) {
          return alert('내용을 반드시 입력하셔야 합니다');
        }
        try {
          await axios.patch(`/comments/${comment.id}`, { comment: newComment });
          getComment(id);
        } catch (err) {
          console.error(err);
        }
      });
      const remove = document.createElement('button');
      remove.textContent = '삭제';
      remove.addEventListener('click', async () => { // 삭제 클릭 시
        try {
          await axios.delete(`/comments/${comment.id}`);
          getComment(id);
        } catch (err) {
          console.error(err);
        }
      });
      // 버튼 추가
      td = document.createElement('td');
      td.appendChild(edit);
      row.appendChild(td);
      td = document.createElement('td');
      td.appendChild(remove);
      row.appendChild(td);
      tbody.appendChild(row);
    });
  } catch (err) {
    console.error(err);
  }
}
// 사용자 등록 시
document.getElementById('user-form').addEventListener('submit', async (e) => {
  e.preventDefault();
  const name = e.target.username.value;
  const age = e.target.age.value;
  const married = e.target.married.checked;
  if (!name) {
    return alert('이름을 입력하세요');
  }
  if (!age) {
    return alert('나이를 입력하세요');
  }
  try {
    await axios.post('/users', { name, age, married });
    getUser();
  } catch (err) {
    console.error(err);
  }
  e.target.username.value = '';
  e.target.age.value = '';
  e.target.married.checked = false;
});
// 댓글 등록 시
document.getElementById('comment-form').addEventListener('submit', async (e) => {
  e.preventDefault();
  const id = e.target.userid.value;
  const comment = e.target.comment.value;
  if (!id) {
    return alert('아이디를 입력하세요');
  }
  if (!comment) {
    return alert('댓글을 입력하세요');
  }
  try {
    await axios.post('/comments', { id, comment });
    getComment(id);
  } catch (err) {
    console.error(err);
  }
  e.target.userid.value = '';
  e.target.comment.value = '';
});

 

  • app.js
const express = require("express");
const path = require("path");
const morgan = require("morgan");
const nunjucks = require("nunjucks");

const { sequelize } = require("./models");
const indexRouter = require("./routes");
const usersRouter = require("./routes/users");
const commentsRouter = require("./routes/comments");

const app = express();
app.set("port", process.env.PORT || 3001);
app.set("view engine", "html");
nunjucks.configure("views", {
  express: app,
  watch: true,
});
sequelize
  .sync({ force: false })
  .then(() => {
    console.log("데이터베이스 연결 성공");
  })
  .catch((err) => {
    console.error(err);
  });

app.use(morgan("dev"));
app.use(express.static(path.join(__dirname, "public")));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));

app.use("/", indexRouter);
app.use("/users", usersRouter);
app.use("/comments", commentsRouter);

app.use((req, res, next) => {
  const error = new Error(`${req.method} ${req.url} 라우터가 없습니다.`);
  error.status = 404;
  next(error);
});

app.use((err, req, res, next) => {
  res.locals.message = err.message;
  res.locals.error = process.env.NODE_ENV !== "production" ? err : {};
  res.status(err.status || 500);
  res.render("error");
});

app.listen(app.get("port"), () => {
  console.log(app.get("port"), "번 포트에서 대기 중");
});

 

  • routes/index.js
const express = require('express');
const User = require('../models/user');

const router = express.Router();

router.get('/', async (req, res, next) => {
  try {
    const users = await User.findAll();
    res.render('sequelize', { users });
  } catch (err) {
    console.error(err);
    next(err);
  }
});

module.exports = router;

 

  • routes/users.js
const express = require('express');
const User = require('../models/user');
const Comment = require('../models/comment');

const router = express.Router();

router.route('/')
  .get(async (req, res, next) => {
    try {
      const users = await User.findAll();
      res.json(users);
    } catch (err) {
      console.error(err);
      next(err);
    }
  })
  .post(async (req, res, next) => {
    try {
      const user = await User.create({
        name: req.body.name,
        age: req.body.age,
        married: req.body.married,
      });
      console.log(user);
      res.status(201).json(user);
    } catch (err) {
      console.error(err);
      next(err);
    }
  });

router.get('/:id/comments', async (req, res, next) => {
  try {
    const comments = await Comment.findAll({
      include: {
        model: User,
        where: { id: req.params.id },
      },
    });
    console.log(comments);
    res.json(comments);
  } catch (err) {
    console.error(err);
    next(err);
  }
});

module.exports = router;

 

  • routes/comments.js
const express = require('express');
const { Comment } = require('../models');

const router = express.Router();

router.post('/', async (req, res, next) => {
  try {
    const comment = await Comment.create({
      commenter: req.body.id,
      comment: req.body.comment,
    });
    console.log(comment);
    res.status(201).json(comment);
  } catch (err) {
    console.error(err);
    next(err);
  }
});

router.route('/:id')
  .patch(async (req, res, next) => {
    try {
      const result = await Comment.update({
        comment: req.body.comment,
      }, {
        where: { id: req.params.id },
      });
      res.json(result);
    } catch (err) {
      console.error(err);
      next(err);
    }
  })
  .delete(async (req, res, next) => {
    try {
      const result = await Comment.destroy({ where: { id: req.params.id } });
      res.json(result);
    } catch (err) {
      console.error(err);
      next(err);
    }
  });

module.exports = router;

 

  • 서버 실행

 

  • http://localhost:3001로 접속

 

  • 사용자 이름 누르기 -> 사용자가 등록한 댓글 출력

 

  • 댓글 수정 

 

 


 

문제

1. 노드와 MySQL을 연동해주는 라이브러리는?

2. 시퀄라이즈 모델의 메서드 중 다른 모델과의 관계를 기재하는 메서드는?

3. 시퀄라이즈 모델의 메서드 중 테이블에 대한 설정을 하는 메서드는?

 


 

 

1. 시퀄라이즈

2. static associate

3. static init

728x90

관련글 더보기