Sequelize Model 제네릭 타입 정의

SequelizeModel을 메서드의 매개변수등으로 전달 받거나 할때 특정한 하나의 Model이 아닌 여러 Model을 받아야 하는 경우가 있을 수 있습니다. 이 요구사항을 만족시키기 위해 Generic 타입을 지정하는 것은 보편적인 해결 방법입니다. 그렇다면 Sequelize Model의 타입은 어떻게 Generic Type으로 지정할 수 있는지 알아보겠습니다.

ModelCtor 타입

1
type ModelCtor<M extends Model = Model> = Repository<M>;

앞서 말한 케이스 같은 경우 ModelCtor 타입을 토대로 Generic 타입을 지정할 수 있습니다.
ModelCtor 타입의 정의는 위와 같고 “sequelize-typescript”에 정의되어있습니다.

예시를 통한 사용방법

아래와 같은 요구사항이 있다고 가정하고 예시 코드를 살펴 보겠습니다.

요구사항

  • 여러 Model과 Service에서 Pagination 기능을 포함하여야 한다.
  • 결과 값과 조건에 해당하는 총 rows의 수를 반환한다.

해결 방법

메서드에서 <T extends Model> 제네릭 타입을 선언한 후, 매개변수의 model의 타입을 ModelCtor<T>로 정의합니다.

코드의 재사용성을 높이기 위해 BaseService라는 Class를 작성하였습니다.
어떤 모델이던 아래의 코드를 통해 API 호출시 filter를 위한 쿼리 스트링을 sequelize where 객체로 변경하거나 페이지네이션을 위한 Offset을 매번 계산할 필요가 없습니다.

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 { Model, ModelCtor } from "sequelize-typescript";
import QueryBuilder from "./common/query.builder";

export class BaseService {
public async findAllAndCountWithPage<T extends Model>(
model: ModelCtor<T>,
searchQuery: { query; sort; page; pageSize }
): Promise<{ rows: T[]; count: number }> {
const { page, pageSize, sort, query } = searchQuery;

const offset = this.getPaginationOffset(page, pageSize);

return model.findAndCountAll({
where: query,
order: sort,
offset: +offset,
limit: +pageSize,
});
}

public buildSequelizeQuery(validKey, reqQuery) {
const query = QueryBuilder.buildFilterQuery(validKey, reqQuery);
const sort = QueryBuilder.buildSortQuery(reqQuery.sort);
const page = reqQuery.page || 1;
const pageSize = reqQuery.pageSize || 10;

return { query, sort, page, pageSize };
}

getPaginationOffset(page, pageSize) {
let offset = 0;

if (page > 1) {
offset = pageSize * (page - 1);
}

return offset;
}
}

요구사항을 만족하는 기능이 필요한 Service에서 BaseService를 상속받아 사용합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { Injectable } from "@nestjs/common";
import { BaseService } from "../common/service";
import { InjectModel } from "@nestjs/sequelize";
import { Book } from "./boook.model";

@Injectable()
export class BooksService extends BaseService {
constructor(@InjectModel(Book) private bookModel: typeof Book) {
super();
}

async getBooks(reqQuery) {
const VALID_KEY = ["title", "description"]; // 검색 가능한 column 필터링
const { query, sort, page, pageSize } = super.buildSequelizeQuery(VALID_KEY, reqQuery);
return super.findAllAndCountWithPage(this.bookModel, { query, sort, page, pageSize });
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { Controller, Get, Query } from "@nestjs/common";
import { BooksService } from "./books.service";
import { BaseController } from "../common/controller";

@Controller("books")
export class BooksController extends BaseController {
constructor(private booksService: BooksService) {
super();
}

@Get()
async getUsers(@Query() query) {
return this.booksService.getBooks(query);
}
}