JS

Prisma 란?

728x90

# Prisma 2 

Prisma 의 정의를 보면 아래와 같이 서술 되어 있다.

 

"Prisma is an open-source database toolkit. It replaces traditional ORMs and makes database access easy with an auto-generated query builder for TypeScript & Node.js."

 

-> 말이 거창하게 써있는것 처럼 보이는데 그냥 JS, TS 에서 쓰는 ORM 이다.

 

 

* Prisma 의 연산 과정

Prisma 가 작업을 수행하는 과정을 대략적으로 요약하면 아래 그림과 같다

출처 : https://www.prisma.io/docs/understand-prisma/under-the-hood

Node.js 에서 (백엔드 서버 단) Prisma Client 를 생성하고, 

Query Engine 을 이용해서 DB 와 연결한다

그리고 백엔드 서버에서 어떤 요청을 DB 로 보내면

Query Engine 이 이에 맞춰서 SQL 로 전환하여 DB 에서 연산을 수행하도록 하게 하고,

이에 대한 결과를 JS 의 Object 값으로 받아온다

(보통 Prisma 를 사용하면 GraphQL 을 이용해서 DB 를 이용하는것이 일반적이다)

 

 

그리고 Prisma 와 DB 간에 연산을 위한 대표적인 두 명령어가 있는데

prisma migrate 와 prisma introspect 이다.

 

 

* Prisma Introspection

 

Introspection 은 한마디로 말하면 DB 에 있는 스키마를 그대로 받아와서 prisma 의 schema 로 전환하는 것이다.

출처 : https://www.prisma.io/docs/reference/tools-and-interfaces/introspection

DB 에 이미 스키마가 정의되어 있거나, 어떤 변화가 DB 내부에서 생긴경우,

이 변화된 값을 Prisma 에서도 알아야 맞춰갈 수 있기 때문에,

이 명령어를 사용해서 Prisma 에도 변경된 사항을 알려줘야 한다.

 

 

* Prisma Migration

이 명령어는 위의 introspection 과는 반대로,

Prisma 에 변화가 생겼을 때, DB 에 알려주는 방식이다.

출처 : https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-migrate

 

* Prisma Studio

원래 Prisma 1 에서는 Prisma 사이트 안에다가 서버 올리고, 관리도 모두 사이트 내에서 했었다.

그러나 Prisma 2 는 개발자 각자가 관리를 하는 방식으로 바뀌었고,

별도의 GUI 환경이 없었다.

아직은 완전히 개발된 단계는 아닌것 같긴한데

prisma studio 를 실행하면 아래 같은 모습으로 나오게 된다.

 

아직까지는, prisma migrate 와 prisma studio 가 "experimental" 키워드가 붙은것으로 봐서는 

완전히 개발된 단계는 아닌것 같다.

 

 

그러면, 직접 Prisma 코드를 작성하여 어떤지 알아보자.

 

 

# 코드 작성 - 1. DB 연결 및 Prisma 설정

여기서 DB 는 MySQL 을 사용할 것이고,

Prisma 가 지원하는 DB 는 SQLite, PostgreSQL, MySQL, MariaDB, AWS Aurora 가 있으나,

실질적으로는, PostgreSQL, SQLite, MySQL 만 지원한다고 본다.

개인적인 생각으론 그냥 PostgreSQL 쓰는게 제일 나은 것 같다.

 

(아래의 내용들은 이미 node.js 와 MySQL 이 설치 되어 있고, javascript 와 GraphQL 에 대한 이해도가 이미 어느 정도 있다고 가정한다

또한 이 내용은 "Prisma 2" 를 기준으로 하기 때문에 Prisma 1 에서의 실행방법은 다소 차이가 있다.)

 

 

먼저, @prisma/cli 를 설치한다.

npm install @prisma/cli --save-dev
cs

 

그 다음 초기 Prisma Schema 파일을 생성하기 위해 다음과 같은 명령어를 입력한다

npx prisma init
cs

 

이 명령을 수행하면 현재 폴더에 prisma/schema.prisma 라는 파일이 생성된다.

 

 

그리고 schema.prisma 파일에 다음과 같이 작성하여 

DB 와 연결을 시도한다.

 

- schema.prisma

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

 

이는 DB 를 MySQL 을 사용하겠다는 의미이고,

env("DATABASE_URL") 은 

prisma 폴더에 .env 파일이 있어야 하며,

.env 파일에는 아래처럼 정의되어 있어야 한다.

 

- .env

DATABASE_URL="mysql://username:password@localhost:3306/dbname"
cs

 

