ORM으로 하는 데이터베이스 작업

Jeris·2023년 5월 7일
0

코드잇 부트캠프 0기

목록 보기
90/107

1. 데이터베이스와 SQL

데이터를 배열로 관리하면 서버를 재실행할 때마다 데이터가 초기화되기 때문에 실제 서비스에서는 데이터베이스로 관리합니다.

데이터베이스

데이터베이스(Database)는 컴퓨터 시스템에 저장된 구조화된 데이터의 집합을 말합니다. 데이터베이스는 일반적으로 여러 사용자가 동시에 접근하여 이용할 수 있으며, 데이터를 효율적으로 저장하고 검색할 수 있는 기능을 제공합니다. 데이터베이스는 크게 관계형 데이터베이스(RDBMS)와 비관계형 데이터베이스(NoSQL)로 나뉩니다.

관계형 데이터베이스

관계형 데이터베이스(Relational Database Management System, RDMBS)는 테이블(table)로 구성되며, 각 테이블은 열(column)과 행(row)으로 구성됩니다. 테이블은 서로 관계를 맺을 수 있으며, SQL(Structured Query Language)을 사용하여 데이터를 조작합니다. MySQL, Oracle, PostgreSQL 등이 대표적인 관계형 데이터베이스입니다.

비관계형 데이터베이스

비관계형 데이터베이스(NoSQL)는 JSON이나 XML과 같은 형식으로 데이터를 저장합니다. 데이터 간의 관계를 설정하지 않아도 되기 때문에 유연하게 데이터를 저장하고 검색할 수 있습니다. MongoDB, Redis, Cassandra 등이 대표적인 비관계형 데이터베이스입니다.

SQL과 NoSQL

SQL 데이터베이스는 스키마를 가지며, 데이터는 이러한 스키마에 따라 저장됩니다. SQL 데이터베이스는 데이터의 일관성과 무결성을 보장하고 복잡한 쿼리를 수행할 수 있습니다.

NoSQL 데이터베이스는 대량의 데이터를 빠르게 처리할 수 있으며, 수평적 확장이 가능합니다.

SQL과 NoSQL의 선택은 사용자의 목적과 데이터의 특성에 따라 달라집니다. 일반적으로 정형화된 데이터와 정확한 일관성을 요구하는 경우에는 SQL을, 비정형화된 데이터나 대규모 데이터 처리 및 확장성이 필요한 경우에는 NoSQL을 사용합니다.


2. ORM이란?

클라이언트 객체

클라이언트 객체(Client Object)는 웹 브라우저나 모바일 앱과 같은 클라이언트 측에서 실행되는 코드를 작성할 때 사용되는 객체입니다. 클라이언트 객체를 사용하면 브라우저나 앱에서 사용자와 상호작용하는 코드를 작성할 수 있습니다.

대표적으로 클라이언트 객체에는 DOM(Document Object Model), XMLHttpRequest(XHR), window 객체, navigator 객체, location 객체 등이 있습니다. 이 객체들은 JavaScript를 사용하여 제어할 수 있으며, 웹 페이지나 앱의 사용자 인터페이스(UI)를 변경하거나 서버와 데이터를 주고받는 등의 작업을 수행할 수 있습니다.

MySQL에서는 MySQL2 모듈에서 클라이언트 객체를 생성할 수 있습니다.

import mysql from "mysql2";

const connection = mysql.createConnection({
  host: "localhost",
  user: "root",
  database: "test",
});

connection.query(
  "SELECT * FROM `member` WHERE `id` = 1",
  function (err, results, fields) {
    console.log(results);
  }
);

ORM 패키지를 통해서 이런 객체와 데이터베이스 간의 매핑을 간결하게 작성할 수 있습니다.

ORM

ORM(Object-Relational Mapping)은 객체와 관계형 데이터베이스 간의 데이터 매핑을 자동화하는 기술입니다. 객체 지향 프로그래밍에서는 객체를 생성하고 조작하는 방법을 제공하며, 관계형 데이터베이스에서는 데이터를 테이블로 저장하고 SQL(Structured Query Language)을 사용하여 데이터를 조작합니다. ORM은 이러한 두 가지 패러다임 간의 간극을 줄이기 위한 기술로 개발되었습니다.

