NestJS - prisma default CRUD
지난번에, prisma 를 통해 각 모델 스키마를 분리하고 테이블 생성까지 처리 하였었다.
이제 가장 기본이 되는 기본 CRUD를 먼저 구현해 보도록 하자.
User Model을 통한 기본 CRUD 작성
명령어를 통한 module, service, controller 생성
먼저 NestJS에서 제공해주는 CLI 명령어를 통해 response에 관련된 객체들을 생성한다.
가장 기본이 되는 User Modle을 토대로 기본 CRUD를 작성한다.
nest g res modules/user
명령어 입력 후, 선택지가 나오게 되면 아래와 같이 입력한다.
? What transport layer do you use? REST API
? Would you like to generate CRUD entry points? Yes
CREATE src/modules/user/user.controller.ts (917 bytes)
CREATE src/modules/user/user.controller.spec.ts (576 bytes)
CREATE src/modules/user/user.module.ts (250 bytes)
CREATE src/modules/user/user.service.ts (633 bytes)
CREATE src/modules/user/user.service.spec.ts (464 bytes)
CREATE src/modules/user/dto/create-user.dto.ts (31 bytes)
CREATE src/modules/user/dto/update-user.dto.ts (168 bytes)
CREATE src/modules/user/entities/user.entity.ts (22 bytes)
이제, src/modules 폴더 하위에 user 폴더가 생성되었고, 기본적인 REST API가 세팅된 코드 파일들이 만들어 졌다.
디렉토리 구조
modules
└── user
├── dto
│ ├── create-user.dto.ts
│ └── update-user.dto.ts
├── entities
│ └── user.entity.ts
├── user.controller.spec.ts
├── user.controller.ts
├── user.module.ts
├── user.service.spec.ts
└── user.service.ts
기본 CRUD 정의
create-user.dto.ts 정의
user.prisma에서 정의한 model을 참조하여 create-user-dto.ts 파일을 추가적으로 정의해 준다.
export class CreateUserDto {
email: string;
name?: string;
}
user.service.ts 정의
기존에 생성된 create, findAll, findOne, update, remove 에 대하여 먼저 정의한다.
문구는 굉장히 심플하다.
import { Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../../prisma/prisma.service';
@Injectable()
export class UserService {
constructor(private readonly prisma: PrismaService) {}
private readonly model = this.prisma.user;
create(data: Prisma.UserCreateInput) {
return this.model.create({ data });
}
findAll() {
return this.model.findMany();
}
findOne(userWhereUniqueInput: Prisma.UserWhereUniqueInput) {
return this.model.findUnique({
where: userWhereUniqueInput,
});
}
update(params: {
where: Prisma.UserWhereUniqueInput;
data: Prisma.UserUpdateInput;
}) {
return this.model.update(params);
}
remove(userWhereUniqueInput: Prisma.UserWhereUniqueInput) {
return this.model.delete({
where: userWhereUniqueInput,
});
}
}
user.controller.ts 정의
이제 controller에 service 들을 연결해 주도록 하자.
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
} from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.userService.create(createUserDto);
}
@Get()
findAll() {
return this.userService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.userService.findOne({
id: Number(id),
});
}
@Patch(':id')
update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
return this.userService.update({
where: { id: Number(id) },
data: updateUserDto,
});
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.userService.remove({ id: Number(id) });
}
}
POSTMAN을 통한 TEST
등록한 기본 API들을 확인 해 보자.
이번에는 Postman을 이용하였다.
정상적으로 CRUD가 동작 된다.
NestJS - prisma relation CRUD
아직 post table이 남아 있다.
User Model을 확인해 보면, Post Model과 관계를 가지고 있다.
위와 마찬가지로, post도 service, module, controller를 nset cli를 통해 만들어 보자.
CLI + 코드 정의
nest g res modules/post
export class CreatePostDto {
title: string;
content?: string;
published?: boolean;
authorId?: number;
}
import { Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { UpdatePostDto } from './dto/update-post.dto';
import { PrismaService } from '../../prisma/prisma.service';
@Injectable()
export class PostService {
constructor(private readonly prisma: PrismaService) {}
private readonly model = this.prisma.post;
create(data: Prisma.PostCreateInput) {
return this.model.create({ data });
}
findAll() {
return this.model.findMany();
}
findOne(postWhereUniqueInput: Prisma.PostWhereUniqueInput) {
return this.model.findUnique({
where: postWhereUniqueInput,
});
}
update(params: {
where: Prisma.PostWhereUniqueInput;
data: Prisma.PostUpdateInput;
}) {
return this.model.update(params);
}
remove(postWhereUniqueInput: Prisma.PostWhereUniqueInput) {
return this.model.delete({
where: postWhereUniqueInput,
});
}
}
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
} from '@nestjs/common';
import { PostService } from './post.service';
import { CreatePostDto } from './dto/create-post.dto';
import { UpdatePostDto } from './dto/update-post.dto';
@Controller('post')
export class PostController {
constructor(private readonly postService: PostService) {}
@Post()
create(@Body() createPostDto: CreatePostDto) {
return this.postService.create(createPostDto);
}
@Get()
findAll() {
return this.postService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.postService.findOne({
id: Number(id),
});
}
@Patch(':id')
update(@Param('id') id: string, @Body() updatePostDto: UpdatePostDto) {
return this.postService.update({
where: { id: Number(id) },
data: updatePostDto,
});
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.postService.remove({ id: Number(id) });
}
}
테스트를 해 보면 User와 마찬가지로 정상 동작 함을 알 수 있다.
하지만, User 에서 정의한 `posts` 값이나 Post 에서 정의한 `author` 값이 오지 않는다.
service 단에서의 relation 처리
가장 간단하게, service 단에서의 relation 처리 방법에 대해 알아보자.
user service를 예시로 들어 보자.
findAll() {
return this.model.findMany({
include: {
posts: true,
},
});
}
findOne(userWhereUniqueInput: Prisma.UserWhereUniqueInput) {
return this.model.findUnique({
where: userWhereUniqueInput,
include: {
posts: true,
},
});
}
이후 통신을 하게 된다면 아래와 같은 결과값을 확인 할 수 있다.
{
"id": 2,
"email": "jjong2028@gmail.com",
"name": "jjong",
"posts": [
{
"id": 2,
"title": "second post",
"content": "this is second post",
"published": true,
"authorId": 2
}
]
}
하지만 또 문제가 발생한다.
현재는 User 테이블에 password 등의 field가 없지만, 보편적으로는 존재할 것이다.
즉, post 테이블에서 author 을 가져오게 한다면 password도 같이 노출되는 문제가 발생한다.
entity 정의
정상적으로 Nest CLI를 통해 파일들을 만들었다면, entity 파일도 생성이 되었을 것이다.
해당 entity 파일들을 정의하여, 위와 같은 문제를 해결해 보자.
일단 기존 모델에 password 같은건 없으니, email을 password라고 가정하여 없애 보도록 하자.
import { User } from '@prisma/client';
import { PostEntity } from '../../post/entities/post.entity';
export class UserEntity implements User {
id: number;
email: string;
name: string;
posts?: PostEntity[];
constructor(partial: Partial<UserEntity>) {
Object.assign(this, partial);
}
}
import { UserEntity } from '../../user/entities/user.entity';
import { Post } from '@prisma/client';
export class PostEntity implements Post {
id: number;
title: string;
content: string | null;
published: boolean;
authorId: number | null;
author?: UserEntity;
constructor({ author, ...data }: Partial<PostEntity>) {
Object.assign(this, data);
if (author) {
delete author.email;
this.author = author;
}
}
}
controller 재정의
prisma의 반환값이 정의한 Entity를 따르게끔 재 정의를 해 보자.
예재로, post.controller.ts 의 findOne만 변경 해 보았다.
@Get(':id')
async findOne(@Param('id') id: string) {
const post = await this.postService.findOne({
id: Number(id),
});
return new PostEntity(post);
}
TEST
{
"id": 2,
"title": "second post",
"content": "this is second post",
"published": true,
"authorId": 2,
"author": {
"id": 2,
"name": "jjong"
}
}
결과값에서 email이 없어진 것을 확인 할 수 있다.
하지만, 코드가 마음에 들지 않는다.
아래와 같은 부분들이 개선 되어야 한다.
개선되어야 할 점
- 쿼리별로 반환 되어야 하는 filed가 상이 할 때 로직이 복잡 해 짐
- 다양한 model이 있고, 중복되는 hidden, visible 값이 있을 때 각 Entity 별로 constructor 에 유사 코드가 들어감.
- 관계 데이터를 가져 올 때 include로 넣어 줘야 하는 문제
몇가지는 쉽게 개선이 가능 할 것으로 예상 된다.
다음 포스팅에서 알아 보도록 하자.
'JAVASCRIPT > nest.js' 카테고리의 다른 글
[nest.js] prisma schema 분리 (0) | 2024.06.04 |
---|---|
[nest.js] nest.js + prisma - setting (0) | 2024.05.10 |
[nest.js] module reference (2) | 2023.10.02 |
[Nest.js] Circular dependency (0) | 2023.10.02 |