Node 강의/숙련

1-8 Prisma Method(+Posts 게시판 만들기 서버와 DB 연결)

kagan-draca 2024. 9. 7. 00:02

1. Prisma의 Method 살펴보기 및 밑 작업하기

Prisma는 mongoose와 동일하게,

findMany(), findFirst(), findUnique() 등 다양한 메서드를 지원합니다.

 

mongoose를 사용했을 때는 Schema를 이용하였다면,

Prisma에서는 Prisma Client를 이용해 MySQL의 데이터를 조작할  것 입니다.

 

저번 페이지에서 만든 Posts 테이블에서 생성수정할 때 필수 인자값 3개

이용해 권한 검증데이터 생성구현해보겠습니다.

 

먼저, routes/posts.router.js 파일 생성

 

import express from "express"
import { PrismaClient } from "@prisma/client"

const router = express.Router();

const prisma = new PrismaClient({
    // Prisma를 이용해 데이터베이스를 접근할 때, SQL을 출력해줍니다.
    log : ['query','info','warn','error'],

    // 에러 메시지를 평문이 아닌, 개발자가 읽기 쉬운 형태로 출력해줍니다.
    errorFormat : 'pretty'
}) // PrismaClient 인스턴스를 생성합니다.

export default router

 

import { PrismaClient } from "@prisma/clinet"

 

로 PrismaClient를 prisma/client 라이브러리에서 가져옵니다.

 

그래서, CLI 환경에서는 Prisma 패키지 자체를 사용하지만,

 

Node.js 코드 환경에서는 라이브러리를 통해 PrismaClient로

DB와 통신을 해서 설정을 한다.

 

const prisma = new PrismaClient()

 

는 PrismaClient Class의 생성자로 인스턴스를 생성한다.

 

이렇게 할당된 prismaClient 인스턴스를 활용해서

Posts 테이블에 해당하는 데이터를 생성하거나, 수정, 삭제할 수 있다.

 

이때,

 

{
    log : ['query','info','warn','error'],
    errorFormat : 'pretty'
}

 

추가적인 기능을 객체 내부에 지정해줄 수 있는데,

 

log 라는 옵션error 출력형식의 속성을 지정해줄 수 있습니다.

 

log : ['query','info','warn','error']

 

설정을 통해

 

query 실행이나,

info로 데이터베이스 내 정보성 메시지 전달,

warn로 문제 발생 시 원인 분석,

error로 query를 했을 때 데이터 자체 에러 발생 내용

 

터미널 내에서 출력할 수 있도록 해줄 수 있습니다.

 

그리고, 출력 형식을

 

errorFormat : 'pretty'

 

를 통해 개발자가 이해하기 쉬운 형태로 출력해줍니다.

 

다 작성을 완료했다면

 

app.js에

 

import express from "express";
import postsRouter from "./routes/posts.router";
const app = express();
const PORT = 3000;

app.use(express.json());
app.use("/api", [postsRouter]);

app.listen(PORT, () => {
  console.log(PORT + "로 서버에 연결 성공");
});

 

위와 같이 작성해 Server와의 연결 및 postsRouter가 사용될 수 있게 해줍니다.

 

이제 Prisma 게시글 API 명세서를 바탕으로 API를 구현해봅시다.

 

1) Prisma 게시글 생성(Create) API

 

게시글 생성 API의 비즈니스 로직

1. title, content, password를 body로 전달받는다.

2. title, content, password를 이용해 Posts 테이블에 데이터를 삽입한다.

3. 생성된 게시글을 반환한다.

 

 
router.post("/posts", async (req, res, next) => {
  const { title, content, password } = req.body;
  const post = await prisma.Posts.create({
    data: {
      title,
      content,
      password,
    },
  });
  // 데이터 삽입을 SQL에서는 Insert Into지만,
  // Prisma에서는 prisma.지정한테이블이름.create({data : {values}})
  // 로 가능하다.
  return res.status(201).json({ data: post });
});

 

위와 같이 작성할 수 있습니다.

 

SQL에서는 Insert Into로 값들을 테이블에 삽입하지만,

 

