Photo by bert sz on Unsplash

[번역] 마이그레이션과 Sequelize-CLI 튜토리얼

Stark Studio
Prisma Korea
Published in
20 min readMar 1, 2020

--

해당 글은 Anders Magnus Andersen님의 Sequelize/-CLI With Migrations 글을 허락받고 번역한 글입니다.

Sequelize CLI 문서에서 마주하는 다양한 문제를 보완해, 좀 더 쉬운 getting started(시퀄라이즈를 시작하는 방법)에 대한 글을 써보려합니다.

GitHub(깃허브 주소): sequelize_getting_started

Python(파이썬)이나 Javascript(자바스크립트) 같은 프로그래밍 언어를 사용하고 있다면, 머지않아 데이터 베이스를 조작해야할 것입니다. 만약 Python 개발자라면, SQLAlchemyDjango’s ORM 같은 ORM(Object-Relational-Management)을 이미 사용해봤을지 모릅니다.

ORM은 Object-Relational Management의 약자로, MySQL, MsSQL 등 등의 데이터베이스와 데이터베이스 안의 테이블을 프로그래밍 할 때 객체 처럼 쓸 수 있게 해주는 방식입니다.

만약 Javascript를 배우고 있고, 데이터베이스에 대해 조사를 하고 있다면, ORM이란 단어를 보게될 것이고, 곧 Sequelize(시퀄라이즈)에 대해 자주 접하게 될 것입니다.

Python의 Django(장고)를 이미 써보신 분들은 알겠지만, ORM을 통해 쉽게 데이터베이스 마이그레이션을 진행할 수 있습니다. 혹시 아직 ORM에 대한 지식이 없다면 아마 앞으로 수도 없이 테이블을 지우는 작업을 알게 될 것 입니다.

개인적으로, 아직 Django 프레임워크와 비슷한 시스템을 찾지 못했지만, Sequelize도 어느정도 발전했고 ORM에 요구되는 다양한 기능을 지원합니다. Sequelize를 사용하면 좀 더 많은 작업(코드량)이 필요하지만, 다양한 상황에 맞게 쓸 수 있기 때문에 유용합니다.

해당 글에서는 각 각의 ORM을 비교하는 것이 목적이 아니기 때문에, 해당 부분에 대한 것은 스스로 찾아보는 것이 좋을 것 같습니다. 이 글의 목적은 Sequelize를 시작하며 헷갈리는 부분에 대한 이해를 돕고자 쓰였습니다.

튜토리얼 시작

해당 글은 읽는 사람이 다음에 대해 충분히 이해한다고 가정합니다.

  • Node.js와 NPM 사용법에 대해 익숙한 사람
  • Javascript의 기본 지식이 충분한 사람
  • ORM의 컨셉에 대해 충분히 아는 사람

해당 튜토리얼은 Sequelize CLI 문서의 마이그레이션(Migration) 문서를 통해 진행할 것입니다. 이 글을 통해서 다른 문서나 블로그 글, 칼럼들을 복잡하게 찾지 않고, Sequelize 마이그레이션을 진행하는데 충분한 지식을 얻어 가길 바랍니다. (운이 좋게도 저는 엄청 큰 스크린을 사용하고 있어, 이 부분에서 좀 더 편했습니다.)

물론, Sequelize의 공식 문서가 나쁜건 아니지만, 조금 더 쉽게 사용하길 바라며 이 글을 작성합니다.

NB: Some of these commands will fail!

I recommend you follow along typing out (or pasting…) the commands and code as I present it. This way you’ll experience the “pitfalls” first hand and hopefully it will stick better.
I will not explain every command and you are expected to look up functions / commands if you dont understand what they to. However, most (99%) are pretty self-explaining. (Options not mentioned are many, but easy to find later)

먼저, 튜토리얼 관련된 디렉토리를 설정하고, npm 패키지를 초기화하며 Sequelize CLI를 설치하고 어떤 데이터베이스를 사용할 지 선택합시다. 어떤 데이터베이스와 패키지를 사용할 수 있을지는 여기에서 확인할 수 있습니다. 해당 튜토리얼에서는 SQLite를 사용할 것입니다.

