ORM(Obeject relational mapping)은 어떻게든 SQL문을 최소한으로 작성하며 생산성을 높일지에 대한 고민이 담긴 기술로, 많은 언어에서 라이브러리 또는 프레임워크를 통해 지원한다. Java 진영에서는 Hibernate, Spring Data JPA를, Python에서는 Django ORM과 SQLAlchemy를, Ruby에서는 Active Record를 경험해봤다.
JavaScript, TypeScript 진영에서도 Sequelize, TypeORM 등을 찾을 수 있었다. NestJS에서는 기본적으로 RDBMS를 사용할 때 TypeORM을 지원하니 정리해봐야겠다는 생각이 들었다.
type에는 어떤 드라이버를 사용할지 명시해준다. init 시에 --database mysql을 썼기에 기본으로 mysql이 들어간다.
host, port, username, password, database는 데이터베이스의 접속 정보를 가지고 있는데, 우리는 위에서 root의 password를 password로 설정해줬으니 이에 맞게 설정한다. database는 MySQL 안에서 어떤 database를 쓸지에 대한 내용인데, photo_app으로 설정해두자.
synchronize는 우리가 작성할 Entity(Model)과 데이터베이스 테이블 상태를 동기화 할지에 대한 여부이다. 개발 시에는 true로 하되, production 환경 등에서 이미 있는 테이블을 사용한다거나 할때 반드시 false로 바꿔주자.
entities, migrations, subscribers는 TypeORM에서 사용할 Entity, Migration, Subscriber 파일을 관리하기 위해 경로 및 파일명 패턴을 정해준다.
잠깐 용어 정리
Entity는 데이터베이스의 테이블 스키마로 변환될 파일이다. 이후에 설명하겠지만, Photo, User, PhotoMetadata 등 하나의 테이블과 매칭될 .ts 파일이라고 생각하자.
Migration은 데이터베이스 테이블 스키마의 변경사항을 명시할 수 있다. synchronize가 true인 경우 쓰일 일은 잘 없지만, 나중에 테이블 구조를 변경하기 위해 migration 명령어를 통해 변경사항을 적용하거나 롤백할 수 있다.
Subscriber은 TypeORM에서 어떤 이벤트(데이터 삽입, 수정, 삭제 등)가 일어나면 특정 함수를 실행할 수 있도록 하는 파일이다.
package.json 살펴보기
init을 통해 프로젝트를 설저하게 되면 package.json에 필수 의존성들을 포함한 기본적인 설정들이 담긴다.
dependencies에 mysql이 추가되고, devDependencies에 @types/node, ts-node, typescript가 포함된 것을 확인할 수 있다. scripts의 경우 start를 할 때 ts-node src/index.ts를 해주는 부분이 추가되어있다. 우선 새로 추가된 의존성들을 설치해주고, 실행해보자.
$npmi$npmstartError: ER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication protocol requested by server; consider upgrading MySQL client
at Handshake.Sequence._packetToError (/Users/hodol/Development/typeorm-sample/node_modules/mysql/lib/protocol/sequences/Sequence.js:47:14)
at Handshake.ErrorPacket (/Users/hodol/Development/typeorm-sample/node_modules/mysql/lib/protocol/sequences/Handshake.js:123:18)
at Protocol._parsePacket (/Users/hodol/Development/typeorm-sample/node_modules/mysql/lib/protocol/Protocol.js:291:23)
atParser._parsePacket (/Users/hodol/Development/typeorm-sample/node_modules/mysql/lib/protocol/Parser.js:433:10)atParser.write (/Users/hodol/Development/typeorm-sample/node_modules/mysql/lib/protocol/Parser.js:43:10)atProtocol.write (/Users/hodol/Development/typeorm-sample/node_modules/mysql/lib/protocol/Protocol.js:38:16)atSocket.<anonymous> (/Users/hodol/Development/typeorm-sample/node_modules/mysql/lib/Connection.js:88:28)atSocket.<anonymous> (/Users/hodol/Development/typeorm-sample/node_modules/mysql/lib/Connection.js:526:10)atSocket.emit (events.js:315:20)atSocket.EventEmitter.emit (domain.js:467:12)--------------------atProtocol._enqueue (/Users/hodol/Development/typeorm-sample/node_modules/mysql/lib/protocol/Protocol.js:144:48)atProtocol.handshake (/Users/hodol/Development/typeorm-sample/node_modules/mysql/lib/protocol/Protocol.js:51:23)atPoolConnection.connect (/Users/hodol/Development/typeorm-sample/node_modules/mysql/lib/Connection.js:116:18)atPool.getConnection (/Users/hodol/Development/typeorm-sample/node_modules/mysql/lib/Pool.js:48:16)at/Users/hodol/Development/typeorm-sample/src/driver/mysql/MysqlDriver.ts:894:18atnewPromise (<anonymous>)atMysqlDriver.createPool (/Users/hodol/Development/typeorm-sample/src/driver/mysql/MysqlDriver.ts:891:16)atMysqlDriver.<anonymous> (/Users/hodol/Development/typeorm-sample/src/driver/mysql/MysqlDriver.ts:344:36)atstep (/Users/hodol/Development/typeorm-sample/node_modules/tslib/tslib.js:141:27)atObject.next (/Users/hodol/Development/typeorm-sample/node_modules/tslib/tslib.js:122:57) {code:'ER_NOT_SUPPORTED_AUTH_MODE',errno:1251,sqlMessage:'Client does not support authentication protocol requested by server; consider upgrading MySQL client',sqlState:'08004',fatal:true}
정상적으로 실행되지 않고 에러가 발생한다. 'Client does not support authentication protocol requested by server; consider upgrading MySQL client'를 보니 데이터베이스와 드라이버의 호환성 문제로 보인다. 메세지를 검색해보면 MySQL 8.x와 mysql 라이브러리를 사용한 커넥션에서 보안 관련 이슈가 있다는 것을 알 수 있다. 이를 간단히 해결하기 위해 mysql 의존성을 삭제하고, mysql2를 추가해주자.
에러 메세지가 없어지고, 정상적으로 실행된 것 처럼 보인다. 뭔가 User가 추가되었다는데, 실행된 파일인 src/index.ts를 살펴보자.
import"reflect-metadata";import {createConnection} from"typeorm";import {User} from"./entity/User";createConnection().then(async connection => {console.log("Inserting a new user into the database...");constuser=newUser();user.firstName ="Timber";user.lastName ="Saw";user.age =25;awaitconnection.manager.save(user);console.log("Saved a new user with id: "+user.id);console.log("Loading users from the database...");constusers=awaitconnection.manager.find(User);console.log("Loaded users: ", users);console.log("Here you can setup and run express/koa/any other framework.");}).catch(error =>console.log(error));
아직 모든 코드를 다 파악하기는 어렵지만, createConnection을 하고 .then() 내부의 콜백함수에서 new User()로 인스턴스를 만들어 속성을 정해주고 connection.manager.save(user), await connection.manager.find(User)을 실행하고 있는 것을 알 수 있다. 뭔가 User를 생성하고 저장한 것처럼 보이는데, 다시 MySQL 컨테이너로 돌아가서 이를 확인해보자.
mysql>USE photo_app;Reading table information for completion of tableand column namesYou can turn off this feature toget a quicker startup with-ADatabase changedmysql> SHOW TABLES;+---------------------+| Tables_in_photo_app |+---------------------+| user |+---------------------+1rowinset (0.00 sec)mysql>SELECT*FROM user;+----+-----------+----------+-----+| id | firstName | lastName | age |+----+-----------+----------+-----+| 1 | Timber | Saw | 25 |+----+-----------+----------+-----+1rowinset (0.00 sec)
SHOW TABLES를 통해 TypeORM이 MySQL에 접근해 CREATE TABLE user...을 대신 해준 것을 확인할 수 있다. 그리고 src/index.ts에서 봤던 인스턴스가 실제 데이터베이스에 잘 저장된 것을 확인할 수 있다.