๐ŸŽ‰ berenickt ๋ธ”๋กœ๊ทธ์— ์˜จ ๊ฑธ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค. ๐ŸŽ‰
Back
NestJs
09-Relations

1. SQL ๊ด€๊ณ„

  • foreign key๋ฅผ ์•Œ๊ณ  ์žˆ์œผ๋ฉด, ํ•ด๋‹น ํ‚ค์— ๋ฐ์ดํ„ฐ๋ฅผ ๊ตณ์ด ๊ฐ€์งˆ ํ•„์š”๊ฐ€ ์—†๋‹ค.

2. UserModel ์ƒ์„ฑ

์ด์ œ ๋‹ค์‹œ typeorm์šฉ ํด๋”๊ฐ€ ์•„๋‹Œ ๊ธฐ์กด rest-server๋กœ ์‹ค์Šต์„ ์ง„ํ–‰ํ•œ๋‹ค. ๊ทธ ๋‹ค์Œ user ๋ชจ๋ธ์„ nest cli ๋ช…๋ น์–ด๋กœ ์ƒ์„ฑํ•œ๋‹ค.

1
nest g resource
2
? What name would you like to use for this resource? users
3
? What transport layer do you use? REST API
4
? Would you like to generate CRUD entry points? No

๊ทธ๋ฆฌ๊ณ  cli๋กœ ๋งŒ๋“ค๋ฉด, ์ž๋™์œผ๋กœ app ๋ชจ๋“ˆ์— ๋ฐฉ๊ธˆ ์ƒ์„ฑํ•œ ๋ชจ๋“ˆ์„ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค.

src/users/entities/users.entity.ts ํŒŒ์ผ์€ ๋งŒ๋“ ๋‹ค.

users.entity.ts
1
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
2
3
@Entity()
4
export class UsersModel {
5
@PrimaryGeneratedColumn()
6
id: number
7
8
@Column()
9
nickname: string
10
11
@Column()
12
email: string
13
14
@Column()
15
password: string
16
}

app ๋ชจ๋“ˆ์— UsersModel์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

app.module.ts
1
@Module({
2
imports: [
3
PostsModule,
4
TypeOrmModule.forRoot({
5
// ์ƒ๋žต
6
// entitiesํด๋”์— ์ž‘์„ฑํ•œ ๋ชจ๋“ˆ ๊ฐ€์ ธ์˜ค๊ธฐ
7
entities: [PostsModel, UsersModel],
8
synchronize: true,
9
}),
10
UsersModule,
11
],
12
controllers: [AppController],
13
providers: [AppService],
14
})

3. Column ์˜ต์…˜ ์ข…๋ฅ˜

@Column์„ Cmd + ๋งˆ์šฐ์Šค ์™ผ์ชฝ ๋กœ ํด๋ฆญํ•˜๋ฉด, ๋ช…์„ธ์„œ๊ฐ€ ๋‚˜์˜จ๋‹ค.

users.entity.ts
1
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
2
3
@Entity()
4
export class UsersModel {
5
@PrimaryGeneratedColumn()
6
id: number
7
8
/*** ๋‹‰๋„ค์ž„ ํŠน์„ฑ
9
* 1) ๊ธธ์ด๊ฐ€ 20์„ ๋„˜์ง€ ์•Š์„ ๊ฒƒ
10
* 2) ์œ ์ผ๋ฌด์ดํ•œ ๊ฐ’์ด ๋  ๊ฒƒ
11
*/
12
@Column({
13
length: 20,
14
unique: true,
15
})
16
nickname: string
17
18
/*** ์ด๋ฉ”์ผ ํŠน์„ฑ
19
* 1) ์œ ์ผ๋ฌด์ดํ•œ ๊ฐ’์ด ๋  ๊ฒƒ
20
*/
21
@Column({
22
unique: true,
23
})
24
email: string
25
26
@Column()
27
password: string
28
}

4. Enum Column ์ƒ์„ฑ

users/const/roles.const.ts ํŒŒ์ผ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

roles.const.ts
1
export enum RolesEnum {
2
ADMIN = 'ADMIN',
3
USER = 'USER',
4
}

cf, enum ํƒ€์ž…์€ TS์—์„œ ์ œ๊ณตํ•˜๋Š” ๋ช…๋ช…๋œ ์ƒ์ˆ˜์˜ ์ง‘ํ•ฉ์„ ๋‚˜ํƒ€๋‚ด๋Š” ๋ฐ์ดํ„ฐ ํƒ€์ž…