ORM은 객체와 데이터베이스 간의 매핑을 자동화하기 때문에 개발자들이 SQL 쿼리를 작성하거나 데이터베이스에 직접 접근하지 않아도 됩니다. ORM은 개발자가 객체로 데이터를 다룰 수 있도록 API를 제공하며, 이 API는 ORM이 내부적으로 SQL 쿼리를 생성하고 데이터베이스에 접근하여 데이터를 가져오거나 조작합니다.

ORM은 개발 생산성을 높이고, 유지보수를 용이하게 하며, 데이터베이스와의 의존성을 낮출 수 있습니다. ORM은 데이터베이스의 스키마 변경에도 적응할 수 있으며, 코드의 재사용성을 높일 수 있습니다. 대표적인 ORM 프레임워크로는 Java에서는 Hibernate, Python에서는 Django ORM, Ruby에서는 ActiveRecord 등이 있습니다.

// NoORM
UPDATE vehicles SET color=`blue` WHERE `id`=1;

// ORM usage
vehicle.color = `blue`;

3. 데이터베이스 생성하기

sequelize

Sequelize는 Node.js에서 사용하는 ORM(Object-Relational Mapping) 패키지 중 하나로, MySQL, PostgreSQL, SQLite, MSSQL 등의 데이터베이스를 지원합니다. Sequelize는 데이터베이스와의 상호작용을 추상화하여, 객체 지향적인 방식으로 데이터베이스를 다룰 수 있게 해주는 패키지입니다.

Sequelize는 ORM의 기본적인 기능을 제공하며, 모델(Model)과 마이그레이션(Migration)을 지원합니다. 모델은 데이터베이스의 테이블과 매핑되는 JavaScript 객체를 정의하는 것으로, 데이터베이스에서 데이터를 가져오거나 저장할 때 사용됩니다. 마이그레이션은 데이터베이스 스키마를 관리하는 것으로, 데이터베이스의 스키마를 변경할 때 사용됩니다.

Sequelize는 간편한 쿼리 작성과 유효성 검사(Validation)를 지원하며, 트랜잭션(Transaction)을 사용하여 데이터 일관성을 유지할 수 있습니다. 또한, Sequelize는 Node.js의 비동기 처리 방식에 맞게 작성되어, 높은 성능을 보입니다.

Sequelize는 Node.js의 프로젝트에서 사용되며, Express.js나 Koa.js와 같은 웹 프레임워크와 함께 사용하여 데이터베이스와의 상호작용을 쉽게 처리할 수 있습니다. Sequelize를 사용하면 데이터베이스와의 작업을 추상화하여 코드의 유지보수성을 높이고, 개발 생산성을 높일 수 있습니다.

sequelize-cli

Sequelize-cli는 Sequelize ORM을 사용할 때 쉽게 사용할 수 있는 Command Line Interface(CLI) 도구입니다. Sequelize-cli를 사용하면, 데이터베이스 마이그레이션(Migration)과 시딩(Seeding)을 쉽게 관리할 수 있습니다. 시딩은 초기 데이터를 삽입하는 작업을 의미하며, Sequelize-cli를 사용하여 시딩 파일을 생성하고, 실행하는 등의 작업을 수행할 수 있습니다.

설치하기

mysql2, sequelize, sequelize-cli 패키지를 설치합니다.

npm install mysql2 sequelize sequlize-cli

Sequelize CLI를 사용하여 Sequelize 프로젝트를 초기화합니다.

npx sequelize init

이 명령어는 다음과 같은 디렉토리와 파일을 생성합니다.

  • config directory Sequelize 설정 파일을 저장하는 디렉토리입니다.
  • models directory Sequelize 모델(Model)을 저장하는 디렉토리입니다.
  • migrations directory Sequelize 마이그레이션(Migration)을 저장하는 디렉토리입니다.
  • seeders directory Sequelize 시딩(Seeding) 파일을 저장하는 디렉토리입니다.
  • .sequelizerc file Sequelize CLI를 구성하는 파일입니다.

config 디렉토리 안의 config.json 파일에서 development 객체를 수정해줍니다.

