[node] 데이터베이스 적용

웅평·2023년 7월 26일
0

mysql를 이용하여 데이터베이스를 연결해보자

데이터베이스 생성

install mysql2 sequelize sequelize-cli

sequelize를 설치

npx sequelize init

명령어를 실행하면 config, models, migrations, seeders 디렉토리가 생긴다

config

  • configuration의 줄임말, 데이터베이스 접속에 관한 각종 설정들이 들어있다

  • 개발용, 서비스용, 테스용으로 3가지 종류의 설정 정보가 있다

  • MySQL 설치했던 username과 비밀번호를 development에 입력하고 데이터베이스 이름은 원하는 이름으로 설정한다

migrations

  • 데이터베이스 내부에서 일어나는 모든 변경 사항
npx sequelize db:create --env development

실행을 하면 데이터베이스가 생성된다

모델과 테이블 생성

  • sequelize에서는 데이터베이스에 존재하는 하나의 테이블이 자바스크립트에서 하나의 클래스에 대응된다
  • 이 클래스로 만든 객체는 해당 테이블에서의 하나의 row에 해당
npx sequelize model:generate --name Member --attributes name:string,team:string,position:string,emailAddress:string,phoneNumber:string,admissionDate:date,birthday:date,profileImage:string
  • 모델을 생성하고 모델의 이름은 Member로 설정
  • 데이터 타입 설정
  • id는 자동으로 설정

다양한 데이터 타입 : Sequelize 공식 페이지

migrations디렉토리 내부 파일

'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
      },
      // ...
    });
  },
  async down(queryInterface, Sequelize) {
    await queryInterface.dropTable('Members');
  }
};

up 메소드는 migration을 적용할 때 실행
down 메소드는 migration을 적용 해제할 떄 실행

createTable은 테이블 생성 메소드인데 아까 Member라고 이름을 붙였는데 Members 복수형이 되어있다. 이것은 sequelize의 특징은 복수형으로 변환해서 짓는다

migrations 적용

npx sequelize db:migrate

디렉토리의 모든 migration 파일들을 적용하라는 뜻
데이터베이스 안에 테이블이 생성됨

테이블 삭제

npx sequelize db:migrate:undo

가장 최근에 적용된 migration을 해제

seed 데이터 넣기

테이블에 처음 놓는 데이터를 시드(seed) 데이터라고 한다.

npx sequelize seed:generate --name initialMembers

seed 데이터를 추가하는 내용을 담은 파일을 생성하고 그 파일 이름을 initialMembers로 설정
seeders 디렉토리에 js파일이 생성된다

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',
      },
    ]);
  },
  
  // members 테이블 모든 row 삭제
  down: async  (queryInterface, Sequelize) => {
    await queryInterface.bulkDelete('Members', null, {});
  },
};
npx sequelize db:seed:all

seeders 디렉토리 안에 seeder 파일이 모두 적용된다
Members테이블에 정보가 들어간다

모델과 테이블 연동

models 디렉토리의 members.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;
};

init 부분이 모델과 테이블을 연동하는 코드이다.
같은 이름의 프로퍼티를 설정해주지 않으면 모델이 해당 column을 인식할 수 없다

두 번째 객체에 sequelize가 있는데 sequelize: sequelize 코드와 같은 뜻인데 sequelize객체는 이 전체 함수의 파라미터로 전달될 sequelize객체를 의미

sequelize란 객체를 생성해서 이 함수에 넘겨 줄 건데 그 객체가 들어와서 Member 모델이 실제 데이터베이스에 존재하는 Members 테이블을 인식하도록 하는데 사용

Primary key로 사용할 id column에 대응될 id 프로퍼티가 없는데 직접 입력할것이기 때문에 코드를 추가했다

sequelize객체를 생성해서 함수에 넣어보자
models 디렉토리의 index.js 파일로 가보자

// sequelize 클래스 외부에 공개
const Sequelize = require('sequelize');

// config 디렉토리의 config.json에서 설정한 내용 가져온것
const config = require('../config/config.json');

const {
  username, password, database, host, dialect,
} = config.development;

// sequelize객체 생성
const sequelize = new Sequelize(database, username, password, {
  host,
  dialect,
});

