> yarn add @nestjs/typeorm typeorm mysql
ormconfig.json
{
"type": "mysql",
"host": "localhost",
"port": 3308,
"username": "peter",
"password": "1234",
"database": "typeorm_test_db",
"entities": ["dist/**/*.entity{.ts,.js}"],
"synchronize": true
}
app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [TypeOrmModule.forRoot()],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
프로젝트 root 폴더에 ormconfig.json 파일을 작성하고, 위와 같이 app.module.ts 파일에서 TypeOrmModule.forRoot() 로 import 하면 typeorm을 사용 가능합니다.
(database 는 ormconfig.json 에 기록한 내용과 동일하게 셋팅했다고 가정합니다.)
user.entity.ts
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
firstName: string;
@Column()
lastName: string;
@Column({ default: true })
isActive: boolean;
}
위와 같이 User class 를 작성한 뒤 프로젝트를 실행하면 mysql database에 user 테이블이 자동으로 생성됩니다.
(ormconfig.json 파일의 "synchronize" 옵션이 false이면 자동생성하지 않습니다.)
user.service.ts
import { Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { User } from './user.entity';
import { InjectRepository } from '@nestjs/typeorm';
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
) {}
async add(user: User): Promise<void> {
await this.userRepository.save(user);
}
findAll(): Promise<User[]> {
return this.userRepository.find();
}
findOne(id: string): Promise<User> {
return this.userRepository.findOne(id);
}
async modify(user: User): Promise<void> {
const userNew = await this.userRepository.findOne(user.id);
userNew.firstName = user.firstName;
userNew.lastName = user.lastName;
userNew.isActive = user.isActive;
await this.userRepository.save(userNew);
}
async remove(id: string): Promise<void> {
await this.userRepository.delete(id);
}
}
위와 같이 save, findXX, delete 함수로 CRUD를 구현할 수 있습니다.
update는 보통의 find와 save의 조합으로 처리하는 것 같습니다.
Entity 간 1:1 관계 예제를 위해 Profile Entity를 추가합니다.
profile.entity.ts
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Profile {
@PrimaryGeneratedColumn()
id: number;
@Column()
gender: string;
@Column()
address: string;
}
Profile은 User의 상세정보로 User와 1:1 관계를 갖습니다.
User Entity에 관계설정 부분을 추가합니다.
user.entity.ts
import { Column, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn } from 'typeorm';
import { Profile } from '../profile/profile.entity';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
firstName: string;
@Column()
lastName: string;
@Column({ default: true })
isActive: boolean;
@OneToOne(() => Profile)
@JoinColumn()
profile: Profile;
}
위와 같이 작성한 뒤, 프로젝트를 실행하면 profile 테이블이 생성됩니다.
이 때 user 테이블에 profileId가 외래키(profile.id 참조)로써 추가됩니다.
user.service.ts
async add(user: User): Promise<void> {
const profile = user.profile;
await this.profileRepository.save(profile); // 저장하면 DB에 profile row가 생성되고, 자동생성된 id가 profile 객체의 id에 매핑됩니다.
await this.userRepository.save(user);
}
위와 같이 profile과 user를 각각의 테이블에 저장할 수 있습니다.
user.service.ts
findAll(): Promise<User[]> {
return this.userRepository.find({relations: ["profile"]});
}
위와 같이 간단하게 User와 Profile을 Join한 결과를 가져올 수 있습니다.
하지만, 보통의 경우 검색조건과 순서 및 페이징 등이 추가될 수 있습니다.
findAll(): Promise<User[]> {
return this.userRepository
.createQueryBuilder('user')
.leftJoinAndSelect('user.profile', 'profile')
.orderBy('user.id', 'DESC')
.getMany();
}
위와 같이 구체적인 상황을 서술할 수 있습니다.
findOne(id: string): Promise<User> {
return (
this.userRepository
.createQueryBuilder('user')
.leftJoinAndSelect('user.profile', 'profile')
.where('user.id = :id', { id })
.getOne()
);
}
하나의 User를 조회하고 싶은 경우 위와 같이 작성할 수 있습니다.
async modify(newUser: User): Promise<void> {
const user = await this.findOne(newUser.id.toString());
user.firstName = newUser.firstName;
user.lastName = newUser.lastName;
user.isActive = newUser.isActive;
const profile = user.profile;
profile.address = newUser.profile.address;
profile.gender = newUser.profile.gender;
await this.userRepository.save(user);
await this.profileRepository.save(profile);
}
3) READ에서 작성한 findOne 함수를 재활용하여 위와 같이 Update를 구현할 수 있습니다.
async remove(id: string): Promise<void> {
const user = await this.findOne(id);
const profile = user.profile;
await this.userRepository.delete(id);
await this.profileRepository.delete(profile.id);
}
위와 같이 작성하면 id를 key로 갖는 user가 삭제되고, user와 매핑된 profile도 삭제됩니다.
이 때, user.profileId가 외래키이기 때문에 profile을 먼저 지울 수 없어서 user를 먼저 삭제합니다. (둘의 순서를 바꾸면 에러가 나면서 동작하지 않습니다.)
한 명의 사용자(User)가 여러 개의 글(Posts)을 쓸 수 있습니다.
이 경우, User 와 Posts 는 1:N (One To Many)관계에 있습니다.
Posts
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import { User } from '../user/user.entity';
@Entity()
export class Posts {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column()
contents: string;
@ManyToOne(() => User, user => user.posts)
user: User;
}
User
import { Column, Entity, JoinColumn, OneToMany, OneToOne, PrimaryGeneratedColumn } from 'typeorm';
import { Profile } from '../profile/profile.entity';
import { Posts } from '../post/post.entity';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
firstName: string;
@Column()
lastName: string;
@Column({ default: true })
isActive: boolean;
@OneToOne(() => Profile, { cascade: true })
@JoinColumn()
profile: Profile;
@OneToMany(() => Posts, post => post.user)
posts: Posts[];
}
위와 같이 작성하면, DB에 posts 테이블이 생성됩니다.
이 때, posts 테이블에 userId 가 외래키로 추가됩니다. (user.id 를 참조합니다.)
특정 User가 올린 Posts를 저장하기 위해서는 userId와 Posts 내용이 필요합니다.
PostReqDto
export class PostReqDto {
userId: number;
title: string;
contents: string;
}
PostController
import { Body, Controller, Logger, Post } from '@nestjs/common';
import { UserService } from '../user/user.service';
import { PostService } from './post.service';
import { PostReqDto } from './post.req.dto';
@Controller('post')
export class PostController {
constructor(private readonly userService: UserService, private readonly postService: PostService) {}
@Post()
async add(@Body() req: PostReqDto): Promise<void> {
const user = await this.userService.findOne(req.userId.toString());
await this.postService.add(user, req);
}
}
컨트롤러에서는 요청 파라미터에 있는 userId로부터 앞서 작성했던 UserService.findOne() 메소드를 이용하여 user 객체를 조회해옵니다.
user 객체는 Posts와 User 간의 관계를 정의해주기 위해서 필요합니다.
PostService
import { Injectable } from '@nestjs/common';
import { User } from '../user/user.entity';
import { PostReqDto } from './post.req.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { Posts } from './post.entity';
import { Repository } from 'typeorm';
@Injectable()
export class PostService {
constructor(@InjectRepository(Posts) private postsRepository: Repository<Posts>) {}
async add(user: User, req: PostReqDto) {
const posts = new Posts();
posts.contents = req.contents;
posts.title = req.title;
posts.user = user;
await this.postsRepository.save(posts);
}
}
위와 같이 posts 객체를 생성하고 프로퍼티를 셋팅한 뒤 PostRepository를 이용하여 저장하면 해당 user가 작성한 posts 가 저장됩니다.
아래는 userId = 1로 셋팅하여 테스트해본 결과입니다.
위와 같이 posts.user 를 셋팅하는 방법도 있지만, "user.posts = [posts]"와 같이 셋팅한 뒤 userRepository에서 user를 save하는 방법도 있습니다.
자세한 내용은 typeorm 공식문서를 참고합시다!
https://typeorm.io/#/many-to-one-one-to-many-relations
await this.postsRepository.find();
...
// return 형태
[
{
"id": 1,
"title": "글 제목",
"contents": "글 내용"
},
...
]
위와 같이 find()를 실행하면 post에 대한 정보만 조회됩니다.(연관된 user에 대한 정보는 조회되지 않습니다.)
return await this.postsRepository.find({relations: ["user"]});
// return 형태
{
"id": 1,
"title": "글 제목",
"contents": "글 내용",
"user": {
"id": 1,
"firstName": "Peter",
"lastName": "Choi",
"isActive": true
}
},
연관된 user에 대한 정보까지 조회하기 위해서는 위와 같이 "relations" 옵션을 추가하면 됩니다.
return await this.postsRepository
.createQueryBuilder('posts')
.leftJoinAndSelect('posts.user', 'user')
.orderBy('posts.id', 'DESC')
.getMany();
대부분 게시글은 최신글 순으로 보여주기 때문에 위와 같이 order by 조건을 추가해줘야 합니다.
그 이외에도 검색 조건 등을 적용하기 위해 where 절이 필요하게 되는게, 이런 여러가지 조건들을 처리해주기 위해서는 위와 같이 QueryBuilder로 query 옵션을 추가할 수 있습니다.
return await this.postsRepository
.createQueryBuilder('posts')
.leftJoinAndSelect('posts.user', 'user')
.where('posts.id = :id', { id })
.getOne();
단건 조회는 위와 같이 where 절에 posts.id 일치 조건을 추가합니다.
TBW...
찾고있던 내용인데, 감사합니다~