// config/config.json
{
  "development": {
    "username": "root",
    "password": "PASSWORD",
    "database": "COWORK",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  
  // ...
}

Sequelize CLI를 사용하여 개발 환경에서 데이터베이스를 생성합니다.

npx sequelize db:create --env development

이 명령어를 실행하면 config 디렉토리에 있는 config.json 파일에서 설정한 개발 환경의 데이터베이스 정보를 읽어와 데이터베이스를 생성합니다. 즉, 개발 환경에서 사용할 데이터베이스를 자동으로 생성할 수 있습니다.


4. npx

npx

npx는 npm의 기능 중 하나로, 로컬에 설치된 패키지를 실행하거나, 새로운 패키지를 간편하게 설치하여 실행할 수 있도록 해주는 도구입니다.

npx를 사용하면, 로컬에 설치된 패키지를 npm run-script 명령어를 사용하여 실행할 수 있습니다. 또한, npx를 사용하여 새로운 패키지를 간편하게 설치하고 실행할 수도 있습니다. 이는, 해당 패키지를 일시적으로 설치하고 실행한 후, 더 이상 사용하지 않을 경우 패키지를 자동으로 삭제하여 저장 공간을 절약할 수 있습니다.

예를 들어, npx create-react-app my-app 명령어를 사용하면, 새로운 React 프로젝트를 생성할 수 있습니다. 이 명령어는 create-react-app 패키지를 일시적으로 설치하고, my-app 디렉토리에 새로운 React 프로젝트를 생성한 후, 해당 패키지를 자동으로 삭제합니다.

따라서, npx는 패키지를 간편하게 설치하고 실행할 수 있도록 해주는 도구로, Node.js 개발에서 유용하게 사용됩니다.

npm, npx의 차이점

npm과 npx는 둘 다 Node.js 패키지 매니저(Node Package Manager)에서 제공하는 도구입니다. 그러나 npm과 npx는 각각 서로 다른 목적을 가지고 있습니다.

npm은 패키지를 설치하고 관리하는 도구로, 로컬 또는 전역적으로 패키지를 설치하고, 패키지의 의존성 관리 및 버전 업데이트 등의 작업을 수행합니다. npm을 사용하여 패키지를 설치하면, 해당 패키지가 로컬 또는 전역적으로 설치되어 패키지를 사용할 수 있습니다.

반면에, npx는 npm을 사용하여 설치하지 않고, 로컬에 설치된 패키지를 간편하게 실행할 수 있는 도구입니다. npx를 사용하여 패키지를 실행하면, 해당 패키지가 로컬에 일시적으로 설치되어 실행되며, 실행 후에는 자동으로 삭제됩니다. 따라서, npx를 사용하여 패키지를 실행하면, 로컬 저장소를 절약할 수 있습니다.

즉, npm과 npx는 서로 다른 목적을 가지고 있으며, npm은 패키지를 설치하고 관리하는 도구이고, npx는 로컬에 설치된 패키지를 간편하게 실행하는 도구입니다.

npx sequelize

npx sequelize를 실행하면 node_modules 디렉토리 안에 있는 .bin 디렉토리 안의 sequelize라는 파일을 실행하게 됩니다. 이때 이 sequelize 파일은 바로가기 파일이라서 그 원본 파일인 node_modules 디렉토리 안에 있는 sequelize-cli 패키지(디렉토리) 안의 lib 디렉토리 안의 sequelize 파일을 node로 실행하게 됩니다.


4. 모델과 테이블 생성, 삭제하기

Sequelize 기본 개념

하나의 table 하나의 class에 대응되고, 하나의 row는 class의 객체에 대응됩니다.

Member 테이블 생성 마이그레이션 파일 및 Member 모델 파일 생성하기

npx sequelize model:generate --name Member --attributes name:string,team:string,position:string,emailAddress:string,admissionDate:date,birthday:date,profileImage:string

Model

모델(Model)은 데이터베이스에서 데이터를 구성하는 구조와 규칙을 정의한 객체입니다. 데이터베이스 테이블의 구조와 규칙을 정의하는 역할을 합니다.

ORM(Object-Relational Mapping)에서 모델은 데이터베이스의 테이블과 1:1로 대응합니다. 즉, 각 모델은 데이터베이스의 각 테이블을 대표합니다. 모델은 해당 테이블의 속성(column)과 관계(key)를 정의하며, 이를 통해 데이터베이스에서 데이터를 조작할 수 있도록 합니다.

ORM에서 모델은 JavaScript 클래스로 정의됩니다. 각 모델 클래스는 ORM에서 제공하는 Model 클래스를 상속받아 정의됩니다. Model 클래스는 데이터베이스 테이블과 연결된 모델 객체를 생성하는 데 사용됩니다.

모델을 사용하면, 데이터베이스의 각 테이블과 연결된 객체를 생성할 수 있습니다. 이를 통해, 데이터베이스에서 데이터를 생성, 조회, 수정, 삭제할 수 있으며, ORM에서 제공하는 기능을 활용하여 데이터를 조작할 수 있습니다.

Migration

Migration(마이그레이션)은 데이터베이스 스키마를 변경하기 위한 절차로, 데이터베이스의 테이블 생성, 수정, 삭제 등의 작업을 수행합니다. Migration은 데이터베이스의 버전 관리를 할 때 매우 유용합니다.

Sequelize에서는 Migration을 위한 CLI(Command Line Interface)를 제공하며, npx sequelize-cli migration:create 명령어를 사용하여 새로운 마이그레이션 파일을 생성할 수 있습니다. 마이그레이션 파일은 변경할 스키마의 구조를 정의하고, 실제로 데이터베이스 스키마를 변경하는 작업을 수행합니다.

마이그레이션 파일은 일련의 변경 작업을 기록하는 파일이며, 파일명에는 타임스탬프가 포함됩니다. 마이그레이션 파일은 "up"과 "down" 함수를 가지고 있으며, "up" 함수에서는 데이터베이스 스키마를 변경하는 작업을 수행하고, "down" 함수에서는 "up" 함수에서 수행한 작업을 되돌리는 작업을 수행합니다.

Sequelize에서는 Migration을 사용하여 데이터베이스 스키마의 변경 이력을 추적할 수 있습니다. 따라서, 개발자는 데이터베이스 스키마를 관리하고, 변경 이력을 추적하여 문제가 발생했을 때 이력을 검토하고 복구할 수 있습니다.

// migration file
"use strict";
/** @type {import('sequelize-cli').Migration} */
module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.createTable("Members", {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER,
      },
      name: {
        type: Sequelize.STRING,
      },
      team: {
        type: Sequelize.STRING,
      },
      position: {
        type: Sequelize.STRING,
      },
      emailAddress: {
        type: Sequelize.STRING,
      },
      admissionDate: {
        type: Sequelize.DATE,
      },
      birthday: {
        type: Sequelize.DATE,
      },
      profileImage: {
        type: Sequelize.STRING,
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE,
        defaultValue: Sequelize.fn("now"),
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE,
        defaultValue: Sequelize.fn("now"),
      },
    });
  },
  async down(queryInterface, Sequelize) {
    await queryInterface.dropTable("Members");
  },
};