// member.js에 있던 함수 가져오기
// Member 모델은 Members 테이블과 연동
const Member = require('./member')(sequelize, Sequelize.DataTypes);

// db 객체안에 Member 모델을 넣어서 공유
const db = {};
db.Member = Member;

module.exports = db;

Member 모델과 Members 테이블이 연동되었기 때문에 원하는 작업이 이제 가능하다

app.js

// model 디렉토리에 있던 index.js에서 db 객체 가져옴
const db = require('./models');
const { Member } = db;

기존에 있던 정보를 가져오던 members를 지우고 코드 수정

정보 조회

// 익스프레스 패키지에서 공개하는 것을 가져오기
const express = require('express');

// model 디렉토리에 있던 index.js에서 db 객체 가져옴
const db = require('./models');
const { Member } = db;

// 함수 외부로 공개(실행된 함수는 하나의 객체를 리턴)
const app = express();

app.use(express.json());

// 전체 직원 정보 조회, 만약 쿼리가 존재하면 특정 팀만 조회
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 members = await Member.findAll();
    res.send(members);
  }
});


// 포트 번호가 3000에서 실행
app.listen(3000);

코드를 고치고 test.http가서 GET 리퀘스트를 보내면 터미널에 내용이 출련된다

Member.findAll() 이라는 코드는 sequelize에 의해서 SQL문으로 변환되어서 DBMS에 전송된다. 이렇게 코드가 SQL문으로 자동 변환되어 실행되는 것이 ORM이다

await를 쓰지 않으면 빈 객체를 리턴하는데 그 이유는 모델이 갖고 있는 대부분 메소드들은 프로미스 객체를 리턴하는 비동기 실행 함수이다.

await을 쓰려면 async가 붙은 함수 안에서만 사용 가능하다

{ where: { team } }은 조건을 나타내는 객체이다

Member.findAll({ where: { team } })를 보면
where라는 프로퍼티에 특정 column의 이름과 특정 값을 적으면 특정 column의 특정 값을 가진 row만 조회할 수 있다.

특정 정보 조회

// 특정 직원 정보 조회
app.get('/api/members/:id', async (req, res) => {
  const { id } = req.params;
  const member = await Member.findOne({ where: { id } });
  if (member) {
    res.send(member);
  } else {
    res.status(404).send({ message: 'There is no such member!!' });
  }
});

findOne은 조건에 맞는 데이터들이 존재하도 가장 첫 번째 row만 조회한다

GET http://localhost:3000/api/members/1

정보 추가

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

build는 하나의 Member모델 객체를 생성하고 리턴한다
이 객체는 테이블에서 하나의 row가 된다

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

create 메소드를 통해서 같은 기능을 구현할 수 있다.
build + save 기능을 같이한다

정보 수정

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

update(수정할 내용, 수정할 row 특정하기 위한 조건)
update메소드가 리턴하는 프로미스 객체에는 배열을 리턴한다
배열의 첫 번째 요소에는 수정된 row가 리턴된다

위 코드는 하나만 수정하기 때문에 수정한다면 첫 번째 요소는 1이 될것이고 없다면 0이 될것이다

test.http

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

{
    "id": 1,
    "name": "somsom",
    "team": "engineering",
    "position": "IOS Developer",
    "emailAddress": "alex@google.com",
    "phoneNumber": "010-xxxx-xxxx",
    "admissionDate": "2018/12/10",
    "birthday": "1994/11/08",
    "profileImage": "profile1.png"
}

이런식으로 수정을 해도 되지만

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

{
    "name": "kimkim"
}

수정하고 싶은 부분만 설정해도 된다

또다른 수정 방법

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({ message: `수정`})
  } else {
    res.status(404).send({ message: "There is no Member" });
  }
});

findOne를 통해서 수정할 row를 가져오고 맴버 객체의 프로퍼티의 값을 리퀘스트의 바디에 있던 새 객체의 프로퍼티의 값으로 수정 후 저장한다

정보 삭제

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} 'Deleted'` });
  } else {
    res.status(404).send({ message: 'Threr is no member' });
  }
});

비동기 함수인 destroy 이용해서 삭제가 가능하다

모델 객체 메소드 참고 : https://sequelize.org/api/v6/class/src/model.js~model

참고
코드잇

0개의 댓글