TypeScript Connect Sequelize

Typescript

프로젝트를 TypeScript로 만들어보자는 의견을 수렴하여 TypeScript를 시작하게 되었다.
이번 프로젝트는 TypeScript와 DB는 mysql를 사용하고 Sequelize ORM를 사용하여서 프로젝트를 구성하였는데 Sequelize의 type을 정의해주는것이 까다로웠는데 아래에서 어떻게 구현했는지 보도록하자.
여기서 서버는 express를 사용하였다.

여기서 최종 dir구조를 보면 src 폴더안에는 ts file을 만들고 dist 폴더에는 ts file을 변환한 js file들을 넣어줄것이다.

method prototype this

우선 프로젝트에 필요한 파일들을 npm으로 설치하였다.

1
2
3
4
$ npm install express sequelize sequelize-cli mysql2 nodemon dotenv

// 개발용
$ npm install --save-dev typescript @types/express @types/mysql @types/sequelize @types/node
  • nodemon은 매번 서버를 껏다키는것을 방지하기 위하여 사용하였다.
  • dotenv는 민감한 정보를 .env에 보관하기위하여 사용하였다.

sequelize 기본설정

sequelize를 mysql로 연결하기위해서는 config파일 및 model 설정이 필요하다.

1
2
$ sequelize init:models
$ sequelize init:config

이렇게 하면 위에서 보았듯이 models, config 폴더가 생성된다.
config file은 .json형식으로 되어있지만 민감한 정보를 보호하기 위하여 .env를 사용할 것이다.

config.json => config.ts로 변경한 파일이다.

1
2
3
4
5
DB_HOST=127.0.0.1
DB_USER=~~
DB_PASS=~~
DB_NAME=~~
DB_Dialect=mysql

.env에 있는 파일을 받아오기위하여 import를 통하여 가져온다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import dotenv from "dotenv";
dotenv.config();

module.exports = {
"development": {
"username": process.env.DB_USER,
"password": process.env.DB_PASS,
"database": process.env.DB_NAME,
"host": process.env.DB_HOST,
"dialect": process.env.DB_Dialect,
"define": {
freezeTableName: true, // sequelize는 복수형으로 table을 만드는데 그것을 방지한다.
createdAt: 'created_at',
updatedAt: 'updated_at'
}
},
"test": {
"username": process.env.DB_USER,
"password": process.env.DB_PASS,
"database": process.env.DB_NAME,
"host": process.env.DB_HOST,
"dialect": process.env.DB_Dialect,
"define": {
freezeTableName: true,
createdAt: 'created_at',
updatedAt: 'updated_at'
}
},
"production": {
"username": process.env.DB_USER,
"password": process.env.DB_PASS,
"database": process.env.DB_NAME,
"host": process.env.DB_HOST,
"dialect": process.env.DB_Dialect,
"define": {
freezeTableName: true,
createdAt: 'created_at',
updatedAt: 'updated_at'
}
}
}

아래는 sequelize를 생성하는 순서이다.

  1. TableFactory를 생성한다.
  • TableFactory는 attribute와 instance를 합쳐서 Sequelize Model을 생성한다.
  1. sequelizeConfig를 정의한다.
  2. config에 들어온 값을 통하여 Sequelize Instance를 생성한다.
  3. Sequelize Instance, Sequelize, TableFactory를 사용하여 Sequelize와 Mysql을 연결한다.

TableFactory 생성

  1. UserAtttrbutes를 정의한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import Sequelize from "sequelize";
import { SequelizeAttributes } from "./index.d";

enum ApplicantStatus {
Applicant = "applicant",
PaperPass = "paper_pass",
InterviewPass= "interview_pass",
Fail = "fail",
Admin = "admin"
}

enum JobType {
Student = "Student",
Prepare = "Prepare",
Worker = "Worker"
}

enum PositionType {
Developer = "Developer",
Designer = "Designer"
}

export interface UserAttributes {
id?: number;
name?: string;
member_provider: string;
member_provider_number: string;
age?: number;
phone_number?: string;
email?: string;
job?: JobType;
position?: PositionType;
provide_image?: string;
status?: ApplicantStatus;
token: string;
created_at?: Date;
updated_at?: Date;
};

  1. UserInstance를 정의한다.

실제 데이터베이스 행에 대한 Sequelize Instance를 나타낸다.

  • ex) User.find({id: 1}) 함수는 User Model의 Instance를 반환한다.
    .validate, .save, .update 와 같은 Sequelize Instance Method가 추가된다.
    Instance class를 직접 Instance화 해서는 안되고 Model의 finder, creation Method를 사용해서 Instance Class에 접근한다.
1
2
3
4
5

export interface UserInstance extends Sequelize.Instance<UserAttributes>, UserAttributes {

}

  1. SequelizeAttributes 정의한다.
  • SequelizeAttributes를 직접 생성하여서 우리가 UserAttributes에서 지정한 모든 속성에 대하여 정의가 있는 객체를 sequelize.define으로 전달하기 위하여 생성한다.
1
2
3
4
5
6
7
8
// src/models/index.d.ts
import Sequelize from "sequelize";

