[입문] NestJS+Preisma+prisma-nestjs-graphiql의 편안한GraphiQL 환경 구축🧑🏽‍💻

개시하다


NestJS 정보


네스트JS는 깃허브의 스타 수에서 좋은 성장세를 보이며 앞으로도 계속 성장할 것이라는 점에서 주목받는 노드JS 프레임이다.참고일 뿐이지만 NestJS와 NestJS 이외에 자주 사용하는 백엔드 프레임워크인 GitHub의 스타 수 변화를 비교했다.이렇게 비교해 보면 어느 프레임이든 인기를 끌고 있어 향후 성장세에는 도장고, 패스트API, 진, 네스트JS 등이 군배 상승의 느낌을 받을 것으로 보인다.(특히 Fastapi의 기세가 대단하군요.)

Prisma 정보


Proisma는 Type Script의 ORM입니다.TypeScript의 ORM 하면 지금까지 TypeORM을 많이 사용한 것 같은데 최근 Prism의 발전을 보면 Prism이 주류가 된 것은 시간문제(이미 됐나요?)라는 느낌을 받았다.

Prism은 이미 업무에서 사용되고 있지만 Typerom은 개인 개발에서 다소 접촉을 했기 때문에 상세한 비교는 할 수 없지만 사용하기에 Prism이 더 좋다고 생각합니다.또 사용이 편리할 뿐만 아니라 일반 컨트롤러를 이용해 다양한 기능을 추가할 수 있다는 점도 매력이다.게다가 그래픽QL 사용 시 과제인 N+1 문제도 조회 최적화를 통해 해결하고, 그래픽QL과 호환성도 좋다는 점도 가장 큰 매력이다.
https://www.prisma.io/docs/guides/performance-and-optimization/query-optimization-performance

GraphiQL(RESTAPI에서 사용한 사람을 위한 간단한 설명)



(※ 수제 스케치는 죄송합니다)
GraphiQL 하면 사귀기 어려울 수도 있지만, NestJS에서 단순히 GraphiQL 서버를 시작하는 것만 생각하면 우선 적자 부분인 Resolver와 Skima가 정의한 맞춤법만 기억하면 작동할 수 있다!

빨리 실천해 보세요!


순서를 간단히 설명했으니까 이해가 안 가는 부분이 있다면 질문을 할 수 있으면 좋겠다.

NestCLI를 통한 프로젝트 생성


1. NestCLI 설치
terminal
# 既にインストールしている場合はスキップして大丈夫です。
npm install -g @nestjs/cli
2. NestCLI를 통해 프로젝트를 만듭니다.
terminal
# プロジェクト作成、パッケージマネージャーはnpmを選択します。
nest new hello-prisma
# プロジェクトフォルダに移動
cd hello-prisma

DB 설정(MySQL)


1.compose.yml을 생성합니다.
terminal
# 下のcompose.ymlの中身をコピペしてCRTL+Dで保存します。普通に作成しても大丈夫です。
cat > compose.yml
compose.yml
services:
  db:
    image: mysql:8.0
    ports:
      - 3308:3306
    volumes:
      - db-store:/var/lib/mysql
      - ./mysql_logs:/var/log/mysql
    environment:
      MYSQL_DATABASE: test
volumes:
  db-store:
2. MySQL 컨테이너를 시작합니다.
terminal
 # MySQLのコンテナを起動します。
 docker compose up
 # ホスト側からMySQLに接続できるか確認
 mysql -h 127.0.0.1 -u root -P 3308 -proot
호스트측 포트는 3306을 피하고 3308로 설정합니다.
docker composiev2의 방법에 따라 쓰여 있습니다.
https://zenn.dev/miroha/articles/whats-docker-compose-v2

Prisma 설정


1. nest add nestjs-Prisma 실행
terminal
# DBはMySQLを選択し、dockerizeするかどうかはnoを選択してください
nest add nestjs-prisma
이 명령으로prisma의 설정은 거의 끝났지만, 몇 가지 남은 작업이 있습니다.
2. AppModule에서 PreismaModule을 가져와 전체 응용 프로그램에서 Preisma를 사용할 수 있도록 합니다
app.module.ts
import { Module } from '@nestjs/common';
import { PrismaModule } from 'nestjs-prisma';

