프로젝트를 구현할 때 db에 초기 데이터가 필요한 경우들이 있습니다.
초기 관리자 계정, 테스트를 위한 더미 데이터 등..
이렇게 db에 더미 데이터를 미리 삽입하는 것을 seeding이라 합니다.
매번 다른 분들의 블로그 글을 참고하며 seeding을 했었는데, 이번 기회에 직접 정리해보려 합니다.
저는 seeding을 위해 typeorm-extension 라이브러리를 사용하겠습니다.
(seeder 라이브러리로 typeorm-seeding, nestjs-seeder 라이브러리가 있었지만, 두 라이브러리 모두 현재 업데이트가 되고있지 않으며, 주간 사용자도 typeorm-extension에 비해 적기에 typeorm-extension을 선택했습니다.)
랜덤 값들로 데이터를 생성하려면 각 entity 마다 factory를 만들어줘야 합니다.
factory는 랜덤 값들로 채워진 더미 데이터를 생성하는 공장과 비슷합니다.
랜덤 값들은 faker 라이브러리를 통해 생성이 됩니다.
Faker 라이브러리는 TypeScript를 지원합니다. Faker 라이브러리가 제대로 작동시키기 위해
tsconfig 파일에
{
"compilerOptions": {
...
"esModuleInterop": true,
"moduleResolution": "Node"
}
}
위 라인을 추가합니다.
seeding을 위해 package.json 파일 scripts에 다음 스크립트를 삽입합니다.
"seed:run": "ts-node -r tsconfig-paths/register ./node_modules/typeorm-extension/bin/cli.cjs seed:run"
Factory 코드를 작성하겠습니다.
// src/database/factories/users.factory.ts
import { UserModel } from 'src/user/entities/user.entity';
import { setSeederFactory } from 'typeorm-extension';
export default setSeederFactory(UserModel, (faker) => {
const user = new UserModel();
user.name = faker.person.fullName();
user.studentNumber = faker.number.int({ min: 10000000, max: 99999999 });
user.birth = faker.date.birthdate();
user.phone = faker.phone.number();
user.major = faker.music.genre();
user.password = faker.internet.password();
user.isStaff = faker.datatype.boolean({
probability: 0.5,
});
user.currentSemesterMember = faker.datatype.boolean({
probability: 0.5,
});
user.isPaid = faker.datatype.boolean({
probability: 0.5,
});
return user;
});
더 많은 데이터 타입 및 랜덤 값 생성 방법은 공식 홈페이지에서 찾아볼 수 있습니다.
seeder를 설정하겠습니다.
// src/database/seeds/users.seeder.ts
import { UserModel } from 'src/user/entities/user.entity';
import { DataSource } from 'typeorm';
import { Seeder, SeederFactoryManager } from 'typeorm-extension';
export default class UsersSeeder implements Seeder {
async run(
dataSource: DataSource,
factoryManager: SeederFactoryManager,
): Promise<any> {
const repository = dataSource.getRepository(UserModel);
const user = {
name: 'jun',
studentNumber: 12341234,
birth: new Date(),
phone: '010-1234-1234',
major: 'Computer Science',
password: '1234',
isStaff: true,
currentSemesterMember: true,
isPaid: true,
};
await repository.insert([user]); // 하나의 데이터만 삽입할 경우
const userFactory = factoryManager.get(UserModel);
await userFactory.saveMany(100); // 100개의 데이터를 생성한 뒤 삽입
}
}
아래 명령어를 실행합니다.
npm run seed:run
db에 랜덤 데이터가 잘 들어간 것을 볼 수 있습니다.
2024.08.05 추가
데이터를 seeding 할 때 연관관계로 묶여있는 엔티티가 있습니다.
현재 구현중인 프로젝트에선 활동 게시글과 유저의 관계였는데요,
활동 게시글은 유저 중 임원진만 작성할 수 있게 되어있습니다.
이 글의 내용을 따라 활동게시글 seeder와 factory를 정의해 생성하려 했지만, seeder의 실행 순서를 정의하지 못해 에러가 발생하는 경우들이 있었습니다.
그래서 이 부분을 아래와 같은 방식으로 해결했습니다.
1. 엔티티에 맞게 factory 코드를 작성합니다.
// src/database/factories/activity.factory.ts
import { ActivityModel } from 'src/activity/entities/activity.entity';
import { setSeederFactory } from 'typeorm-extension';
export default setSeederFactory(ActivityModel, (faker) => {
const activity = new ActivityModel();
...
return activity;
});
2. users.seeder.ts에서 activity를 생성합니다.
import { ActivityModel } from 'src/activity/entities/activity.entity';
import { UserModel } from 'src/user/entities/user.entity';
import { DataSource } from 'typeorm';
import { Seeder, SeederFactoryManager } from 'typeorm-extension';
export default class UsersSeeder implements Seeder {
async run(
dataSource: DataSource,
factoryManager: SeederFactoryManager,
): Promise<any> {
const repository = dataSource.getRepository(UserModel);
...
const userFactory = factoryManager.get(UserModel);
await userFactory.saveMany(3000);
const activityFactory = factoryManager.get(ActivityModel);
for (let i = 0; i < 10; i++) {
const activity = await activityFactory.make();
activity.Author = await repository.findOne({ where: { isStaff: true } });
await activityFactory.save(activity);
}
}
}
임시방편으로 문제를 해결했습니다.
seeder의 실행순서를 설정하는 방법을 찾거나 더 좋은 방법을 찾는다면 추후에 추가하도록 하겠습니다.