테이블 생성하기

npx sequelize db:migrate

이 명령어를 실행하면, 마이그레이션 파일을 참조하여 데이터베이스 스키마를 변경합니다.

테이블 지우기

npx sequelize db:migrate:undo

이 명령어를 실행하면, 최근에 적용된 마이그레이션을 찾아 down 함수를 실행하여 데이터베이스 스키마를 이전 상태로 되돌립니다. npx sequelize db:migrate:undo:all 명령어를 사용하면 모든 마이그레이션을 되돌릴 수 있습니다.


6. 데이터 타입과 기본값

테이블을 생성하는 migration 파일에서는 각 프로퍼티(컬럼)의 데이터 타입을 지정하고 defaultValue라는 속성을 줄 수 있습니다.

데이터 타입

Sequelize.STRING은 문자열 타입으로, 데이터베이스에서 VARCHAR(255)라고 하는 타입으로 변환됩니다.
Sequelize.INTEGER는 정수형 타입으로 데이터베이스에서 INTEGER라고 하는 타입으로 변환됩니다.
Sequelize.FLOAT은 실수형 타입으로 데이터베이스에서 FLOAT이라고 하는 타입으로 변환됩니다.
Sequelize.DATE은 날짜형 타입으로 데이터베이스에서 DATETIME이라고 하는 타입으로 변환됩니다.

Manual | Sequelize 참조

기본값

migration 파일에서 프로퍼티에 defaultValue 속성을 주게 되면 테이블의 컬럼에도 이 기본값이 적용됩니다. 만약 테이블에 어떤 row를 새롭게 삽입할 때 특정 값을 설정해주지 않으면, 이 defaultValue에 있는 값이 들어가게 됩니다.

Sequelize ORM에서는 데이터베이스에서 지원하는 함수를 나타내기 위해 Sequelize.fn('함수 이름') 형식의 코드를 사용할 수 있습니다.