$ npm init -y

만약, 루트 디렉토리에 모든 소스 코드를 보관하고 싶지 않다면, /src 디렉토리를 생성합니다. 해당 튜토리얼에서는 다른 프레임워크를 사용하지 않을 것이기 때문에, index.js 파일을 src 디렉토리에 생성합니다.

먼저 index.js 에 다음과 같은 코드를 간단하게 추가합니다.

console.log("Hello World!")

이를 통해 우리의 토이 프로젝트의 구조는 다음과 같아질 것입니다.

.
├── package.json
├── package-lock.json
└── src
└── index.js

현재의 프로그램은, 한 줄을 불러오는 것이 전부다. 앞으로 이런 줄을 데이터베이스로 부터 불러올 것입니다.

이 프로그램은 데이터베이스를 설정할 준비가 끝났습니다. 이제 Seqeuelize-CLI와 데이터베이스 엔진을 설치할 것입니다.

$npm i sequelize sequelize-cli sqlite3

이제, 우리는 sequeliz와 sequelize-cli를 모두 설치했고, npm executor와 함께 sequelize-cli를 사용할 수 있을 것 입니다. 앞으로 남은 일은 프로젝트를 초기화 하는 일입니다.

아래 명령어를 입력해야 합니다.

$npx sequelize init

이후 다시 프로젝트 디렉토리를 확인하면

.
├── config
│ └── config.json
├── migrations
├── models
│ └── index.js
├── package.json
├── package-lock.json
├── seeders
└── src
└── index.js

아쉽게도 이건 약간, 더러운 폴더 구조를 만듭니다.

Sequelize는 이를 해결하기 위해 .sequelizerc 파일을 프로젝트의 root에 추가 할 수 있습니다. 앞서 생성된 파일과 폴더를 모두 삭제하고, 원래의 프로젝트 구조로 남깁니다.

그리고, .sequelizerc 파일을 root에 아래와 같이 추가합니다.

.js 확장자를 빼먹지 마세요, 이 파일에 plain javascript를 쓸 수 있습니다.

이제 다시 init을 합니다.

$ npx sequelize init

그리고 프로젝트를 다시 봅니다.

.
├── package.json
├── package-lock.json
└── src
├── db
│ ├── config
│ │ └── config.js
│ ├── migrations
│ ├── models
│ │ └── index.js
│ └── seeders
└── index.js

만약 이 구조가 마음에 안들어도, 어쩔 수 없습니다. config.js 파일을 확인해봅시다. 아래와 같이 보일 것입니다.

{
"development": {
"username": "root",
"password": null,
"database": "database_development",
"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"
}
}

보이는 것 처럼, Javascript가 아닌 JSON 형태일 것입니다. 아래와 같이 .sequelizerc안의 내용물을 변경해봅시다.

config 파일은 .json 이나 .js 확장자를 가질 수 있습니다.

이 부분은 Javascript(ES6)에서 객체를 내보내는 방식과 비슷하며 이를 통해 데이터 베이스를 Javascript를 이용해 조작할 수 있게 될 것 입니다. 데이터베이스는 Root 디렉토리에 저장될 것입니다.

실제 Production 환경은 해당 튜토리얼과는 다른 환경일 것이지만 위와 같은 설정을 모두 해보는 것이 Sequelize를 실제로 도입할 때 좀 더 도움이 될 것이라고 생각합니다.

만약 NODE_ENVproduction 이나 text 와 같이 설정해주지 않으면, Sequelize는 이를 development 로 가정합니다.

이제 Seqeulize의 기본 설정이 끝났습니다. 이제 모델에 대한 부분을 구성해봅시다.

데이터베이스 모델링

해당 튜토리얼는 좀 보편적인 User/Task 모델 구현을 진행할 것입니다. 이 부분이 관계(Relationship)을 보여주기 좋을 것입니다. 이를 통해 어떻게 User Model을 만들고, .hasMany (one-to-many)키워드를 통해 Task Model과 연결하며, Task Model을 .belongTo (one-to-one)를 통해 연관(Relationship) 을 만드는 것을 진행할 것입니다.