Prisma에서는 prisma.지정한 테이블 이름.create({data : {값들}}) 로 가능했다.

 

Posts는

의 "Posts"를 가져온 것 입니다.

 

이제, yarn dev로 

 

package.json의 "scripts"에 있는 "dev"를 바탕으로,

yarn dev로 app.js를 실행시킨다.

 

그 후, Insomnia를 실행시켜 테이블의 열(Colum)에 해당하는 values들을 넣어주면

 

정상적으로 열(Colum)에 해당하는 값들이

잘 들어간 걸 볼 수 있다.

 

2) Prisma 게시글 조회(Read) API

 

- 게시글 목록 조회

- 게시글 상세 조회

 

를 구현해보겠습니다.

 

- 게시글 목록 조회

router.get("/posts", async (req, res, next) => {
  const posts = await prisma.Posts.findMany({
    select: {
      postId: true,
      title: true,
      content: true,
      createdAt: true,
      updatedAt: true,
    },
  });

  //prisma.테이블 이름.findMany({ select : {값들 출력 여부 : true(출력)},})
  //findMany : 테이블 전체 조회
  //findFirst : 조건에 맞는 첫 번 째 행(tupple, row, recode)을 제공한다.
  //findFirstOrThrow : 조건에 맞는 첫 번째 레코드를 찾는데 없을 경우 예외를 발생.
  //findUnique : Unique 필드를 기반 조건에 맞는 첫 번째 레코드만 제공한다.
  //findUniqueOrThrow : Unique 필드를 기반 조건에 맞는 첫 번째 레코드를 찾는데 없을 경우 예외 발생
  return res.status(200).json({ data: posts });
});

로 구현 가능하고,

 

만약, Colum Name : False를 줄 경우 오류가 발생하게 됩니다.

 

Insomnia를 실행시켜 확인해보면

위와 같이 객체 내 "data" Key에 해당하는 Value가 배열 형태로 출력된 것을 볼 수 있고,

 

한 Row에 해당하는 값들이 객체 형식으로 반환된 것을 볼 수 있다.

 

- 게시글 상세 조회

router.get("/posts/:postId", async (req, res, next) => {
  const { postId } = req.params;
  const post = await prisma.Posts.findFirst({
    where: {
      postId: +postId,
    },
    select: {
      postId: true,
      title: true,
      content: true,
      createdAt: true,
      updatedAt: true,
    },
  });
  // where : {테이블의 Colum : 찾고자 하는 값 가진 변수}
  //으로 비교를 먼저한다! 이때, postId는 Int 형이기 때문에,
 
  //Number(찾고자 하는 값 가진 변수)
  //ParseInt(찾고자 하는 값 가진 변수)
  //+(단항 플러스 연산자) 찾고자 하는 값 변수
 
  //(덧셈으로 인식해서 암묵적 형변환이 발생했을거라 생각한다.)
  //로 해야 정상 동작하게 된다.

  // 조건에 부합하는 row는
  // select : {Colum : true}로 출력여부를 결정해줄 수 있다.
  return res.status(200).json({ data: post });
});

으로 찾고자 하는 값을 통한 Colum을 제공 받을 수있다.

 

Insomnia를 실행시켜 확인해보면

위와 같이 

 

찾고자 하는 postId에 해당하는

출력 여부가 true인

결과들이 출력되는걸 볼 수 있다. 

 

3) Prisma 게시글 수정(Update) API

 

게시글 수정 API의 비즈니스 로직

  1. Path Parameters로 어떤 게시글을 수정할 지 postId를 전달받습니다.
  2. 변경할 title, content와 권한 검증을 위한 password를 body로 전달받습니다.
  3. postId를 기준으로 게시글을 검색하고, 게시글이 존재하는지 확인합니다.
  4. 게시글이 조회되었다면 해당하는 게시글의 password가 일치하는지 확인합니다.
  5. 모든 조건을 통과하였다면 게시글을 수정합니다.