이 부분은 어떤 DB 를 쓰는 가에 따라서 값이 달라진다.

(이에 대한 좀 더 자세한 사용법은 https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-schema/prisma-schema-reference/#datasource 참조)

 

 

그리고 prisma client 를 백엔드 서버에 이용하기 위해서

@prisma/client 를 설치해준다.

npm install @prisma/client
cs

 

그리고 prisma client 에 대한 설정값이 schema.prisma 파일에 설정되어야 한다.

schema.prisma 파일에 아래의 내용을 추가한다

 

- schema.prisma

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

 

실제 테스트를 해보기 위해서

schema.prisma 파일에 model 을 정의해준다

(model 이란 관계형 데이터베이스에서 테이블과 같은 개념이다)

 

- schema.prisma

model User {
  id          Int       @default(autoincrement()) @id
  avatarUrl   String?   @default("")
  username    String
  email       String    @unique
  password    String
  phoneNum    String    @default("") @unique
  bio         String?   @default("")
  createdAt   DateTime? @default(now())
}
 
cs

 

최대한 간단하게 테스트만 해보고자 model 을 하나만 정의했다.

단순히 유저 생성만 테스트 해볼 것이다.

 

그리고, MySQL 에도 이 모델이 테이블 형태로 들어가야 하기 때문에

prisma migrate 명령어를 넣어줘서 테이블을 만들어야 한다.

npx prisma migrate save --name init --experimental && npx prisma migrate up --experimental
cs

 

이 명령어를 입력해서 migration 을 시도한다.

(prisma migration 에 대한 더 자세한 내용은 https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-migrate 참조)

 

 

# 코드 작성 - 2. GraphQL, JS

백엔드 서버를 REST 기반의 Express 를 써도 되고 다른 프레임워크를 써도 상관은 없다.

REST 기반의 프레임워크를 이용해서 작성하고 싶다면 https://www.prisma.io/docs/understand-prisma/prisma-in-your-stack/rest 를 참조

 

여기서 필자는 GraphQL 을 사용할 것이다.

(GraphQL 을 모른다면 GraphQL 에 대한 자세한 설명은 https://tech.kakao.com/2019/08/01/graphql-basic/ 를 참조)

 

GraphQL 로 백엔드 서버를 구성할때, 다양한 방식이 있지만 

여기서는 graphql 과 apollo-server-express 를 이용해서 구성할 것이다.

 

먼저 관련된 패키지들을 설치를 해주고,

npm install graphql express apollo-server-express body-parser helmet helmet-csp dotenv morgan graphql-playground-middleware-express
cs

 

서버 파일을 다음과 같이 구성한다

- server.js

import dotenv from "dotenv";
dotenv.config();
import express from "express";
import bodyParser from "body-parser";
import helmet from "helmet";
import csp from "helmet-csp";
import expressPlayground from "graphql-playground-middleware-express";
import { ApolloServer } from "apollo-server-express";
import morgan from "morgan";
import schema from "./schema";
 
const PORT = process.env.PORT;
 
const server = new ApolloServer({
  schema,
});
 
const app = express();
app.use(helmet());
 
app.use(
  csp({
    directives: {
      defaultSrc: ["'self'"],
      styleSrc: ["'self'""'unsafe-inline'"],
      styleSrcElem: [
        "'self'",
        "fonts.googleapis.com",
        "cdn.jsdelivr.net",
        "'unsafe-inline'",
      ],
      imgSrc: ["'self'""cdn.jsdelivr.net"],
      scriptSrcElem: ["'self'""cdn.jsdelivr.net""'unsafe-inline'"],
      fontSrc: ["'self'""'unsafe-inline'""fonts.gstatic.com"],
    },
  })
);
 
app.use(bodyParser.json(), cors());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(morgan("dev"));
 
app.get("/", expressPlayground({ endpoint: "/graphql" }));
 
server.applyMiddleware({ app });
 
const handleListening = () => {
  console.log(`Server ready at http://localhost:${PORT}`);
};
 
app.listen(PORT, handleListening);
 
cs

 위 코드에서 csp 설정은 꼭 이렇게 맞출 필요는 없고, 그냥 각자 원하는 대로 맞추면 된다.

(csp 에 대한 자세한 설명은 https://sdy-study.tistory.com/63 를 참조)

 

 

 

다음으로, 

GraphQL 에서 Resolver 와 TypeDefs 설정을 위해서 다음의 패키지들을 설치한다

(Resolver 와 TypeDefs 에 대한 내용을 모르면 https://www.apollographql.com/docs/apollo-server/api/graphql-tools/#makeexecutableschemaoptions 를 참조)

npm install @graphql-tools/schema @graphql-tools/merge @graphql-tools/load-files
cs

 

- schema.js

import path from "path";
import { makeExecutableSchema } from "@graphql-tools/schema";
import { mergeTypeDefs, mergeResolvers } from "@graphql-tools/merge";
import { loadFilesSync } from "@graphql-tools/load-files";
 
const allTypes = loadFilesSync(path.join(__dirname, "api/**/*.graphql"));
const allResolvers = loadFilesSync(path.join(__dirname, "api/**/*.js"));
 
const schema = makeExecutableSchema({
  typeDefs: mergeTypeDefs(allTypes),
  resolvers: mergeResolvers(allResolvers),
});
 
export default schema;
 
cs

 

다음으로, Resolver 와 TypeDefs 파일을 만들것인데

주의할 점이 한가지 있다면 schema.js 에서 설정한 Resolver 와 TypeDefs 에 대한 위치가 설정 되었기 때문에

path.join(__dirname, "api/**/*.graph"); 이 값에 맞도록 파일 구조를 만들어야 한다

벗어날 경우 graphql 이 제대로 실행되지 않고 [object Object] 만 리턴한다.

그래서 파일 구조를 아래와 같이 설정해야 한다.

|- prisma
|     |- .env
|     |- schema.prisma
|- src
|     |- api
|     |      |- User
|     |      |     |- createAccount
|     |      |     |           |- createAccount.js
|     |      |     |           |- createAccount.graphql       
|     |      |     |- User.js
|     |      |- models.graphql
|     |- schema.js
|     |- server.js
|     |- utils.js
cs

 

아래처럼 파일들을 생성한다.

 

- utils.js

import { PrismaClient } from "@prisma/client";
 
export const prisma = new PrismaClient();
cs

 

- models.graphql 

type User {
  id: ID!
  avatarUrl: String
  username: String!
  email: String!
  password: String!
  phoneNum: String!
  bio: String
  createdAt: String
}
cs

 

아래처럼 Resolver 에 해당하는 createAccount.js 를 만든다.

 

- createAccount.js

import { prisma } from "../../../utils";
 
export default {
  Mutation: {
    createAccount: async (_, args) => {
      const { username, password, email, phoneNum, bio, avatarUrl } = args;
 
      const user = await prisma.user.create({
        data: {
          username,
          email,
          password,
          phoneNum,
          bio,
          avatarUrl,
        },
      });
      return user;
    },
  },
};
 
cs

 

 그 다음으로 TypeDefs 파일인 createAccount.graphql 파일을 만든다.

 

- createAccount.graphql

type Mutation {
  createAccount(
    username: String!
    email: String!
    password: String!
    phoneNum: String!
    bio: String
    avatarUrl: String
  ): User!
}
 
cs

 

# Test

이제 직접 코드를 실행해보고 어떤지 파악해보자

백엔드 서버를 실행시키면, graphql playground 가 나오고

여기에 다음과 같이 입력을 해서 유저 계정 하나를 생성하면

'abc' 라는 이름의 유저가 하나 생성되었다.

 

 

prisma 2 에서 제공하는 prisma studio 를 실행시켜서 확인해보면

아래 그림처럼 레코드가 하나 생성되었음을 알 수 있고,

(password 값이 다르게 나오는 것은 나는 bcrypt 를 사용해서 비밀번호를 해시로 암호화해서 넣었기 때문이다)

 

 

MySQL 의 테이블에도 실제로 들어갔는지 확인해보면

레코드를 실제로 만들어서 넣은 것을 볼 수 있다.

 

 

 

 

 

 

- Reference

1. https://en.wikipedia.org/wiki/Object-relational_impedance_mismatch

2. https://www.quora.com/What-is-the-object-relational-impedance-mismatch

3. https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-schema/prisma-schema-reference/#datasource

4. https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-migrate

5. https://www.prisma.io/docs/understand-prisma/prisma-in-your-stack/rest

6. https://tech.kakao.com/2019/08/01/graphql-basic/

7. https://sdy-study.tistory.com/63

8. https://www.apollographql.com/docs/apollo-server/api/graphql-tools/#makeexecutableschemaoptions

728x90

'JS' 카테고리의 다른 글

webpack (1)  (0) 2020.09.06
Prisma Migration 관련 오류  (0) 2020.09.03
CSP (Content Security Policy) 란?  (0) 2020.08.30
#33 가지 Javascript 필수 개념 - 5. Equals  (0) 2020.08.22
CORS (Cross Origin Resource Sharing)  (0) 2020.08.19