Prisma + PlanetScale + Next

hwisaac·2023년 2월 17일
1

당근마켓클론코딩

목록 보기
1/2

Prisma

  • Prisma 는 Node.js 와 Typescript ORM 입니다
  • SQL 같은 데이터베이스 언어를 안쓰고 Typescript 코드만 작성할 수 있습니다.
  • 한마디로 Prisma 는 편리하게 DB를 사용할 수 있게 해줍니다.

ORM (Object Relational Mapping) 이란?

객체와 관계형 데이터베이스의 데이터를 자동으로 매핑해주는 것을 말합니다.
일종의 번역기라 보면 되는데, 자바스크립트(타입스크립트) 코드와 DB 사이를 연결해주는 것입니다.

  • Prisma 를 사용하기 전에 schema.prisma 파일을 통해 데이터베이스가 어떻게 생겼는지 알려줘야 합니다.

    schema.prisma 는 데이터베이스에 대한 모든 설명을 담은 파일입니다.

  1. content String? : string 타입. required는 아님
  2. published Boolean @default(false) : boolean타입. 디폴트값은 false
  3. author User? @relation(fields : [authorId], references: [id]) : User타입(User는 또다른 model)
  • Prisma 에 설명해주면 Prisma 는 데이터베이스의 타입을 알게되고 client 를 생성해 줄 수있습니다.
  • client 를 이용하면 타입스크립트로 데이터베이스와 직접 상호작용할 수 있습니다.
  • Prisma Studio 를 통해 시각화된 데이터를 읽을 수도 있습니다.

Setup

  1. 우선 vscode 에서 Prisma extension 을 설치해줍니다.
  2. npm i prisma -D : prisma 설치
  3. npx prisma init : prisma 초기 세팅
    1. .env 파일에 있는 DATABASE_URL 을 설정해줍니다. -> PlanetScale
    2. prisma/schema.prisma 파일에서 datasource의 provider(사용할 DB)를 설정해줍니다. -> MySQL
    3. DB에 사용할 model 을 만듭니다.
// prisma/schema.prisma
// learn more about it in the docs: https://pris.ly/d/prisma-schema

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

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

model User {
  id Int @id @default(autoincrement()) // id로 구분할수 있으며 디폴트로 증가하는 값이다.
  phone Int? @unique // 옵셔널한 Int값이며 유니크하다
  email String? @unique
  name String
  avatar String?
  createdAt DateTime @default(now()) // 현재날자를 디폴트로한다
  updatedAt DateTime @updatedAt // 업데이트마다 이 필드가 변한다
}
  • prisma 는 schema.prisma 파일을 읽고 변경점을 deploy 합니다
  • schema.prisma 파일을 읽고 타입스크립트 client 를 생성해 줍니다.

PlanetScale

  • MySQL 과 호환되는 serverless 데이터베이스 플랫폼
  • Database platform : 데이터베이스를 제공합니다.
  • serverless: 서버를 관리 및 유지보수할 필요가 없습니다
  • vitess 를 사용한다: scaling 기능이 뛰어난 오픈소스 데이터베이스 클러스터링 시스템
  • git 을 사용하는 것처럼 데이터베이스를 사용할 수 있는 CLI 를 제공합니다.

설치하기

  1. brew install planetscale/tap/pscale
  2. brew install mysql-client
  3. brew upgrade pscale : 최신버전으로 업데이트
  4. pscale auth login : 계정로그인
  5. 이제 CLI 기능을 이용할 수 있다.

PlanetScale 사용하기 문서

명령어---
pscale region list서버 목록을 보여준다
pscale database create <DB이름>DB를 생성한다
pscale database create <DB이름> -region <SLUG명>해당 지역 서버에 DB를 생성한다.
pscale connect <DB명>PlanetScale서버의 DB와 연결한다.
npx prisma db pushDB정보를 push한다
npx prisma studioprisma studio 열기

DB를 생성하고 연결하기

  1. pscale database create <DB이름> -region ap-northeast 로 도쿄 서버에 DB를 생성합니다.

보통 DB 플랫폼에서는 DB를 만들면 암호를 생성하고 관리해야 하는데 보안에 취약할 수 있기 때문에, 진짜DB 대신 서버를 작동시킬수 있는 가짜DB 를 사용합니다. 이 후 실제 배포할 때 AWS 나 Heroku를 이용하면 됩니다.

planetscale 에서는 보안 tunnel 을 사용할 수 있습니다(가짜 DB를 다운받고 설치하고 실행할 필요가 없고, .env 에 정보를 보관할 이유도 없습니다.)

  1. pscale connect <DB명> 를 하면 PlanetScale 서버의 DB와 연결됩니다.
  2. 연결시 나오는 주소를 복사하여 .env 의 DATABASE_URL 에 넣어줍니다.
// .env
DATABASE_URL = "mysql://127.0.0.1:3306/<DB명>";

DB 를 push 해보기

planetscalemysql 과 '호환'되는 플랫폼입니다. 이 호환을 위해서 몇가지 처리해줄 것이 있습니다.

foreign key 제약
> planetscalemysql 과 달리 foreign 키로 DB에 해당 데이터가 있는지 체크하는 기능이 없기 때문에 안정성을 보완해줘야 합니다.

  1. schema.prisma 파일의 datasource db 에다가 relationMode ="prisma" 를 추가해줍니다.
// schema.prisma

datasource db {
provider = "mysql"
url = env("DATABASE_URL")
relationMode = "prisma"
}
  1. npx prisma db push 를 하면 schema.prisma 를 읽고 해당 정보를 push 합니다.