7. Primary Key란?

Primary key

Primary key(기본 키)는 데이터베이스 테이블에서 각 레코드(row)를 고유하게 식별하기 위한 필드(column)입니다. Primary key는 테이블에서 중복되지 않는 고유한 값을 가지며, 다른 테이블과의 관계를 맺을 때 사용됩니다.

Primary key는 보통 테이블의 첫 번째 필드로 설정되며, 자동으로 증가하는 숫자(auto increment) 형태의 값을 사용하는 것이 일반적입니다. 그러나, 문자열이나 다른 데이터 타입도 Primary key로 설정될 수 있습니다.

Primary key를 사용하면, 데이터베이스 테이블에서 각 레코드를 쉽게 식별할 수 있습니다. 또한, 다른 테이블과의 관계를 맺을 때도 Primary key를 사용하여 관계를 구성할 수 있습니다.

Sequelize를 사용하여 ID 필드를 자동으로 생성하면, id라는 객체 프로퍼티를 사용하고 객체 내부에 다음과 같은 프로퍼티들이 생성됩니다.

  • allowNull: false ID 필드가 null 값을 허용하지 않는 것을 나타냅니다.
  • autoIncrement: true ID 값을 자동으로 증가시키는 것을 나타냅니다.
  • primaryKey: true ID 필드가 Primary key인 것을 나타냅니다.
  • type: Sequelize.INTEGER ID 필드의 데이터 타입이 정수형(integer)임을 나타냅니다.

8. seed 데이터 넣기

npx sequelize seed:generate --name initialMembers

Sequelize CLI를 사용하여 시드(seed) 파일을 생성하는 명령어입니다. 시드 파일은 데이터베이스에 초기 데이터를 추가할 때 사용됩니다.

위 명령어를 실행하면, seeds 디렉토리에 initialMembers.js 파일이 생성됩니다. 이 파일은 시드 파일로서, 데이터베이스에 초기 데이터를 추가하는 작업을 수행합니다.

시드 파일은 일련의 변경 작업을 기록하는 파일입니다. 시드 파일은 "up" 함수를 가지고 있으며, 이 함수에서는 데이터베이스에 초기 데이터를 추가하는 작업을 수행합니다.

시드 파일을 사용하면, 초기 데이터를 데이터베이스에 쉽게 추가할 수 있습니다. 이를 통해, 개발자는 데이터베이스에 초기 데이터를 쉽게 추가할 수 있으며, 테스트 환경에서 데이터베이스를 초기화하는 작업을 자동화할 수 있습니다.

// seeders/SEED_FILE.js
"use strict";

/** @type {import('sequelize-cli').Migration} */
module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.bulkInsert("Members", [
      {
        id: 1,
        name: "Alex",
        team: "engineering",
        position: "Server Developer",
        emailAddress: "alex@google.com",
        phoneNumber: "010-xxxx-xxxx",
        admissionDate: "2018/12/10",
        birthday: "1994/11/08",
        profileImage: "profile1.png",
      },
      {
        id: 2,
        name: "Benjamin",
        team: "engineering",
        position: "Server Developer",
        emailAddress: "benjamin@google.com",
        phoneNumber: "010-xxxx-xxxx",
        admissionDate: "2021/01/20",
        birthday: "1992/03/26",
        profileImage: "profile2.png",
      },
      {
        id: 3,
        name: "Charles",
        team: "engineering",
        position: "Android Developer",
        emailAddress: "charles@google.com",
        phoneNumber: "010-xxxx-xxxx",
        admissionDate: "2018/10/09",
        birthday: "1994/09/08",
        profileImage: "profile3.png",
      },
      {
        id: 4,
        name: "Eric",
        team: "engineering",
        position: "Web Frontend Developer",
        emailAddress: "eric@google.com",
        phoneNumber: "010-xxxx-xxxx",
        admissionDate: "2020/04/07",
        birthday: "1995/04/10",
        profileImage: "profile4.png",
      },
      {
        id: 5,
        name: "Danial",
        team: "marketing",
        position: "Marketing Manager",
        emailAddress: "danial@google.com",
        phoneNumber: "010-xxxx-xxxx",
        admissionDate: "2021/04/21",
        birthday: "1991/07/12",
        profileImage: "profile5.png",
      },
      {
        id: 6,
        name: "George",
        team: "marketing",
        position: "Marketing Staff",
        emailAddress: "george@google.com",
        phoneNumber: "010-xxxx-xxxx",
        admissionDate: "2020/01/06",
        birthday: "1997/02/09",
        profileImage: "profile6.png",
      },
      {
        id: 7,
        name: "Henry",
        team: "marketing",
        position: "Marketing Staff",
        emailAddress: "henry@google.com",
        phoneNumber: "010-xxxx-xxxx",
        admissionDate: "2020/04/03",
        birthday: "1997/08/18",
        profileImage: "profile7.png",
      },
      {
        id: 8,
        name: "James",
        team: "sales",
        position: "Sales Manager",
        emailAddress: "james@google.com",
        phoneNumber: "010-xxxx-xxxx",
        admissionDate: "2020/11/26",
        birthday: "1993/05/22",
        profileImage: "profile8.png",
      },
      {
        id: 9,
        name: "Kevin",
        team: "sales",
        position: "Sales Staff",
        emailAddress: "kevin@google.com",
        phoneNumber: "010-xxxx-xxxx",
        admissionDate: "2020/06/19",
        birthday: "1989/06/10",
        profileImage: "profile9.png",
      },
      {
        id: 10,
        name: "Michael",
        team: "sales",
        position: "Sales Staff",
        emailAddress: "michael@google.com",
        phoneNumber: "010-xxxx-xxxx",
        admissionDate: "2019/11/12",
        birthday: "1992/09/17",
        profileImage: "profile10.png",
      },
    ]);
  },

  down: async (queryInterface, Sequelize) => {
    await queryInterface.bulkDelete("Members", null, {});
  },
};
npx sequelize db:seed:all