[개인적 생각]
Sequelize는 기본적으로 RDB(Relational Database)를 코드 단에서 사용하기 쉽게 해주는 ORM 툴 입니다. 따라서, RDB의 1:1, 1:N, N:M의 연관(관계)에 대해서 별도로 알아두는 것이 좋습니다.

BelongsTo 연관(관계)는 기존의 모델과 1:1 관계를 위해 foreign key(외래키)를 지원하는 Associations입니다.
One-To-Many(hasMany) 연관(관계)는 1:N 관계를 위한 Associations입니다. 하나의 모델은 반드시 다른 하나의 모델에 반드시 연결되어야 합니다.

만약, 이 튜토리얼을 끝냈을 때 쯤 SequelizeAPI reference을 읽으며, 다양한 관계와 옵션을 확인하여 이 글을 읽으시는 분이 원하는 모델을 구현할 수 있기를 바랍니다.

보통 우리는 어떤 모델이 필요하고, 어떤 관계가 요구 되는지 미리 알 것입니다. 그래서 우리는 이것이 미리 계획 되었다고 가정할 것 입니다. 만약, 이런 부분이 처음이라면, 걱정하지 마세요 이 과정을 따라오다보면 이해가 될 것입니다.

해당 튜토리얼의 모델은 실제로 사용하기 위함이 아니고 Sequelize의 기능을 보여주기 위해 가능한 단순한 구조입니다.

Task Model부터 시작해봅시다.

$npx sequelize model:generate --name Task --attributes
taskName: string

보시다시피 생각보다 간단하게 끝났습니다. 이제 User Model을 마저 만들어봅시다.

$npx sequelize model:generate --name User --attributes name:string

Model의 속성(Attributes)과 기본 길이 및 다른 설정은 해당 documentation을 통해 제한할 수 있습니다.

Seqeulize cli는 models(src/db/models)을 생성하고 데이터베이스를 생성하고 업데이트하는 migration files(src/db/migrations)이 저장될 것 입니다.

이제 프로젝트 구조를 다시 봅시다.

.
├── package.json
├── package-lock.json
└── src
├── db
│ ├── config
│ │ └── config.js
│ ├── migrations
│ │ ├── 20190611175237-create-task.js
│ │ └── 20190611175246-create-user.js
│ ├── models
│ │ ├── index.js
│ │ ├── task.js
│ │ └── user.js
│ └── seeders
└── index.js

생성한 Models은 src/db/models 폴더 안에 저장되고, migrations는 src/db/migrations 폴더 안에 저장될 것입니다. Migrations파일 앞에 붙은 숫자는 시간에 대한 정보로 sequelize는 이를 인식해 어떤 마이그레션이 먼저이고, 어떤 순서로 생성되어야 할 지에 대해 판단합니다. (지금은 별로 중요하지 않지만, 나주엥 columns과 tables를 추가할 때 중요합니다.) 이제 src/db/seeders 에 대해 알아봅시다. Sequlize는 우리 데이터베이스에 seeding(더미 데이터 넣기)하는 방법을 제공합니다.

이제 우리의 데이터베이스를 생성하고, 마이그레이션 해봅시다.

$npx sequelize db:migrate

우리는 이제 db.sqlite 파일을 우리의 테이블을 포함한 root 디렉토리에 가지게 될 것입니다. 우리의 데이터베이스가 비었기에 seed파일을 통해 이를 해결해봅시다.

$npx sequelize seed:generate --name task

이제 우리는 새로운 파일을src/seeders 폴더 안에 가질 것입니다. {dateTime}-task.js 파일을 열면서 진행해봅시다.

NB: “Tasks” 라는 복수형으로 명시한 것을 파악하세요. 이는 Model이 아닌 실제 Table을 참조하고 있기 때문입니다. Migrations 파일도 같은 문법을 가지기에 처음 사용하시는 분들에게 혼란을 줄 수 있습니다.

[개인적 생각]
ORM에서 Models은 코드 단에서 제공하는 데이터베이스의 모델 객체를 말하고, Table은 실질적으로 데이터베이스 테이블을 의미합니다.

배열로 된 tasks를 추가했습니다.