@Module({
  imports: [PrismaModule.forRoot({ isGlobal: true }),],
})
export class AppModule {}
3. enableShutdownHooks를 사용하여 프로그램을 정상적으로 끝냅니다.
main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { PrismaService } from 'nestjs-prisma';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // enable shutdown hook
  const prismaService: PrismaService = app.get(PrismaService);
  prismaService.enableShutdownHooks(app);

  await app.listen(3000);
}
bootstrap();

테이블 작성


1./prisma/schema.prisma 편집
schema.prisma
generator client {
  provider = "prisma-client-js"
}

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

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

//追加
model Post {
  id        Int      @default(autoincrement()) @id
  title     String
  content   String?
  published Boolean? @default(false)
  author    User?    @relation(fields: [authorId], references: [id])
  authorId  Int?
}
2. .env를 만듭니다.
terminal
# 下の.envの内容でファイルを新規作成
 echo DATABASE_URL=\"mysql://root:root@localhost:3308/test\" > .env 
3. schema.prisma의 내용을 DB에 반영하여 migration 파일을 만듭니다.
terminal
# migrationファイルが作成されます。名前を聞かれるので適当に入力します。
npx prisma migrate dev

seed 만들기


이후 GraphiQL을 통해 DB에서 데이터를 얻기 위해 초기 데이터를 투입한다.
1.package.json의 다음 항목을 편집합니다.
package.json
"prisma": {
    "seed": "ts-node ./prisma/seed/start.ts"
  }
2.start.ts를 생성합니다.
양이 늘어나면 파일을 분리하면 전망이 더 좋을 것 같은데 이번에는 분리하지 않겠습니다, start.ts에만 적으세요.
terminal
# seedフォルダを作成
mkdir prisma/seed
#以下のstart.tsをコピペします。
cat > prisma/seed/start.ts
start.ts
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

const main = async () => {
  console.log('💫 seed executing ...');
  await prisma.post.deleteMany();
  await prisma.user.deleteMany();
  await prisma.user.create({
    data: {
      name: 'john',
      email: '[email protected]',
      posts: {
        create: {
          title: 'first article',
          content: 'hello!world!',
          published: true,
        },
      },
    },
  });
  console.log('💫 seed finished.');
};

main()
  .catch((e) => console.error(e))
  .finally(async () => {
    await prisma.$disconnect();
  });
3. seed를 실행합니다.
terminal
npx prisma db seed

GraphiQL 설정


1. 라이브러리 설치
terminal
# ライブラリのインストール
npm i @nestjs/graphql @nestjs/apollo graphql apollo-server-express
2.app.module.개작
app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { PrismaModule } from 'nestjs-prisma';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';