// 게시글 수정 API
router.put("/posts/:postId", async (req, res, next) => {
  const { postId } = req.params;
  const { title, content, password } = req.body;

  const post = await prisma.Posts.findUnique({
    where: {
      postId: +postId,
    },
  });
  //findFirst도 가능하지만
  //findUnique로 중복이 없는 값을 통해
  //찾고자 하는 row를 찾는다.

  if (!post) res.status(404).json({ massage: "게시글이 존재하지 않습니다." });
  // 찾고자 하는 row가 없다면

  if (post.password !== password)
    res.status(401).json({ massage: "비밀번호가 일치하지 않습니다" });
  // 찾고자 하는 row의 password와 Client가 제공한 password가 다르다면

  await prisma.Posts.update({
    where: {
      postId: +postId,
      password: password,
    },
    data: {
      title: title,
      content: content,
    },
  });

  //prisma.테이블 이름
  //.update({where:{테이블 Colum Name : 찾고자 하는 Colum Name}
  //,data : {변경할 Colum Name},})
  // where 조건에 row를 찾고
  // data를 row에 Colum 값 변경한다.

  return res.status(201).json({ data: "게시글이 수정되었습니다." });
});

 

Insomnia를 실행시켜 확인해보면

password : "1234"로 일치하는

 

title, content 값을 변경합니다.

 

그리고 조회해보면

 

변경된 결과를 확인할 수 있습니다.

 

에러를 발생시켜보면,

1) postId 에러

 

 

postId에 작성한 에러가 출력되고,

 

2) password 에러

 

 

password에 작성한 에러가 출력됩니다.

 

그리고 

게시글 수정(Update) 및 즉시 조회(Select)

const updatedPost = await prisma.Posts.findUnique({
    where: {
      postId: +postId,
    },
    select: {
      postId: true,
      title: true,
      content: true,
      createdAt: true,
      updatedAt: true,
    },
  });

  // 수정된 게시글 정보를 클라이언트에게 반환
  return res.status(200).json({ data: updatedPost });

위의 코드를 수정 문 밑에 추가하면 수정에 따른 즉시 조회가 가능하다.

 

Where 속성에 여러개의 조건

 

SQL에서는 특정한 데이터를 검출하기 위해 where절에서

OR(||), AND(&&), LIKE, 정규표현식 등 다양한 연산자를 사용합니다.

 

Prisma의 where 절은 여러개의 조건이 들어올 경우 AND(&&) 연산자를 사용하는 것과 동일한 결과를 출력합니다.

 

하지만, 이외의 연산자를 사용하고 싶다면, 아래와 같은 문법으로도 사용할 수 있습니다.

 

  await prisma.Posts.findMany({
    where: {
      OR: [
        {
          email: {
            endsWith: "prisma.io",
          },
        },
        {
          email: {
            endsWith: "gmail.com",
          },
        },
      ],
      NOT: {
        email: {
          endsWith: "hotmail.com",
        },
      },
    },
  });

 

4) Prisma 게시글 삭제(Delete) API

 

게시글 삭제 API는 게시글 수정 API와 동일한 로직을 수행하지만,

Body에서 password만 전달 받은 것이 유일한 차이점입니다.

 

게시글 삭제 API의 비즈니스 로직

 

1. Path Parameters로 어떤 게시글을 수정할 지 postId를 전달받습니다.

2. 권한 검증을 위한 password를 body로 전달 받습니다.

3. postId를 기준으로 게시글을 검색하고, 게시글이 존재하는지 확인합니다.

4. 게시글이 조회되었다면 해당하는 게시글의 password가 일치하는지 확인합니다.

5. 모든 조건을 통과하였다면 게시글을 삭제합니다.

 

// 게시글 삭제 API
router.delete("/posts/:postId", async (req, res, next) => {
  const { postId } = req.params;
  const { password } = req.body;
  // 게시글을 작성한 사람인지 확인을 위해 사용

  let post = await prisma.Posts.findFirst({
    where: {
      postId: +postId,
    },
  });
  // 게시글 유무 판단 및 비밀번호 비교를 위해 select 문 사용
  if (!post) res.status(404).json({ massage: "게시글을 찾지 못 했습니다" });

  if (post.password !== password)
    res.status(401).json({ message: "비밀번호가 일치하지 않습니다" });

  await prisma.Posts.delete({
    where: {
      postId: +postId,
    },
  });
  // 모든 조건을 만족했다면 게시글 Id를 비교해 해당 게시글을 삭제한다.

  // 삭제 및 즉시 조회 코드
  // post = await prisma.Posts.findMany({
  //   select: {
  //     postId: true,
  //     title: true,
  //     content: true,
  //     createdAt: true,
  //     updatedAt: true,
  //   },
  // });
  // return res.status(200).json({ data: post });

  return res.status(200).json({ massage: "게시글이 삭제 됐습니다." });
});

