저번에 User와 관련한 Entity를 만들었었다.
@Entity('users')
@Unique(['email'])
export class Users{
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
email: string;
@Column()
password: string;
}
이 Entity를 등록했었고, 이거를 바탕으로 nest repository를 서비스에 만들어보자.
@Injectable()
export class UsersService {
constructor(@InjectRepository(Users) private readonly userRepository: Repository<Users>) { }
}
이렇게 UserService에 UserRepository의 의존성을 등록해준다.
컨트롤러의 내용은 제외하고 서비스의 코드만 채워볼것이다.
- Create
우선 create부터 만들어보자.
async create(createUserDto: CreateUserDto) {
const user = this.userRepository.create({
name: createUserDto.name,
email: createUserDto.email,
password: createUserDto.password
});
return await this.userRepository.save(user);
}
create와 save를 사용했는데, create가 데이터베이스에 값을 create하는 메서드는 아니다.
해당 entity 구조를 바탕으로 새로운 entity를 만들어주는 메서드이다.
save가 데이터베이스에 해당 값을 저장해주는 메서드이다.
사실 아래와 같은 방법으로도 저장은 가능하다.
this.userRepository.save({name: createUserDto.name, email: createUserDto.email, password: createUserDto.password});
하지만 그럼에도 불구하고 entity를 생성해서 저장하는 이유는 entity에 비즈니스 로직들이 들어있으며, 데코레이터등을 사용해 값을 미리 검증할 수 있다.
그리고 insert, update와 같은 작업 이후에 특정 메서드가 동작하도록 할 수 있다.
@AfterInsert()
logInsert(){
console.log(`user inserted id: ${this.id}`);
}
이렇게 AfterInsert()를 사용하면 데이터베이스 저장 이후에 로그가 출력되도록 할 수 있다.
이렇게 entity의 기능들과 hook을 사용할 수 있도록, 직접 객체 리터럴로 저장하는 것이 아닌 create로 엔티티를 생성해서 저장하도록 하자.
- Read
이번엔 read이다.
우선 id를 사용해 하나의 entity만 가져오는 메서드이다.
async findOne(id: number){
return this.userRepository.findOne({where: {id: id}});
}
이렇게 findOne where절 내에 조건을 작성해주면 된다.
id는 PK이기 때문에 하나만 값을 가져와 findOne() 메서드를 사용했다.
여기서 만약 찾지 못하는 경우를 대비해 404 에러를 반환할수 있도록 하자.
async findOne(id: number): Promise<Users> {
const user = await this.userRepository.findOne({where: {id: id}});
if(!user){
throw new NotFoundException('User not found');
}
return user;
}
이번에는 name을 사용해 많은 데이터들을 검색하는 메서드이다.
async findAll(name: string): Promise<Users[]> {
return await this.userRepository.find({where: {name}});
}
역시 where절의 조건으로 name을 추가해주었고, 하나의 엔티티만 가져오는 것이 아니기 때문에 find() 메서드를 사용한다.
여기서는 만약 해당하는 값이 없다면 404에러가 아닌 빈 배열이 반환되게 될 것이다.
- Update
async update(id: number, userAttrs: Partial<Users>): Promise<Users> {
const user = await this.findOne(id);
Object.assign(user, userAttrs);
return await this.userRepository.save(user);
}
여기서 Partial 타입을 사용한 것을 볼 수 있는데
Users의 부분적인 속성들을 가지는 클래스이다.
{name}, {name, email} ... 등등 Users 클래스 내의 속성들을 부분적으로 가지고 있는 클래스이다.
그 값을 Object.assign을 통해 기존의 user 데이터에 덮어쓰고, save() 메서드를 통해 데이터베이스에 저장한다.
export class UpdateUserDto {
@IsString()
@IsOptional()
name: string;
@IsEmail()
@IsOptional()
email: string;
@IsString()
@IsOptional()
password: string;
}
dto에서 @IsOptional()을 사용하면, json에서 선택적으로 값을 넣어 요청할 수 있다.
- Delete
async delete(id: number): Promise<Users> {
const user = await this.findOne(id);
return this.userRepository.remove(user);
}
저번과 마찬가지로 조회 후 값을 확인하고 삭제한다.
remove()는 아이디가 아닌 entity를 넣어서 삭제한다.
그럼 여기서 궁금한점이 있다.
save() 메서드는 언제 insert로 동작하고, 언제 update로 동작하는걸까?
다음과 같은 3가지 상황을 만들어서 save()를 호출해봤다.
모두 find를 호출하지 않은 상태로 save()를 호출하는 것이다.
- save()로 넘긴 entity에 id가 없는 경우
- entity에 id가 있지만 데이터베이스에는 해당 id가 없는 경우
- entity에 id가 있으며, 데이터베이스에도 해당 id가 존재하는 경우
우선 현재 데이터베이스에는 해당 값만 존재한다.
첫번째 경우는 역시 성공한다.
POST http://localhost:3000/users/test
Content-Type: application/json
{
"name": "승규",
"email": "sda1fs@gmail.com",
"password": "1234"
}
그냥 평범하게 insert 쿼리가 날아간 것을 볼 수 있다.
두번째 경우도 성공한다.
POST http://localhost:3000/users/test
Content-Type: application/json
{
"id": 123,
"name": "승규",
"email": "sda1123fs@gmail.com",
"password": "1234"
}
쿼리를 보면 신기하게도 미리 조회를 해보고, insert 한 것을 볼 수 있다.
세번째 경우에서는 중복 에러가 발생할 줄 알았지만 성공했다.
POST http://localhost:3000/users/test
Content-Type: application/json
{
"id": 123,
"name": "승규",
"email": "sda112123s@gmail.com",
"password": "1234"
}
이렇게 요청을 하니
이거도 미리 조회를 하고, 값이 있기에 update로 바꾼 것을 볼 수 있었다.
save() 메서드는 id가 존재하는 경우에 데이터베이스에 2번 접근하는 것을 알 수 있었다.
'Node > Nest' 카테고리의 다른 글
Nest에서 typeorm을 통해 Entity 작성하기 (0) | 2025.07.06 |
---|---|
Nest에서 멀티 모듈 생성하는 방법 (1) | 2025.07.06 |
Nest에서의 IOC/DI 알아보기 (0) | 2025.07.06 |
Nest에서 Service layer 구현하기 (0) | 2025.07.05 |
Nest에서 파일 시스템을 통해 repository layer 구현하기 (0) | 2025.07.05 |