Misc/백엔드

NodeJs Backend REST API (3) - Prisma

foreverWon 2024. 7. 13. 18:19
목차

    1. Prisma를 이용한 Schema 관리

    ORM(Object-relational Mapping): DB데이터(schema)를 객체(object)로 매핑해 주는 역할을 하는 것

    *ORM 툴인 prisma는 DB를 제어하는 sql문을 좀 더 편리하게 개발할 수 있도록 도와주는 orm의 일종입니다.

    *ORM(Object-relational Mapping): DB데이터(schema)를 객체(object)로 매핑해 주는 역할을 하는 것

     

    1) Prisma 설치

    프리즈마도 기본적으로 NodeJS가 설치된 환경에서 사용할 수 있습니다. 

    • npm init -y
      • 초기환된 프로젝트를 생성
    • npm i prisma --save -dev
      • 프리즈마를 설치
    •  package.json 설치 내용들을 확인 가능 

    2) SQLite

    sqlite는 아주 가벼운 RDB로 일반적인 RDB와는 다르게 별다른 환경 설치 없이 임의의 파일 하나만으로 RDB에서의 일반적인 기능들을 사용할 수 있습니다. 그래서 가벼운 프로젝트나 실습 용도로 많이 사용된다.

    • npx prisma init --datasource-provider sqlite

    정상적으로 설치가 되면 prisma>schema.prisma가 생성됩니다. 이곳에 DB 구조를 정의하는 스키마 등을 작성해서 DB에 테이블을 생성하거나 수정할 수 있습니다.

    generator client {
      provider = "prisma-client-js"
    }

    datasource db {
      provider = "sqlite"
      url = env("DATABASE_URL")
    }

    DB와 기본 연결정보가 설정해 보겠습니다. 참고로 여기에서의 url의 경우는 DB의 경로입니다. 생소하겠지만 SQLite의 경우 DB가 파일로 생성이 되는데, 이 파일이 기본적으로 Pisma의 폴더에 생성이 됩니다. 그리고 이 정보는 .env 파일에 설정이 되어있고, 이 설정을 불러와 배치한 내용이 바로 url에 있는 연결정보입니다.

     

    3) Database Schema 

    Prisma가 다른 ORM들과 다른 특징 중 하나는 prisma를 이용해서 DB의 구조 즉 테이블 등을 직접 관리할 수 있다는 것 입니다. 이 말은 DB관리자툴을 사용하지 않고도 prisma만을 이용해 DB 테이블 구조를 생성 및 수정,삭제가 가능합니다. 여기서 DB 테이블 구조는 Schema라고 불리는 것을 작성해서 이를 바탕으로 이루어집니다. 이 스키마 정의는 방금 살펴본 schema.prisma라는 파일에서 이루어집니다.

    • DB 스키마로 DB형태 정의 방법

    model 테이블이름 {
        컬럼이름 타입
    }

     

    • 기본 설정

    #10

    Post라는 테이블 작성

     

    #11~16

    id 칼럼의 타입으로 int 타입 작성

    title, content를 String 타입으로 작성

    published는 true, false만을 사용하는 boolean 타입 작성

    authId라는 작성자 정보를 int로 설정

     

    #19

    작성자 정보인 User 테이블 정의

    고유값인 id를 int로 email과 name을 각각 string으로 타입으로 지정

     

    • 옵션 설정(타입 옆에 설정)

    #11,#20

    Post와 User의 id는 중복되지 않는 고유값이므로 수동보다는 자동으로 증가하게 하는 것이 좋습니다. 그래서 골뱅이기호(@)에 id를 설정하고 @default 안에 autoincrement 라는 기능을 함수 호출 형태로 작성합니다. 이렇게 하면 id는 고유값으로 세팅되고 해당 고유값은 생성될 때 값이 증가되는 형태로 중복없이 만들어지게 됩니다.

     

    #21

    User의 email에 @unique라는 옵션이 있습니다. unique는 중복되지 않은 값들만 넣을 수 있게 하는 옵션입니다.

     

    #13, #15, #22

    타입에 ?를 설정하면 해당 컬럼은 null을 허용합니다.

     

    • 테이블 간의 realation 설정

    #15~16

    구조상 한명의 User는 여러개의 Post를 가질 수 있습니다. 우선 Post에 author라고 정의하고 타입에 대상이 되는 테이블 User를 설정하게 됩니다. 이제 옵션으로 @relation을 작성하고 fields와 referenece라는 설정을 하겠습니다. Post에서 authorId값은 User의 id 를 참조합니다.

    • fields: Post 테이블에 User 정보를 저장하는 영역
    • referenece: 대상 테이블에서의 참조할 컬럼 설정

    #23

    User 테이블에서 posts를 작성하고 타입에 Post에 [대괄호]를 사용해 여러개의 post를 가질 수 있음을 나타내줍니다. 

     

    • onDelete, onUpdate 옵션

    #15

    Post의 경우 User와 관계를 맺고 있습니다. 만약 user가 삭제되면 해당 user를 참조하는 post가 있어 삭제 시 오류가 발생하고 삭제가 이루어지지 않습니다. 이때 onDelete Cascade 옵션을 사용해 자동으로 연관된 테이블들을 삭제할 수 있게 해줍니다. 

     

    • 세부타입 정의

    DB마다 타입에 조금씩 차이가 있습니다. Prisma에서는 이를 단순화해서 Int, String 과 같이 정의 내리지만 실제 DB는 이를 바탕으로 내부적인 타입이 정의됩니다.

    #12

    @db를 작성하고 string에 해당하는 varchar 그리고 문자사이즈에 해당하는 숫자를 입력해서 설정할 수 있습니다. 

     

    4) Database 마이그레이션

    설정된 스키마를 바탕으로 실제 DB에 테이블을 만드는 방법을 알아보겠습니다. 

    • npx prisma migrate dev --name init

    dev.db 파일이 하나 생성되는데 이게 바로 sqlite DB에 해당합니다. 

    그리고 migration 폴더가 하나 있고 여기에 날짜와 방금 설정한 init 옵션이 적용된 폴더가 있을겁니다. 여기에는 sql문으로 어떤 내용으로 쿼리가 만들어져서 여기에 테이블들이 생성되었는지에 대한 정보를 확인할 수 있습니다.

    generator client {
      provider = "prisma-client-js"
    }

    datasource db {
      provider = "sqlite"
      url = env("DATABASE_URL")
    }

    model Post {
      id        Int     @id @default(autoincrement())
      title     String
      content   String?
      author    User    @relation(fields: [authorId], references: [id], onDelete: Cascade, onUpdate: Cascade)
      authorId  Int
    }

    model User {
      id       Int      @id @default(autoincrement())
      email    String   @unique
      name     String?
      posts    Post[]
    }

     


     

    2. Prisma Client

    Prisma를 이용하여 DB를 제어하기 위해 prisma client가 필요합니다.

    • npm i @prisma/client
    • package.json에서 설치 확인 가능
    • 사용방법
    import { PrismaClient } from "@prisma/client";

    const prisma = new PrismaClient()

     

    1) Create

    ● 사용 방법

    prisma.테이블명.create({
        data: {
            컬럼: '데이터',
            ....
        }
    })

     

    - create

     

    prisma.user.create({
        data: {
            email: 'foreverWon@prisma.io',
            name: 'Won',
        },
    })

     

    - createMany

    : 여러개의 데이터를 한번에 입력하기 위해 사용하고, data에 배열을 나타내는 기호 안에 JSON과 비슷한 형태로 입력하면 된다.

    prisma.user.createMany({
        data: [
            {name: 'Bob', email: 'bob@prisma.io'},
            {name: 'Bobo', email: 'bob@prisma.io'}, // Duplicate unique key!
            {name: 'Yewande', email: 'yewande@prisma.io'},
            {name: 'Angelique', email: 'angelique@prisma.io'},
        ],
        skipDuplicates: true, // Skip 'Bobo'
    })

     

    - 관계형 데이터

    prisma.user.create({
        data: {
            email: 'foreverWon@prisma.io',
            name: 'Won',
            posts: {
                create: {
                    title: 'Include this post!'
                },
            },
        }
    })
     
    const createUser = await prisma.user.create({ data:user }) // data 부분 분리해서 작성 가능

     

    2) Read

    - findUnique, findFirst 

    : 하나의 데이터를 읽어옴

    • findUnique: 중복되지 않은 하나의 값 읽어옴
    • findFirst: 중복이 되더라도 제일 우선시 되는 하나의 값을 읽어옴
    prisma.테이블명.findUnique()
    prisma.테이블명.findFirst()

     

    - findMany

    : 하나의 데이터가 아닌 여러개의 데이터를 읽어옴

    const users = await prisma.user.findMany()

     - where 옵션

    : 데이터 필터링

    // By unique identifier
    const user = await prisma.user.findUnique({
        where: {
            email: 'foreverWon@prisma.io',
        },
    })

    // By ID
    const user = await prisma.user.findUnique({
        where: {
            id: 99,
        },
    })
    const users = await prisma.user.findMany({
        where: {
            email: {
                endsWith: 'prisma.io'
            }
        },
    })

     

     - AND/OR 옵션

    : 사용하면 더욱 복잡한  조건의 필터링도 가능합니다. 

    const users = await prisma.user.findMany({
        where: {
            OR: [
                {
                    name: {
                        startsWith: 'E',
                    },
                },
                {
                    AND: {
                        profileViews: {
                            gt: 0,
                        },
                        role: {
                            equals: 'ADMIN',
                        },
                    },
                },
            ],
        },
    })

    관계형으로 이어진 테이블에 대해서도 필터링이 가능합니다.

    const users = await prisma.user.findMany({
        where: {
            email: {
                endsWith: 'prisma.io'
            },
            posts: {
                some: {
                    published: false
                }
            },
        }
    })

    user 테이블에서 email에 필터링하고 이와 연관된 post 테이블에서 published가 false인 값들을 가져오게 설정한 것을 볼 수 있습니다.

     

    가져온 데이터를 정렬하기 위한 옵션인 orderBy도 설정할 수 있습니다. 

    const findUser = await prisma.user.findFirst({
        where: {
            ...
        },
        orderBy: {
            id: 'desc' // asc: 오름차순, desc: 내림차순
        }
    })

     

    select 옵션

    테이블의 모든 컬럼을 가져오지 않고 필요한 컬럼만을 가져오는 경우

    const user = await prisma.user.findUnique({
        where: {
            email: 'emma@prisma.io',
        },
        select: {
            email: true,
            name: true,
        },
    })

    select로 email과 name에 해당하는 목록만을 받게 됨

    const users = await prisma.user.findMany({
        select: {
            name: true,
            posts: {
                select: {
                    title: true,
                },
            },
        },
    })

    user 테이블의 select옵션에서 다시 post 테이블의 select옵션을 사용

     

    관계형으로 이어진 테이블을 가져오는 다른 방법으로 include옵션이 있습니다. 

    const users = await prisma.user.findMany({
        where: {
            role: 'ADMIN',
        },
        include: {
            posts: true,
        },
    })

    user 테이블에 연관된 post 테이블을 가져오기 위해 include 옵션에 posts를 true로 설정

    이때 select나 include해서 사용하는 대상 테이블의 경우 스키마에 정의된 테이블 정보임을 기억해야 합니다. 예제의 경우 실제 테이블명은 post지만 user 테이블에 연계된 정보로 1대 다에 해당하는 복수형인 posts로 작성된 부분의 이름을 사용했습니다. 

     

    Update

    데이터 수정에 해당하는 기능

    prisma.테이블명.update({})
    const updateUser = await prisma.user.update({
        where: {
            email: 'viola@prisma.io',
        },
        date: {
            name: 'Viola the Magnificent',
        },
    })

    user 테이블에서 where 옵션으로 특정 이메일을 가진 값을 수정하게 하고 실제 업데이트될 내용은 create 같이 데이터의 정보들을 입력하면 됩니다. 

    const updateUsers = await prisma.user.updateMany({
        where: {
            email: {
                contains: 'prisma.io',
            },
        },
        date: {
            role: 'ADMIN',
        },
    })

    여러 데이터를 업데이트할 경우 updateMany 사용

    update 옵션 중에서 자주 사용하는 옵션으로 특정 숫자를 증가시키는 기능이 있습니다. 예를 들어 좋아요나 뷰 카운트의 경우 이를 증가시키거나 감소하는 경우입니다. 

    const updatePosts = await prisma.post.updateMany({
        date: {
            views: {
                increment: 1.
            },
            likes: {
                decrement: 1,
            },
        },
    })

    증가에는 increment 감소에는 decrement 옵션을 주고 필요한 숫자를 넣어서 사용할 수 있습니다.

    숫자 필드 관련 옵션

    increment 현재 값에 n을 더함
    decrement 현재 값에 n을 뺌
    multiply 현재 값에 n을 곱함
    divide 현재 값을 n으로 나눔
    set  현재 필드 값을 바로 설정. { myField: n } 과 동일

     

     

    Delete

    값을 삭제하는 방법 

    prisma.테이블명.delete()
    const deleteUSer = await prisma.user.delete({
        where: {
            email: 'bert@prisma.io',
        },
    })

    where 옵션으로 삭제할 조건 입력

    const deleteUSer = await prisma.user.deleteMany({
        where: {
            email: {
                contains: 'prisma.io'
            },
        },
    })

    참고로 delete의 경우 다중조건을 넣어야 한다면 무조건 deleteMany를 사용해야합니다. 그렇지 않을 경우 오류가 발생하니 이부분 기억 바랍니다. 

    728x90

    'Misc > 백엔드' 카테고리의 다른 글

    NodeJs Backend REST API (2) - Fastify  (0) 2024.07.03
    NodeJs Backend REST API (1) - Typescript  (0) 2024.07.03
    HTTP  (0) 2024.06.25
    API  (0) 2024.06.25
    백엔드의 전체 흐름  (0) 2024.06.24