users.entity.ts
1
@Entity()
2
export class UsersModel {
3
// ์ƒ๋žต
4
5
@Column({
6
// role ํŠน์„ฑ์˜ ํƒ€์ž…์„ RolesEnum์˜ ๋ชจ๋“  ๊ฐ’๋“ค๋กœ ์ง€์ •
7
enum: Object.values(RolesEnum),
8
default: RolesEnum.USER,
9
})
10
role: RolesEnum
11
}

5. User ๊ด€๋ จ ์„œ๋น„์Šค ๋ฐ ์ปจํŠธ๋กค๋Ÿฌ ์ž‘์„ฑ

users.module.ts
1
// ์ƒ๋žต
2
3
@Module({
4
// ์ด ๋ชจ๋“ˆ ์•ˆ์—์„œ UsersModel์„ ์–ด๋””์„œ๋“  ์‚ฌ์šฉ ๊ฐ€๋Šฅ
5
imports: [TypeOrmModule.forFeature([UsersModel])],
6
controllers: [UsersController],
7
providers: [UsersService],
8
})
9
export class UsersModule {}

์œ ์ € ์„œ๋น„์Šค๋ฅผ ๋งŒ๋“ ๋‹ค.

users.service.ts
1
import { Injectable } from '@nestjs/common'
2
import { InjectRepository } from '@nestjs/typeorm'
3
import { UsersModel } from './entities/users.entity'
4
import { Repository } from 'typeorm'
5
6
@Injectable()
7
export class UsersService {
8
constructor(
9
@InjectRepository(UsersModel)
10
private readonly userRepository: Repository<UsersModel>,
11
) {}
12
13
async createUser(nickname: string, email: string, password: string) {
14
const user = this.userRepository.create({
15
nickname,
16
email,
17
password,
18
})
19
const newUser = await this.userRepository.save(user)
20
return newUser
21
}
22
23
async getAllUsers() {
24
return this.userRepository.find()
25
}
26
}

์œ„์—์„œ ๋งŒ๋“  ์„œ๋น„์Šค ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ๋งŒ๋“ ๋‹ค.

users.controller.ts
1
import { Body, Controller, Get, Post } from '@nestjs/common'
2
import { UsersService } from './users.service'
3
4
@Controller('users')
5
export class UsersController {
6
constructor(private readonly usersService: UsersService) {}
7
8
@Get()
9
getUsers() {
10
return this.usersService.getAllUsers()
11
}
12
13
@Post()
14
postUser(
15
@Body('nickname') nickname: string, //
16
@Body('email') email: string,
17
@Body('password') password: string,
18
) {
19
return this.usersService.createUser(nickname, email, password)
20
}
21
}

6. Author ๊ด€๊ณ„ ์ƒ์„ฑ

ํฌ์ŠคํŠธ ์—”ํ‹ฐํ‹ฐ์— ๋‹ค๋Œ€์ผ ๊ด€๊ณ„๋ฅผ ๋„ฃ์–ด์ค€๋‹ค.

posts.entity.ts
1
@Entity()
2
export class PostsModel {
3
@PrimaryGeneratedColumn()
4
id: number
5
6
/*** ์ž‘์„ฑ์ž 1๋ช…์ด ์—ฌ๋Ÿฌ ๊ฐœ์˜ ํฌ์ŠคํŠธ๋ฅผ ์ž‘์„ฑ
7
* 1) UserModel๊ณผ ์—ฐ๋™ํ•œ๋‹ค (Foreign Key๋ฅผ ์‚ฌ์šฉํ•ด์„œ)
8
* 2) null์ด ๋  ์ˆ˜ ์—†๋‹ค
9
*/
10
// one์— ํ•ด๋‹นํ•˜๋Š” ํด๋ž˜์Šค ํƒ€์ž…์„ ๋„ฃ์–ด์ฃผ๋ฉด ๋œ๋‹ค
11
// ์ด ํด๋ž˜์Šค ํƒ€์ž…์„ ๋‘ ๋ฒˆ์จฐ ํ•จ์ˆ˜์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.
12
// ์–ด๋–ค ํŒŒ๋ผ๋ฏธํ„ฐ์™€ ์—ฐ๋™์‹œํ‚ฌ์ง€ ์„ ํƒ
13
@ManyToOne(() => UsersModel, user => user.posts, {
14
nullable: false,
15
})
16
author: UsersModel
17
18
// ์ƒ๋žต
19
}

์œ ์ € ์—”ํ‹ฐํ‹ฐ์— ์ผ๋Œ€๋‹ค ๊ด€๊ณ„๋ฅผ ๋„ฃ์–ด์ค€๋‹ค.

users.entity.ts
1
@Entity()
2
export class UsersModel {
3
// ์ƒ๋žต
4
5
@OneToMany(() => PostsModel, post => post.author)
6
posts: PostsModel[]
7
}