이 명령어를 실행하면, 시드 파일에 정의된 데이터를 데이터베이스에 추가합니다. Sequelize CLI는 seeds 디렉토리에 있는 모든 시드 파일의 "up" 함수를 차례대로 실행하여 데이터베이스에 초기 데이터를 추가합니다.


9. 모델과 테이블 연동하기

//models/ member.js
"use strict";
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
  class Member extends Model {}
  Member.init(
    {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: DataTypes.INTEGER,
      },
      name: DataTypes.STRING,
      team: DataTypes.STRING,
      position: DataTypes.STRING,
      emailAddress: DataTypes.STRING,
      phoneNumber: DataTypes.STRING,
      admissionDate: DataTypes.DATE,
      birthday: DataTypes.DATE,
      profileImage: DataTypes.STRING,
    },
    {
      sequelize,
      modelName: "Member",
    }
  );
  return Member;
};

위 코드는 Sequelize를 사용하여 Member 모델을 정의하는 코드입니다. 모델 클래스를 생성하고, 클래스에서 init 메서드를 사용하여 모델의 속성을 정의합니다. Member 클래스는 Sequelize의 Model 클래스를 상속받아 정의되며, init 메서드를 사용하여 모델의 속성을 정의합니다.

// models/index.js
const Sequelize = require("sequelize");
const config = require("../config/config.json");

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

const Member = require("./member")(sequelize, Sequelize.DataTypes);

const db = {};
db.Member = Member;

module.exports = db;

위 코드는 Sequelize를 사용하여 데이터베이스와 모델을 연결하는 코드입니다.

Sequelize를 사용하여 데이터베이스와 연결할 때는, Sequelize 생성자를 사용하여 연결 설정을 구성합니다. 연결 설정은 config/config.json 파일에서 불러옵니다. 이때, config.development 객체에서 필요한 정보들을 추출하여 Sequelize 생성자에 전달합니다.

Member 모델을 불러올 때는 require("./member")(sequelize, Sequelize.DataTypes) 코드를 사용합니다. 이를 통해, Member 모델을 불러오고, sequelize와 DataTypes를 전달하여 Member 모델과 데이터베이스를 연결합니다.

마지막으로, db 객체를 생성하여 Member 모델을 포함시키고, 이를 모듈로 내보냅니다. 이를 통해, 다른 파일에서 db 객체를 불러와서 Member 모델을 사용할 수 있습니다.

// app.js
const db = require("./models");  // ./models/index.js 실행

const { Member } = db;
// ...

위 코드는 app.js 파일에서 db 객체를 불러오고, 이를 통해 Member 모델을 사용하는 코드입니다.


10. 직원 정보 조회하기 - findAll

// app.js

// ...