현재로서, User에 Task를 할당하는 것이 의미가 없기 때문에 Seed에 집중할 것입니다. Seed 할 준비를 하고 우리 데이터베이스에 더미를 채웁시다.

$npx sequelize db:seed:all

db:seed:all 은 모든 src/db/seeds 안의 모든 seeds를 실행하는 명령어입니다.

ERROR: Validation error

Seed가 실패했습니다. 우리는 가이드를 따랐지만, 별로 도움이 되지 않았습니다.

요약: 이는 Seeding 되는 데이터가 우리의 데이터베이스 속성과 맞지 않는다는 것을 말합니다. 빠르게 데이터베이스를 확인해 봅시다.
(이 과정을 직접 할 필요는 없습니다.)

$ sqlite3 db.sqlite 
SQLite version 3.22.0 2018-01-22 18:45:57
Enter ".help" for usage hints.
sqlite> .tables
SequelizeMeta Tasks Users
sqlite> .schema Users
CREATE TABLE `Users` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` VARCHAR(255), `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL);
sqlite> .schema Tasks
CREATE TABLE `Tasks` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `taskName` VARCHAR(255), `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL);
sqlite> SELECT * FROM SequelizeMeta;
20190611175237-create-task.js
20190611175246-create-user.js
sqlite>

보시다시피, Sqlite 테이블에 마이그레이션을 추적하는 SeqeulizeMeta 데이터를 확인할 수 있습니다. 이때 Sequelize에서 createdAt과 updatedAt의 추가적인 2개의 컬럼을 Users 테이블과 Tasks 테이블에 추가한 것을 볼 수 있습니다. 이는 Sequelize에서 자동적으로 추가하는 것입니다. Seeding을 위해 이를 별도로 추가해줘야 합니다.

만약, createdAt과 updatedAt을 지우고 싶으시면, 첫 마이그레이션 이전에 모모델 마이그레이션 파일을 통해 이를 지울 수 있습니다.
하지만, 이미 모델이 생성되었다면, 다른 마이그레이션에서 진행하면 됩니다.

createdAt과 updatedAt을 seed migration 파일에 추가해줍니다.

이제 Seeds를 다시 실행해봅시다.

$ npx sequelize db:seed:allSequelize CLI [Node: 11.15.0, CLI: 5.5.0, ORM: 5.8.9]
Loaded configuration file "src/db/config/config.js".
Using environment "development".
== 20190611182947-task: migrating =======
== 20190611182947-task: migrated (0.030s)
== 20190611182953-user: migrating =======
ERROR: Migration 20190611182953-user.js (or wrapper) didn't return a promise

우리의 데이터베이스가 이제 3개의 tasks를 생성했습니다.

마지막으로 src/index.js 파일에 몇 가지 추가해 새롭게 Seed 된 데이터베이스에서 데이터에 접근하거나 불러올 수 있게되었습니다

getTaskPromise()getTasksAsync() 는 같은 함수입니다. 저는 async/await 문법을 더 선호하지만, 모두 그러는 것은 아니기에 Promise와 async/await 모두 써보았습니다.

위에서 우리는 Seqeulize CLI에서 제공하는 src/db/models/index.js 파일을 import 했습니다. 이를 통해 우리에게 db.Taskdb.User API를 제공해줄 것 입니다.

db.Task.findAll() 은 우리의 Tasks 객체를 배열로 반환하는 Promise입니다. 여기서 할 것은 이 배열을 반복하여 taskName 속성을 출력하는 것입니다.

프로그램을 실행해 봅시다.

$ node src/index.js 
Executing (default): SELECT `id`, `taskName`, `createdAt`, `updatedAt` FROM `Tasks` AS `Task`;
Executing (default): SELECT `id`, `taskName`, `createdAt`, `updatedAt` FROM `Tasks` AS `Task`;
This is task number one!
This is task number two!
This is task number three!
This is task number one!
This is task number two!
This is task number three!

드디어, 데이터를 console을 통해 출력했습니다. 위 쪽에 보이는 로그는 Sequelize에서 작업을 하기 위한 쿼리입니다. 이는 production 모드를 위해config.js파일에서logging: false 옵션을 통해 끌 수 있습니다.

현재 우리는 서로 연관되지 않아 별로 유용하지 않은 두 모델을 가질 뿐입니다. 즉, tasks와 각각의 users를 연결해야 합니다. 이 부분은 Association 부분에서 진행하겠습니다.

[개인적인 생각]

RDBMS에서는 Association이 중요합니다. 데이터를 하나의 테이블에 모두 보관하는 것이 아니라, 여러 모델을 만들고 그 사이의 관계를 통해 데이터를 다루는 방법을 권유합니다.

Associations 관계 구성

슬프게도 우리는 Association을 Sequelize CLI 인터페이스를 통해서 추가할 수 없습니다. CLI를 통해 생성된 models을 수정할 필요가 있습니다.

/db/models/task.js 파이를 열고 belongsTo Association을 추가합시다.

보시다시피, 별거 없습니다. 그저 코드 한 줄을 추가하고, Association을 추가할 수 있습니다.

여기에 보이는 코드는 Task 모델에 없는 필드(Column)을 말하는 foreignKey: "userId" 부분을 제외하고는 따로 설명이 필요 없을 것 같습니다. 이 부분에는 User’s ID (Primary Key, 기본키)이 저장될 것 입니다.

이제 User 모델 부분인 db/models/user.js 에 hasMany를 추가하는 것을 진행해봅시다.

이제 Association과 함께 변경된 Models을 가질 것입니다. 하지만 그 전에 Tasks 테이블을 변경하고 userId 필드(Column)를 추가해주어야 합니다. 다음 migrations 파일을 생성해봅시다.

$ npx sequelize migration:generate --name add-Task-userId-column

새로운 마이그레이션 파일 {dateTime}-add-Task-UserId-column.js 파일을 열고 새로운 필드(Column)를 추가해봅시다.

우리가 데이터 베이스 테이블을 참조할 때 복수형을 쓰는 것을 기억합시다.

끝났습니다. 이제 우리는 Sequelize CLI 을 통해 마이그레이션을 추적할 수 있습니다. 물론, 마이그레이션이 잘못 됐을 경우에 db:migrate:undo 명령어를 통해 복구할 수 있습니다. 한번 적용해 보겠습니다.

$ npx sequelize db:migrateSequelize CLI [Node: 11.15.0, CLI: 5.5.0, ORM: 5.8.9]Loaded configuration file "src/db/config/config.js".
Using environment "development".
== 20190612212617-add-Task-userId-column: migrating =======
== 20190612212617-add-Task-userId-column: migrated (0.056s)

이제 우리는 마이그레이션과 모델 생성을 모두 끝냈습니다. 남은 일은 이제 잘 사용하는 것 뿐입니다. 마무리 부분에서 이를 다루겠습니다.

마무리

몇 가지 기본적인 Model 메소드를 src/index.js 파일에 추가함으로써 마무리 하도록 합시다.

Sequelize API를 이용한 기본적인 호출을 작성하였습니다. 물론 더 많은 유용한 API가 문서에 있습니다.

  • belongsTo는 우리에게 .set<Model> 메소드를 줍니다.
  • hasMany는 우리에게 .get<Model>s 메소드를 줍니다.
  • 추가적으로 더 많습니다.

Associations에서 더 많은 것을 찾을 수 있습니다.

혹시 이 모든 셋팅과 준비가 필요한 지 의문이 들 수 있습니다. 물론 그렇지 않습니다. 모든 것을 하나의 파일에 작성할 수 있습니다. 하지만 그럴 경우 마이그레이션과 복구 옵션이나 다른 것을 사용할 수 없을 것입니다. 여기에 Sequelize CLI 없이 Javascript만을 이용해 Sequelize를 이용하는 좋은 글이 있습니다.

Do it all in JS

만약, 다른 가이드나 글을 쉽게 이해할 만큼 Sequelize에 대해 충분히 이해 했다면, 위의 글을 읽는 것을 바랍니다. 이는 모든 것을 Javascript로 셋팅하는 좋은 시작점이 될 것 입니다.

이 글을 통해 새로운 것을 많이 얻어 가길 바라며, 편하게 댓글이나 질문을 남겨주세요.

--

--