Relational Database는 관계형 데이터베이스입니다.
One to Many(1 : N)
Many to One(N : 1)
Many to Many(N : N)
Posts에는 column으로 id, author, title, content, likeCount, commentCount가 있습니다.
1, newjeans_official, 뉴진스 민지, 메이크업 고치고 있는 민지, 1000, 1000
만약 author_password, author_thumbnail을 추가한다면 새롭게 column을 만들어야 합니다.
id, author, author_password, author_thumbnail, title, content, likeCount, commentCount
1, newjeans_official, 123456, thumbnail.png, 뉴진스 민지, 메이크업 고치고 있는 민지, 1000, 1000
그러면 우리는 Posts를 생성할 때마다 author에 관해서 중복해서 계속 넣어야하는 데이터 중복의 문제가 있습니다. 따라서 User 테이블을 생성합니다.
Posts: id, author, title, content, likeCount, commentCount
User: id, name, author_password, author_thumbnail
그리고 Posts와 User 사이의 관계를 만들면 끝납니다.
Posts: id, author, title, content, likeCount, commentCount + user_id(Foreign Key)
이를 통해서 Relation을 알아보겠습니다.
1명의 User는 N개의 Posts를 작성할 수 있습니다. 반대로 N개의 Posts는 1명의 User에게 귀속됩니다.
하지만 Posts와 Tag의 관계를 생각하면 1개의 Post에는 N개의 Tag가 붙고, 1개의 Tag는 N개의 Posts에 붙습니다.
Posts에 column으로 author가 있습니다. 앞서 말한 데이터의 중복 문제를 해결하기 위해 User 테이블을 만들고 Foreign Key를 사용합시다. 먼저 users module을 생성합니다.
nest g resource
imports: [
PostsModule,
TypeOrmModule.forRoot({
type: 'postgres',
host: '127.0.0.1',
port: 5432,
username: 'postgres',
password: 'postgres',
database: 'postgres',
entities: [
PostsModel,
],
synchronize: true,
}),
UsersModule // 자동으로 추가가 된다.
],
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
@Entity()
export class UsersModel {
@PrimaryGeneratedColumn()
id: number;
@Column()
nickname: string;
@Column()
email: string;
@Column()
password: string;
}
그리고 UsersModel 엔티티를 app.module.ts에 등록해줍니다.
entities: [
// 데이터베이스와 연동될 Model
PostsModel,
UsersModule,
],
enum을 생성하기 위해서 따로 폴더를 생성하고 파일을 만들자.
export enum RolesEnum {
ADMIN = 'ADMIN',
USER = 'USER',
}
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
import { RolesEnum } from "../const/roles.const";
@Entity()
export class UsersModel {
@PrimaryGeneratedColumn()
id: number;
@Column({
length: 20, // 길이가 20넘으면 안됨
unique: true, // 유일무이한 값이 될 것
})
nickname: string;
@Column({
unique: true,
})
email: string;
@Column()
password: string;
@Column({
enum: Object.values(RolesEnum), // RolesEnum에 있는 모든 value들을 가지고 사용할 것이다.
default: RolesEnum.USER,
})
role: RolesEnum;
}
Relation 생성하기 전에 먼저 Users의 Service와 Controller를 만들자. users.module.ts에 레포지토리를 등록하자.
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersModel } from './entities/users.entity';
@Module({
imports: [
TypeOrmModule.forFeature([
UsersModel,
])
],
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}
UsersModel의 레포지토리를 Inject받을 부분은 Service 부분입니다.
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { UsersModel } from './entities/users.entity';
import { Repository } from 'typeorm';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(UsersModel)
private readonly usersRepository: Repository<UsersModel>,
) {}
async createUser(nickname: string, email: string, password: string) {
const user = this.usersRepository.create({
nickname,
email,
password,
});
const newUser = await this.usersRepository.save(user);
return newUser;
}
async getAllUsers() {
return await this.usersRepository.find();
}
}
import { Body, Controller, Get, Post } from '@nestjs/common';
import { UsersService } from './users.service';
@Controller('users')
export class UsersController {
constructor(
private readonly usersService: UsersService
) {}
@Get()
getUsers() {
return this.usersService.getAllUsers();
}
@Post()
postUser(
@Body('nickname') nickname: string,
@Body('email') email: string,
@Body('password') password: string,
) {
return this.usersService.createUser(nickname, email, password);
}
}
참조가 되는 파라미터 이름을 넣어줘야 합니다. 즉, UsersModel에서는 PostsModel의 author로 참조를 걸어야하고, 반대는 posts에 걸어야 합니다.
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
import { RolesEnum } from "../const/roles.const";
import { PostsModel } from "src/posts/entities/posts.entity";
@Entity()
export class UsersModel {
@PrimaryGeneratedColumn()
id: number;
@Column({
length: 20, // 길이가 20넘으면 안됨
unique: true, // 유일무이한 값이 될 것
})
nickname: string;
@Column({
unique: true,
})
email: string;
@Column()
password: string;
@Column({
enum: Object.values(RolesEnum), // RolesEnum에 있는 모든 value들을 가지고 사용할 것이다.
default: RolesEnum.USER,
})
role: RolesEnum;
@OneToMany(() => PostsModel, (post) => post.author)
posts: PostsModel[];
}
import { UsersModel } from "src/users/entities/users.entity";
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
@Entity()
export class PostsModel { // TypeORM이 app.module.ts에 해당 정보들을 기반으로 DB에 생성한다.
@PrimaryGeneratedColumn() // PK 이면서 자동증가전략
id: number;
// 1) UsersModel과 연동, Foreign Key
// 2) Not null
@ManyToOne(() => UsersModel, (user) => user.posts, {
nullable: false,
})
author: UsersModel;
@Column()
title: string;
@Column()
content: string;
@Column()
likeCount: number;
@Column()
commentCount: number;
}
Error
실행을 하게되면 에러가 발생합니다.
async createPost(author: string, title: string, content: string) {
const post = this.postsRepository.create({
author, // 이 부분에서 에러
title,
content,
likeCount: 0,
commentCount: 0,
});
const newPost = await this.postsRepository.save(post);
return newPost;
}
변경...
async createPost(authorId: number, title: string, content: string) {
const post = this.postsRepository.create({
author: {
id: authorId,
},
title,
content,
likeCount: 0,
commentCount: 0,
});
const newPost = await this.postsRepository.save(post);
return newPost;
}
async updatePost(postId: number, author: string, title: string, content: string) {
const post = await this.postsRepository.findOne({
where: {
id: postId,
},
});
if (!post) throw new NotFoundException();
if (author) post.author = author; // 에러: 이 부분은 필요 없음 Post에서 author를 바꿀 필요가 없기 때문에!!
if (title) post.title = title;
if (content) post.content = content;
const newPost = await this.postsRepository.save(post);
return newPost;
}
변경...
async updatePost(postId: number, author: string, title: string, content: string) {
const post = await this.postsRepository.findOne({
where: {
id: postId,
},
});
if (!post) throw new NotFoundException();
if (title) post.title = title;
if (content) post.content = content;
const newPost = await this.postsRepository.save(post);
return newPost;
}
최종 코드
import { Body, Controller, Delete, Get, NotFoundException, Param, Post, Put } from '@nestjs/common';
import { PostsService } from './posts.service';
@Controller('posts')
export class PostsController {
constructor(private readonly postsService: PostsService) {}
@Get()
getPosts() {
return this.postsService.getAllPosts();
}
@Get(':id')
getPost(@Param('id') id: string) { // url의 id 부분만 가져오고 변수 id에 넣는다.
return this.postsService.getPostById(+id);
}
@Post()
postPosts(
@Body('authorId') authorId: number,
@Body('title') title: string,
@Body('content') content: string,
) {
return this.postsService.createPost(
authorId, title, content
);
}
@Put(':id')
putPost(
@Param('id') id: string,
@Body('title') title?: string,
@Body('content') content?: string,
) {
return this.postsService.updatePost(
+id, title, content,
);
}
@Delete(':id')
deletePost(@Param('id') id: string) {
return this.postsService.deletePost(+id);
}
}
import { Injectable, NotFoundException } from '@nestjs/common';
import { Repository } from 'typeorm';
import { PostsModel } from './entities/posts.entity';
import { InjectRepository } from '@nestjs/typeorm';
/**
* author: string
* title: string
* content: string
* likeCount: number
* commentCount: number
*/
export interface PostModel {
id: number;
author: string;
title: string;
content: string;
likeCount: number;
commentCount: number;
}
let posts: PostModel[] = [
{
id: 1,
author: 'newjeans_minji',
title: '뉴진스 민지',
content: '메이크업 고치고 있는 민지',
likeCount: 100000,
commentCount: 999,
},
{
id: 2,
author: 'newjeans_herin',
title: '뉴진스 헤린',
content: '춤추고 있는 헤린',
likeCount: 100000,
commentCount: 999,
},
{
id: 3,
author: 'newjeans_daniel',
title: '뉴진스 다니엘',
content: '노래부르는 다니엘',
likeCount: 100000,
commentCount: 999,
},
]
@Injectable()
export class PostsService {
constructor(
@InjectRepository(PostsModel)
private readonly postsRepository: Repository<PostsModel>
) {}
async getAllPosts() {
return await this.postsRepository.find({
// relations를 포함한 쿼리 작성
relations: ['author']
});
}
async getPostById(id: number) {
const post = await this.postsRepository.findOne({
where: {
id,
},
// relations를 포함한 쿼리 작성
relations: [
'author',
],
});
if (!post) throw new NotFoundException();
return post;
}
async createPost(authorId: number, title: string, content: string) {
const post = this.postsRepository.create({
author: {
id: authorId,
},
title,
content,
likeCount: 0,
commentCount: 0,
});
const newPost = await this.postsRepository.save(post);
return newPost;
}
async updatePost(authorId: number, title: string, content: string) {
const post = await this.postsRepository.findOne({
where: {
id: authorId,
},
});
if (!post) throw new NotFoundException();
if (title) post.title = title;
if (content) post.content = content;
const newPost = await this.postsRepository.save(post);
return newPost;
}
async deletePost(postId: number) {
const post = await this.postsRepository.findOne({
where: {
id: postId,
},
});
if (!post) throw new NotFoundException();
await this.postsRepository.delete(postId)
return postId;
}
}
import { Body, Controller, Get, Post } from '@nestjs/common';
import { UsersService } from './users.service';
@Controller('users')
export class UsersController {
constructor(
private readonly usersService: UsersService
) {}
@Get()
getUsers() {
return this.usersService.getAllUsers();
}
@Post()
postUser(
@Body('nickname') nickname: string,
@Body('email') email: string,
@Body('password') password: string,
) {
return this.usersService.createUser(nickname, email, password);
}
}
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { UsersModel } from './entities/users.entity';
import { Repository } from 'typeorm';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(UsersModel)
private readonly usersRepository: Repository<UsersModel>,
) {}
async createUser(nickname: string, email: string, password: string) {
const user = this.usersRepository.create({
nickname,
email,
password,
});
const newUser = await this.usersRepository.save(user);
return newUser;
}
async getAllUsers() {
return await this.usersRepository.find();
}
}
launch.json 추가
클릭 (Node.js)
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug NestJS",
"runtimeExecutable": "yarn",
"runtimeArgs": [
"start:debug" // yarn start:debug
],
"console": "integratedTerminal", // vscode 터미널
"restart": true,
"port": 9229, // NestJS 디버깅 기본 포트
"autoAttachChildProcesses": true // 디버거를 프로세스에 붙일까?
}
]
디버깅 버튼 누르고 시작