Prisma Client 가 생성됨

  1. https://app.planetscale.com/[계정명]/[DB명]/branches - [main] 브랜치 - [Schema] 탭 - [Refresh Schema] 를 눌러서 확인하면 SQL 버전의 스키마를 볼수 있습니다.

Prisma Studiio

  • npx prisma studio
  • [Add record] 버튼을 누르면 데이터를 추가할 수 있습니다.

Prisma Client

클라이언트 초기화 하는 방법:

  1. npm i @prisma/client
  2. libs폴더에 client.ts 파일을 만듭니다
  3. client.ts 파일에 다음을 작성합니다.
import { PrismaClient } from "@prisma/client";

export default new PrismaClient();
  1. 이제 client 인스턴스를 이용해서 DB와 소통할 수 있습니다.
  2. npx prisma generate 를 하면 node_modules/.prisma/client/index.d.ts 에 우리의 스키마에 해당하는 type 이 생성된 걸 알 수 있습니다.

6. 또한 PrismaClient 의 인스턴스로 자동완성으로 user모델을 찾을 수 있는 걸 알 수 있습니다.

사용예시

// libs/client.ts
import { PrismaClient } from "@prisma/client";

const client = new PrismaClient();

client.user.create({
  data: {
    id: 12,
    name: "helo",
  },
});

export default client;

API Routes

PrismaClient 는 브라우저에서 작동하진 않습니다. 왜냐면 db에 접근할수 있는 파일을 프론트엔드인 브라우저에 추가하기 때문

// pages/indes.tsx
import "../libs/client"; // 에러발생
  • 원래는 react는 프론트엔드를, node.js 는 백엔드를 다뤄야 합니다.
  • 그런데, NextJS 는 API 를 만들기 위해 반드시 서버를 따로 구축할 필요가 없습니다!
  • pages/api 폴더를 생성하면 NextJS 서버에 API 가 생성됩니다.

api 폴더 내의 파일은 export default 로 함수를 내보내야 합니다.

api 라우트 만들기

  • pages/api 폴더에서 connection 핸들러인 함수를 export default 합니다.
// pages/api/client-test.tsx
import { NextApiRequest, NextApiResponse } from "next";

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  res.json({
    ok: true,
    data: "abc",
  });
}

NextApiRequest

NextApiResponse

  • /api/client-test 에 해당하는 url 에 가면 위에서 정의한 json이 나옵니다

client-test 에 PrismaClient 를 import 해보자

// pages/api/client-test.tsx

import { NextApiRequest, NextApiResponse } from "next";
import client from "../../libs/client";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  await client.user.create({
    data: {
      email: "abc@naver.com",
      name: "hello",
    },
  });
  // res.status(200).end(); 로 하면 항상 200 신호를 보냅니다
  res.json({
    ok: true,
  });
}

(브라우저) 핸들러가 실행되면 DB를 업데이트하고 다음 json을 리턴합니다.

(PrismaStudio) DB가 업데이트 된 모습

예시

유효한 form 에서 submit

// enter.tsx
const [submitting, setSubmitting] = useState(false);
const { register, handleSubmit, reset } = useForm<EnterForm>();

const onValid = (data: EnterForm) => { // submit 이 유효할 경우 작동
  setSubmitting(true);
  fetch("/api/users/enter", {
    method: "POST",
    body: JSON.stringify(data),
    headers: {
      "Content-Type": "application/json",
    },
  }).then(() => {
    setSubmitting(false);
  });
};
// return (
<form
  onSubmit={handleSubmit(onValid)}
  className='flex flex-col mt-8 space-y-4'>
// pages/api/users/enter.tsx
import { NextApiRequest, NextApiResponse } from "next";
import client from "../../../libs/client";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method !== "POST") {
    res.status(401).end(); // POST 가 아닌 메소드로 보내면 에러발생
  }
  console.log(req.body.email); // undefined 나올수 있음
  console.log(req.body); // {"email":"abc@naver.com"}
  res.status(200).end();
}
  • 헤더를 빼놓고 요청할 경우 req.body 는 'req의 인코딩'을 기준으로 parse하기 때문에 req.body.email 로 접근하면 undefined 가 나옵니다
  • 프론트에서 요청할 때 headers 를 설정해줘야만 합니다 : header : { "Content-Type" : "application/json" }

커스텀 훅 만들기 순서

  1. 사용자 경험을 결정합니다. 해당 훅이 무엇을 인자로 받고, 무엇을 리턴할지, 리턴값으로 로직을 어떻게 간추릴 것인지 정합니다.
  2. 위에서 정한 조건에 맞게 훅의 인풋과 리턴, 그리고 타입에 대해서 정의합니다.
  3. 훅의 로직을 정의합니다.

상대경로에서 절대경로 바꾸기

  1. tsconfig.json 파일에서 baseUrl: "." 를 추가합니다 : 프로젝트의 baseUrl 을 tsconfig.json 파일의 위치로 함
  2. paths 추가합니다 : 파일과 컴포넌트들을 어떻게 import 할지 정합니다.
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@libs/*": ["libs/*"], // "@libs/*" 라는 경로는 libs폴더를 의미한다.
      "@components/*": ["components/*"]
    }
  }
}
  • "../../../libs/server/withHandler" -> "@libs/server/withHandler"
  • "../../components/abc.ts" -> "@components/abc.ts" 로 쓸 수 있습니다.

0개의 댓글