7. ์—๋Ÿฌ ํ•ด๊ฒฐ ๋ฐ DB ์ดˆ๊ธฐํ™”

posts ์„œ๋น„์Šค์— ๋ณด๋ฉด author ๋ถ€๋ถ„์— ์—๋Ÿฌ๊ฐ€ ๋‚  ๊ฒ๋‹ˆ๋‹ค. author๊ฐ€ string์ด์˜€๋‹ค๊ฐ€, UsersModel๋กœ ๋ณ€๊ฒฝ๋˜์–ด์„œ ์ƒ๊ธด๊ฒ๋‹ˆ๋‹ค.

posts.service.ts
1
// posts.service.ts
2
/**
3
* 1) create : ์ €์žฅํ•  ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑ
4
* 2) save : ๊ฐ์ฒด๋ฅผ ์ €์žฅ (create aใ…”์„œ๋“œ์—์„œ ์ƒ์„ฑํ•œ ๊ฐ์ฒด๋กœ)
5
*/
6
async createPost(authorId: number, title: string, content: string) {
7
const post = this.postsRepository.create({
8
author: {
9
id: authorId,
10
},
11
title,
12
content,
13
likeCount: 0,
14
commentCount: 0,
15
})
16
17
const newPost = await this.postsRepository.save(post)
18
19
return newPost
20
}
21
22
/** save์˜ 2๊ฐ€์ง€ ๊ธฐ๋Šฅ
23
* 1) ๋งŒ์•ฝ์— ๋ฐ์ดํ„ฐ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด(id ๊ธฐ์ค€) ์ƒˆ๋กœ ์ƒ์„ฑํ•œ๋‹ค.
24
* 2) ๋งŒ์•ฝ์— ๋ฐ์ดํ„ฐ๊ฐ€ ์กด์žฌํ•œ๋‹ค๋ฉด(๊ฐ™์€ id์˜ ๊ฐ’์ด ์กด์žฌํ•œ๋‹ค๋ฉด) ์กด์žฌํ•˜๋˜ ๊ฐ’์„ ์—…๋ฐ์ดํŠธํ•œ๋‹ค.
25
*/
26
async updatePost(postId: number, title: string, content: string) {
27
const post = await this.postsRepository.findOne({
28
where: { id: postId },
29
})
30
31
if (!post) throw new NotFoundException()
32
if (title) post.title = title
33
if (content) post.content = content
34
35
const newPost = await this.postsRepository.save(post)
36
return newPost
37
}
posts.controller.ts
1
// posts.controller.ts ์ƒ๋žต
2
/*** 3) POST /posts
3
* post๋ฅผ ์ƒ์„ฑํ•œ๋‹ค
4
*/
5
@Post()
6
postPosts(
7
@Body('authorId') authorId: number, //
8
@Body('title') title: string,
9
@Body('content') content: string,
10
) {
11
return this.postsService.createPost(authorId, title, content)
12
}
13
14
/*** 4) PATCH /posts/:id
15
* id์— ํ•ด๋‹นํ•˜๋Š” post๋ฅผ ๋ถ€๋ถ„ ๋ณ€๊ฒฝํ•œ๋‹ค
16
*/
17
@Patch(':id')
18
putPost(
19
@Param('id') id: string, //
20
@Body('title') title?: string,
21
@Body('content') content?: string,
22
) {
23
return this.postsService.updatePost(+id, title, content)
24
}

๋„ฃ์€ ๋ฐ์ดํ„ฐ๋“ค ๋•Œ๋ฌธ์— author๊ฐ€ null์ธ ๊ฒฝ์šฐ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ๊ธฐ์—, DB ์ดˆ๊ธฐํ™”ํ•ด์ค๋‹ˆ๋‹ค.


8. ๊ด€๊ณ„๋ฅผ ์ด์šฉํ•ด ๋ฐ์ดํ„ฐ ์ƒ์„ฑ

  • posts์™€ users์˜ ๊ด€๊ณ„๋ฅผ PostMan์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

9. ๊ด€๊ณ„ ํฌํ•จํ•œ ์ฟผ๋ฆฌ ์ž‘์„ฑ

posts.service.ts
1
// posts.service.ts ์ƒ๋žต
2
async getAllPosts() {
3
return this.postsRepository.find({ relations: ['author'] })
4
}
5
6
async getPostById(id: number) {
7
const post = await this.postsRepository.findOne({
8
// PostsModel์˜ id๊ฐ€ ์ž…๋ ฅ๋ฐ›์€ id์™€ ๊ฐ™์€์ง€ ํ•„ํ„ฐ๋ง
9
where: {
10
id,
11
},
12
relations: ['author'],
13
})
14
if (!post) {
15
throw new NotFoundException()
16
}
17
return post
18
}