@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      debug: true,
      playground: true,
      autoSchemaFile: './src/schema.graphql',
    }),
    PrismaModule.forRoot({ isGlobal: true }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

prisma에 발생기 추가


1. 라이브러리 설치
terminal
# prisma-nestjs-graphqlをインストール
npm install prisma-nestjs-graphql
2.schema.prisma에gener를 추가합니다.
schema.prisma
//追加
generator nestgraphql {
    //v15.1.1以上の場合は"prisma-nestjs-graphql"のみでokです
    provider = "node node_modules/prisma-nestjs-graphql"
    output = "../src/@generated/prisma-nestjs-graphql"
}
이렇게 하면 npx prisma generate를 실행하면 코드 우선순위의 GraphiQL 코드가 생성됩니다.빨리 실행하고 싶지만 대량의 파일을 자동으로 생성하기 때문에 자동으로 생성된 파일은git의 관리 범위에 있지 않습니다.
3. .gitignore에prisma-nestjs-graphiql로 생성된 파일 추가
terminal
echo "src/@generated" >> .gitignore
4. npx prisma generate를 실행합니다.
terminal
npx prisma generate
src/@generated에서GraphiQL 코드가 생성되었는지 확인할 수 있을 것 같습니다.

NestCLI에서 템플릿 생성


같은 코드를 여러 번 쓰는 것은 번거롭기 때문에 NestCLI를 이용한다.
1. nest g resource로 module 만들기.
terminal
# transport layerを聞かれたら GraphQL (code first) を選択してください
# CRUD entry pointsを生成するかどうか聞かれたら yes を選択してください
nest g resource modules/users
이 명령은modules 폴더에user 폴더를 만들었는지 확인할 수 있지만, 이 단계에서는 src 폴더에schema가 생성됩니다.나는 아직 graphql(SDL)이 생성되지 않았다고 생각한다.NestJS 서버를 시작하면 AppMoudle의 의존 관계, schema를 해결할 수 있습니다.graphiql을 생성합니다.
2. 서버를 시작합니다.
terminal
npm run start:dev
이렇게 하면 src 폴더에 schema가 있습니다.나는 graphql이 생성되었다고 생각한다.

schema.graphql와 볼록대의 대응을 확인하다


무사히graphiql을 생성하여 안의 내용을 확인한 후 다음과 같다고 생각합니다.
schema.graphql
# ------------------------------------------------------
# THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
# ------------------------------------------------------

type User {
  """Example field (placeholder)"""
  exampleField: Int!
}

type Query {
  users: [User!]!
  user(id: Int!): User!
}

type Mutation {
  createUser(createUserInput: CreateUserInput!): User!
  updateUser(updateUserInput: UpdateUserInput!): User!
  removeUser(id: Int!): User!
}

input CreateUserInput {
  """Example field (placeholder)"""
  exampleField: Int!
}

input UpdateUserInput {
  """Example field (placeholder)"""
  exampleField: Int
  id: Int!
}
이 글은 처음에 Resolver와 Schema가 정의한 쓰기 방법을 기억하면 동작을 할 수 있다고 언급했다. 그러나 코드 우선 방법의 경우 decorder로 SDL(Schema Definition Language)을 생성하기 때문에 어떤 decorder가 어떤 SDL을 생성했는지 별로 신경 쓰지 않아도 실현할 수 있다.이번엔 진짜 오랜만이에요.잠깐 확인하고 싶은데요.

@ObjectType


schema.graphql
type User {
  """Example field (placeholder)"""
  exampleField: Int!
}
상기 코드는 다음과 같은 src/module/users/entities/users입니다.entity.ts의 볼록기에서 생성됩니다.
user.entity.ts
import { ObjectType, Field, Int } from '@nestjs/graphql';

@ObjectType()
export class User {
  @Field(() => Int, { description: 'Example field (placeholder)' })
  exampleField: number;
}
NestJS에서 코드 우선 순위를 사용하여GraphiQL을 실현할 때 @ObjectType을 사용하여 데이터 모델링을 합니다.(상황에 따라 DB의 표 구조를 직접 반영하는 것이 아니라 클라이언트가 편리하게 사용할 수 있도록 데이터 모델링을 하는 좋은 방법입니다.)

@Query


schema.graphql
type Query {
  users: [User!]!
  user(id: Int!): User!
}
상기 코드는 다음과 같은 src/module/users/entities/users입니다.resolver.ts의 볼록기에서 생성됩니다.
users.resolver.ts
  @Query(() => [User], { name: 'users' })
  findAll() {
    return this.usersService.findAll();
  }

  @Query(() => User, { name: 'user' })
  findOne(@Args('id', { type: () => Int }) id: number) {
    return this.usersService.findOne(id);
  }
철점기의 첫 번째 매개 변수에 함수를 건네주고, 함수의 반환값으로 해석기가 되돌아오는 유형을 표시합니다.
{name:'user'}을 볼록기의 두 번째 인자에 건네주면Query의 필드 이름을 변경할 수 있습니다.이 객체가 전달되지 않은 경우 필드 이름은 메소드 이름이 됩니다.

@Mutation


schema.graphql
type Mutation {
  createUser(createUserInput: CreateUserInput!): User!
  updateUser(updateUserInput: UpdateUserInput!): User!
  removeUser(id: Int!): User!
}
상기 코드는 다음과 같은 src/module/users/entities/users입니다.resolver.ts의 볼록기에서 생성됩니다.
users.resolver.ts
  @Mutation(() => User)
  updateUser(@Args('updateUserInput') updateUserInput: UpdateUserInput) {
    return this.usersService.update(updateUserInput.id, updateUserInput);
  }

  @Mutation(() => User)
  removeUser(@Args('id', { type: () => Int }) id: number) {
    return this.usersService.remove(id);
  }
Query와 Mutation의 차이로 사용되며, 서버에서 데이터를 가져올 때는 Query를 사용하고, DB의 INSERT, UPDAATE, DELETE까지 진행할 때는 Mutation을 사용한다.기술적 차이점은GraphiQL에서 한 번의 요청은 여러 개의 Query와 Mutation을 포함할 수 있지만 여러 개의 Query를 포함하면 여러 개의 Query가 병행되고 여러 개의 Mutation이 포함되면 여러 개의 Mutation이 순서대로 실행된다는 것이다.중도 Mutation이 실패하면 이후 Mutation은 취소되지만 이전에 실행된 Mutation은 롤백되지 않습니다.따라서 예를 들어 세 개의 Mutation을 한 번의 요청에 포함시켜 실행하고 한 개만 성공하거나 두 개만 허용하지 못하면 이 세 개의 Mutation을 한 개의 Mutation에 집중시켜야 한다.

Resolver 설치


여기까진 좀 길지만 드디어 Resolver를 이루고 싶어요!
1. src/@generated에서 사용 가능한 파일을 복사합니다.
terminal
# @generatedはgitの管理外になっている為、必要なファイルだけコピーします。
# postsモジュールを作成
nest g resource modules/posts
# ファイルの中身をコピー
cat src/@generated/prisma-nestjs-graphql/user/user.model.ts > src/modules/users/entities/user.entity.ts
cat src/@generated/prisma-nestjs-graphql/user/user-count.output.ts > src/modules/users/entities/user-count.output.ts
cat src/@generated/prisma-nestjs-graphql/post/post.model.ts > src/modules/posts/entities/post.entity.ts
2. import 문장을 다시 쓴다.
src/modules/posts/entities/post.entity.ts
post.entity.ts
import { Field } from '@nestjs/graphql';
import { ObjectType } from '@nestjs/graphql';
import { ID } from '@nestjs/graphql';
//変更
import { User } from '../../users/entities/user.entity';
import { Int } from '@nestjs/graphql';

////////////////略/////////////////////////
src/modules/user/entities/user.entity.ts
user.entity.ts
import { Field } from '@nestjs/graphql';
import { ObjectType } from '@nestjs/graphql';
import { ID } from '@nestjs/graphql';
//変更
import { Post } from '../../posts/entities/post.entity';
//追加
import { UserCount } from './user-count.output';

////////////////略/////////////////////////
3. users.service.ts 편집
users.service.ts
import { Injectable } from '@nestjs/common';
import { CreateUserInput } from './dto/create-user.input';
import { UpdateUserInput } from './dto/update-user.input';
import { PrismaService } from 'nestjs-prisma';

@Injectable()
export class UsersService {
  constructor(private readonly prisma: PrismaService) {}

  create(createUserInput: CreateUserInput) {
    return 'This action adds a new user';
  }

  findAll() {
    return this.prisma.user.findMany({
      include: {
        _count: {},
        posts: {},
      },
    });
  }

  findOne(id: number) {
    return `This action returns a #${id} user`;
  }

  update(id: number, updateUserInput: UpdateUserInput) {
    return `This action updates a #${id} user`;
  }

  remove(id: number) {
    return `This action removes a #${id} user`;
  }
}

GraphiQLPlayground에서 데이터를 가져올 수 있는지 확인합니다.


수고하셨습니다!이 정도면 게임 라운드에서 GraphiQL의 동작을 확인할 수 있을 거예요!
1. 서버를 시작합니다.
terminal
npm run start:dev
2.localhost:3000/graphiql에 로그인하여 다음 조회를 수행합니다
query{
  users{
   name
    id
    email
    name
    _count{
      posts
    }
    posts{
      content
      title
      published
      authorId
    }
  }
}
성공하면 다음과 같이 될 것 같아요.🎉

총결산


지금까지 NestJS+Prism의GraphiQL 설치를 황급히 설명했지만 실제로는 Middle Ware와 Guard를 통해 인증 관계 처리, 정식으로 활용하기 위해 prisma의 로그 설정, 성능을 중시하는 상황에서 HTTP 제공자를express에서fastify로 변경하는 등나는 아직 해야 할 일이 많다고 생각한다.아래의 창고를 참고했으니 소개해 주세요.
마지막으로 이 보도에 대한 잘못된 지적과 질문이 있다면 저에게 메시지를 남겨주시면 기쁩니다!
여기까지 읽어주셔서 감사합니다.
https://github.com/notiz-dev/nestjs-prisma-starter/tree/main/src

좋은 웹페이지 즐겨찾기