app.get("/api/members", (req, res) => {
  const { team } = req.query;
  if (team) {
	// ...
  } else {
    const teamMembers = Member.findAll();
    res.send(teamMembers);
  }
});

// ...

Member.findAll() 메서드는 데이터베이스에서 모든 Member 데이터를 조회하는 메서드입니다.

GET request를 보내면 Member.findAll()이라는 코드는 sequelize에 의해서

Executing (default): SELECT `id`, `name`, `team`, `position`, `emailAddress`, `phoneNumber`, `admissionDate`, `birthday`, `profileImage`, `createdAt`, `updatedAt` FROM `Members` AS `Member`;

이런 SQL문으로 변환되어서 DMBS에 전송됩니다.

모델의 비동기 실행

모델이 갖고 있는 대부분의 메소드들은 프로미스 객체를 리턴하는 비동기 실행 함수이기 때문에 async-await문과 같이 비동기 실행으로 리스폰스를 받아야 합니다.

// app.js

// ...

app.get("/api/members", await (req, res) => {
  const { team } = req.query;
  if (team) {
	// ...
  } else {
    const teamMembers = async Member.findAll();
    res.send(teamMembers);
  }
});

// ...

이렇게 하면 직원 정보의 배열이 리스폰스로 잘 전달됩니다.

Query로 특정 직원들의 정보 조회하기

app.get("/api/members", async (req, res) => {
  const { team } = req.query;
  if (team) {
    const teamMembers = await Member.findAll({ where: { team } });
    res.send(teamMembers);
  } else {
    const teamMembers = await Member.findAll();
    res.send(teamMembers);
  }
});

findAll() 메서드에 { where: { query.property } } 조건 객체를 전달하면 쿼리 스트링의 정보에 해당하는 리스폰스를 조회할 수 있습니다.

app.get("/api/members", async (req, res) => {
  const { team } = req.query;
  if (team) {
    const teamMembers = await Member.findAll({
      where: { team },
      order: [["admissionDate", "DESC"]],
    });
    res.send(teamMembers);
  } else {
    const teamMembers = await Member.findAll({
      order: [["admissionDate", "DESC"]],
    });
    res.send(teamMembers);
  }
});

order 옵션을 사용해서 정렬된 순서로 리스폰스를 받을 수도 있습니다.


11. 특정 직원 정보 조회하기 - findOne

app.get("/api/members/:id", async (req, res) => {
  const { id } = req.params;
  const member = await Member.findOne({ where: { id: id } });
  if (member) {
    res.send(member);
  } else {
    res.status(404).send({ message: "There is no such member" });
  }
});

Member.findOne() 메서드를 사용하여 id에 해당하는 멤버를 조회합니다. findOne() 메서드는 데이터베이스에서 데이터를 검색하고, 검색된 데이터 중 첫 번째 데이터를 반환합니다. where 옵션을 사용하여 검색 조건을 지정할 수 있습니다. id 파라미터와 일치하는 id 값을 가진 멤버를 조회합니다.


12. res.send()

res.send()는 express에서 제공하는 응답 객체(res)를 사용하여 클라이언트에게 HTTP 응답을 전송하는 메서드 중 하나입니다. 이 메서드는 아규먼트로 전달된 데이터를 클라이언트에게 전송합니다.

res.send() 메서드는 전달된 데이터의 형식에 따라 적절한 Content-Type을 자동으로 설정하여 응답합니다. 예를 들어, 전달된 데이터가 객체이면 객체 내부에 정의된 toJSON()이라는 메서드를 호출해서 JSON 형식으로 응답하고, 문자열이면 일반 텍스트 형식으로 응답합니다.


13. 새 직원 정보 추가하기

app.post("/api/members", async (req, res) => {
  const newMember = req.body;
  const member = Member.build(newMember);
  await member.save();
  res.send(member);
});

위 코드는 Express 애플리케이션에서 POST /api/members 요청을 처리하는 핸들러 코드입니다.

클라이언트에서 POST 요청을 보내면, 요청 본문에 있는 데이터를 새로운 Member 객체로 만들고, 데이터베이스에 저장한 후, 새로 생성된 Member 객체를 클라이언트에게 응답합니다.

req.body 객체는 Express에서 제공하는 Request 객체의 속성 중 하나로, HTTP 요청의 본문 데이터를 파싱하여 저장한 객체입니다. 따라서, 위 코드에서는 req.body를 사용하여 클라이언트에서 전달된 데이터를 읽어옵니다.