위와 같은 코드로 postsId를 확인 및 작성자 password를 확인하고,

게시글을 삭제시킵니다.

 

Insomnia를 실행시켜 확인해보면

 

Client에게 응답으로 게시글이 삭제 됐다는 걸 전달해주고,

 

                                ( 변경 전 )                                                                                  ( 변경 후 )

 

DB의 해당 게시글이 삭제된 것을 확인할 수 있습니다.

 

에러를 발생시켜보면,

1) postId 에러

 

2) password 에러

 

정상적으로 에러 메시지가 Client에게 응답된 것을 확인할 수 있습니다.

 

삭제 및 즉시 조회

 

해당 게시글이 삭제되고, 즉시 조회가 되는 것을 확인할 수 있습니다.

 

2. Prisma 리팩토링(PrismaClient 파일 따로 만들기)

PrismaClient는 Prisma를 사용하여 실제 DB와의 연결을 관리하는 객체입니다.

new PrismaClient()를 이용해 JavaScript에서 Prisma를 사용할 수 있도록 인스턴스를 생성합니다.

 

const prisma = new PrismaClient()

 

현재는 게시글(Posts) 라우터만 구현하고 있지만,

사용자(Users) 라우터, 사용자 정보(UserInfo), 해시 태크(HashTags)

같은 여러 라우터들이 추가된다면, 각 라우터 개수마다

데이터 베이스와 연결하는 문제가 발생하게 됩니다.

 

여러 번 데이터베이스의 연결을 생성한다면, 리소스가 과도하게 사용돼

어플레이케션 성능이 저하됩니다. 따라서, 최대한 데이터베이스의 연결을

줄이는 것이 효율적인 방법 입니다.

 

/utils/prisma/index.js 를 생성합니다.

 

import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient({
  // Prisma를 이용해 데이터베이스를 접근할 때, SQL을 출력해줍니다.
  log: ["query", "info", "warn", "error"],

  // 에러 메시지를 평문이 아닌, 개발자가 읽기 쉬운 형태로 출력해줍니다.
  errorFormat: "pretty",
}); // PrismaClient 인스턴스를 생성합니다.

export default prisma;

 

그리고 위의 코드 처럼 PrismaClient와의 연결 부분만 작성하고

 

export default prisma

 

로 외부에서 사용할 수 있게 해줍니다.

 

그후

 

app.js에서는

import { PrismaClient } from "@prisma/client";
 
const prisma = new PrismaClient({
  // Prisma를 이용해 데이터베이스를 접근할 때, SQL을 출력해줍니다.
  log: ["query", "info", "warn", "error"],

  // 에러 메시지를 평문이 아닌, 개발자가 읽기 쉬운 형태로 출력해줍니다.
  errorFormat: "pretty",
}); // PrismaClient 인스턴스를 생성합니다.

 

을 지우고, ( const router = express.Router(); 이 안 지워지게 조심해야 한다.)

import prisma from "../utils/prisma/index.js";

 

utils/prisma/index.js의 prisma를 import 해줍니다.

그 결과,

 

 

정상적으로 서버가 작동하고

API 도 동작하는 것을 확인할 수 있습니다.

'Node 강의 > 숙련' 카테고리의 다른 글

1-10 JWT  (0) 2024.09.07
1-9 쿠키와 세션  (0) 2024.09.07
1-7 ORM과 Prisma(+ Posts 게시판 만들기 DB 밑 작업)  (0) 2024.09.06
1-6 Raw Query시작하기  (0) 2024.09.06
1-5 SQL 제약조건  (0) 2024.09.05