type SequelizeAttribute = string | Sequelize.DataTypeAbstract | Sequelize.DefineAttributeColumnOptions;

export type SequelizeAttributes<T extends { [key:string]: any}> = {
[p in keyof T] : SequelizeAttribute;
};
  1. UserFactory 함수를 정의한다.
    실제로 User Table을 위한 Sequelize Model을 정의할 함수를 생성한다.
    Model은 이미 정의한 UserAttribute, UserInstance와 type이 일치해야한다.
    일반적인 Sequelize에서는 Sequelize Instance와 DataType을 통하여 Sequelize 객체를 생성한다.
    TypeScript에서는 Sequelize Model을 정의하기 위하여 Sequelize.define<TInstance, TAttribute>('tablesname', attributes) 함수를 사용한다.
    이 함수는 생성할 열을 지정하는 객체를 사용한다.
  • Sequelize.Model은 db의 table을 나타낸다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

export const UserFactory = (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes): Sequelize.Model<UserInstance,UserAttributes> => {
const attributes: SequelizeAttributes<UserAttributes> = {
name: {
type: DataTypes.STRING
},
member_provider: {
type: DataTypes.STRING
},
member_provider_number: {
type: DataTypes.STRING
},
age: {
type: DataTypes.INTEGER
},
phone_number: {
type: DataTypes.STRING
},
email: {
type: DataTypes.STRING
},
job: {
type: DataTypes.ENUM(JobType.Student, JobType.Prepare, JobType.Worker)
},
position: {
type: DataTypes.ENUM(PositionType.Developer, PositionType.Designer)
},
provide_image: {
type: DataTypes.STRING
},
status: {
type: DataTypes.ENUM(ApplicantStatus.Applicant, ApplicantStatus.PaperPass, ApplicantStatus.InterviewPass, ApplicantStatus.Fail, ApplicantStatus.Admin)
},
token: {
type: DataTypes.STRING
}
};
const User = sequelize.define<UserInstance,UserAttributes>('User', attributes);

return User;
};
  1. createModels Function을 만든다.

이 함수는 디렉토리 내의 모든 파일들과 함께 sequelize.import를 호출한다. 이것이 바로 model file이다.
이 방식에서 형식검사를 하기위하여 DbInterface를 생성한다.
그런 다음 createModels는 Sequelize Model이 붙은 DbInterface Type의 객체 db를 반환한다.

중요 : DbInterface에서 정의 된 모델과 각 모델에 어떤 attribute, method가 있는지를 명시적으로 정의한다.
그런 다음 db Object를 API route handlers에 전달할 수 있다. 이 handler는 model을 사용하여 Sequelize Query를 만들 수 있다.
최종적으로 db안에 들어갈 값들의 interface를 정의한다.

1
2
3
4
5
6
7
8
9
10
11
// src/db/DbInterface/index.d.ts

import * as Sequelize from "sequelize";
import { UserAttributes, UserInstance} from "../../models/user";

export interface DbInterface {
sequelize: Sequelize.Sequelize;
Sequelize: Sequelize.SequelizeStatic;
User: Sequelize.Model<UserInstance,UserAttributes>;
}

DbInterface를 상속받아서 정의한 값들을 넣어주고 최종적으로 db를 return 해준다.

  • 추후에 db.User.findOne()이런식으로 값을 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// src/models/index.ts
import Sequelize from "sequelize";
import { DbInterface } from "../db/DbInterface/index";
import { UserFactory } from "./user";

export const createModels = (sequelizeConfig: any): DbInterface => {

const { database, username, password, host, dialect, define } = sequelizeConfig;
const sequelize = new Sequelize(database, username, password, {host, dialect, define})
const db: DbInterface = {
sequelize,
Sequelize,
User: UserFactory(sequelize, Sequelize)
};
return db;
}

이제 어떻게 사용할까?

Q. Factory와 Interface를 정의하였지만 어떻게 DB에 연결할까???
A. src/models/index.ts에다 구현할거야! 이 파일은 Sequelize Config를 가져와서 우리가 정의한 Model을 생성 할 함수 createModels를 내보낼것이야!
그 다음 app.ts에서 이 함수를 가져와서 sync할 것입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// src/app.ts
import dotenv from "dotenv";
dotenv.config();

import express from "express";
import bodyParser from "body-parser";
import swaggerUI from "swagger-ui-express";

import user from "./api/user/index";
import auth from "./api/auth/index";
import mail from "./api/mail/index";
import resume from "./api/resume/index";
import evaluation from "./api/evaluation/index";
import form from "./api/form/index";
import { createModels } from "./models/index";
import { swaggerSpec } from "./swagger";

const env = process.env.NODE_ENV || 'development';
const config = require("./config/config")[env];
const port = process.env.PORT || 3000;
const app = express();
export const db = createModels(config);

app.set('view engine', 'pug')

app.get('/', (req: express.Request, res: express.Response) => {
res.send('success router');
});

const options = {
force: process.env.NODE_ENV === 'test' ? true : false
}

db.sequelize.sync(options)
.then(() => {
console.log('Sequelize Sync Success')
app.listen({port}, () => {
console.log(`${port} Sever Start`);
console.log()
});
})



Share