Member.build() 메서드를 사용하여 새로운 Member 객체를 생성하고, req.body 객체의 내용으로 초기화합니다. 이때, Member.build() 메서드는 데이터베이스에는 저장되지 않은 새로운 객체를 생성합니다.

이후, member.save() 메서드를 사용하여 새로 생성된 Member 객체를 데이터베이스에 저장합니다. save() 메서드는 비동기 메서드이므로, await 키워드를 사용하여 비동기적으로 데이터를 저장하도록 합니다.

마지막으로, res.send(member) 메서드를 사용하여 새로 생성된 Member 객체를 클라이언트에게 응답합니다. 이때, Content-Type 헤더는 "application/json"으로 설정됩니다.

app.post("/api/members", async (req, res) => {
  const newMember = req.body;
  const member = await Member.create(newMember);
  res.send(member);
});

create() 메서드는 build() 메서드와 save() 메서드를 한 번에 실행하는 것과 같은 동작을 합니다.


14. 기존 직원 정보 수정하기

app.put("/api/members/:id", async (req, res) => {
  const { id } = req.params;
  const newInfo = req.body;
  const result = await Member.update(newInfo, { where: { id } });
  if (result[0]) {
    res.send({ message: `${result[0]} row(s) affected` });
  } else {
    res.status(404).send({ message: "There is no member with the id!" });
  }
});

위 코드는 Sequelize의 update() 메서드를 사용하여 데이터베이스에 있는 Member 객체의 정보를 수정하는 코드입니다. update() 메서드는 인자로 전달된 데이터로 데이터베이스에 저장된 객체를 수정하고, 수정된 객체의 수를 반환합니다.

// test.http
###
PUT http://localhost:3000/api/members/1
Content-Type: application/json

{ "position": "Server Developer" }

이렇게 PUT 요청도 일반적으로 부분 업데이트(partial update)가 가능합니다. 따라서, 요청 본문에 수정하고자 하는 필드만 포함하는 경우, PUT 요청으로 보내도 정상적으로 기능합니다. 하지만 보다 명확한 RESTful API 설계를 위해서는 PUT과 PATCH를 올바른 용도에 맞게 사용하는 것이 좋습니다.


15. 직원 정보를 수정하는 또다른 방법

app.put("/api/members/:id", async (req, res) => {
  const { id } = req.params;
  const newInfo = req.body;
  const member = await Member.findOne({ where: { id } });
  if (member) {
    Object.keys(newInfo).forEach((prop) => {
      member[prop] = newInfo[prop];
    });
    await member.save();
    res.send(member);
  } else {
    res.status(404).send({ message: "There is no member with the id!" });
  }
});

Member 모델 클래스의 static 메소드인 update() 메소드를 사용하는 대신 해당 row를 조회하고, Member 모델 객체의 key를 돌면서 prop을 매핑해도 똑같은 동작을 수행할 수 있습니다.


16. 기존 직원 정보 삭제하기

app.delete("/api/members/:id", async (req, res) => {
  const { id } = req.params;
  const deleteCount = await Member.destroy({ where: { id } });
  if (deleteCount) {
    res.send({ message: `${deleteCount} row(s) deleted ` });
  } else {
    res.status(404).send({ message: "There is no member with the id!" });
  }
});

17. 직원 정보를 삭제하는 또다른 방법

app.delete('/api/members/:id', async (req, res) => {
  const { id } = req.params;
  const member = await Member.findOne({ where: { id } });
  if (member) {
    const result = await member.destroy();
    res.send({ message: `1 row(s) deleted` });
  } else {
    res.status(404).send({ message: 'There is no member with the id!' });
  }
});

Member 모델 클래스의 static 메소드인 destroy() 메소드를 사용하는 대신 해당 row를 조회하고 그에 대응하는 Member 모델 객체(인스턴스)의 메서드인 destroy 메소드를 사용해도 똑같은 동작을 수행할 수 있습니다.


Feedback

  • Object의 내장 메서드들이 많이 쓰일 것 같은데 따로 공부해서 정리해야겠다.
  • async-await도 제대로 이해 못하고 있는 것 같다. await을 한 번만 썼는데 밑에 있는 문장들도 비동기 실행되는 이유가 무엇일까?

Reference

profile
job's done

0개의 댓글