30
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

NestJS+Prismaで作るGraphQLサーバー

Last updated at Posted at 2020-12-20

概要

Node.jsのバックエンドフレームワークNestJSとTypeScript界のORMPrismaを使って
GraphQLサーバーを作ります。
ちなみに、PrismaはREST APIの構築に用いることもできますし、GraphQLに特化しているわけではありません。
しかし、スキーマを定義するPrisma schemaがGraphQLのSDL(Schema Definition Language = スキーマ定義言語)に近いことや、GraphQLの複雑さをPrisma Clientによってカバーできることなど、GraphQLとの親和性はかなり高いと思っています。

導入手順

NestJSのドキュメントにPrismaとGraphQLの項目がそれぞれありますので、基本的にこれに沿って進めていきます。
https://docs.nestjs.com/recipes/prisma
https://docs.nestjs.com/graphql/quick-start

1.NestJS CLIをインストール

$ npm i -g @nestjs/cli

2.NestJSプロジェクトを作成

$ nest new my-app

3.Prisma CLIとPrisma Clientをインストール

$ cd my-app
$ npm install @prisma/client
$ npm install --save-dev prisma

4.Prismaのセットアップ

$ npx prisma init

このコマンドを実行すると、ルートディレクトリ直下にprisma/schema.prisma.envが生成されます。

5.データベースと接続

prisma/schema.prisma.envを自分で用意するデータベースに合わせて編集します。
私の場合はDockerでMysqlコンテナを立てたので以下のようになりました。

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

generator client {
  provider = "prisma-client-js"
}
.env
DATABASE_URL="mysql://root:password@db/development"

※@の後ろのdbはコンテナ名です。
※developmentはテーブル名なので、適当な名前に変えても問題ありません。

docker-compose.yml
version: '3.8'
services:
  server:
    build: .
    volumes:
      - ./:/usr/app
    ports:
      - '3000:3000'
    tty: true
    depends_on:
      - db
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
    ports:
      - '3306:3306'
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - ./prisma/mysql:/var/lib/mysql

公式ドキュメントに記載の通り、手軽に実行するならsqliteを使うのが一番楽です。

prisma/schema.prisma
datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}
.env
DATABASE_URL="file:./dev.db"

6.スキーマを定義

schema.prismaにスキーマを定義します。ここでは例としてUserを作成します。

prisma/schema.prisma
(↑に追加)
model User {	
  id Int @id @default(autoincrement())	
  registeredAt DateTime?	
  updatedAt DateTime?	
  email String?	
  name String	
} 

7.スキーマをデータベースに適用

6で定義したスキーマをデータベースに適用させるのには二つの方法があります。

Create migrations from your Prisma schema, apply them to the database, generate artifacts (e.g. Prisma Client)
  $ prisma migrate dev --preview-feature
  
  Push the Prisma schema state to the database
  $ prisma db push --preview-feature

migrateを実行した場合は、prismaディレクトリ下にmigrationsディレクトリが生成され、さらにその下にマイグレーション毎にディレクトリが生成されます。
migration.sqlにはスキーマを適用するために発行するSQLが書かれています。
migrateにはresetやstatusなど別のコマンドも用意されていますので、詳しくは以下をご覧ください。
https://www.prisma.io/docs/reference/api-reference/command-reference#prisma-migrate-preview

├── prisma
│   ├── migrations
│   │   └── 20201220094108_
│   │       └── migration.sql

db pushを実行した場合には、何もファイルは生成されずにDBにそのままスキーマが適用されます。
個人的には、Railsでmigrationよりもridgepoleが好まれていることを考えると、db pushを使って良いような気がしています。

8. PrismaClientを扱うPrismaServiceを作成

NestJSでPrisma Client APIを扱うために、src以下にprisma.service.tsを作成します。

src/prisma.service.ts
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient
  implements OnModuleInit, OnModuleDestroy {
  async onModuleInit() {
    await this.$connect();
  }

  async onModuleDestroy() {
    await this.$disconnect();
  }
}

9. GraphQL関連のパッケージをインストール

npm i @nestjs/graphql graphql-tools graphql apollo-server-express

※fastifyを使う場合はapollo-server-expressの代わりにapollo-server-fastifyをインストールします。加えて、src/main.tsのappでFastifyを扱うように置き換えます。

src/main.ts
const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter(),
  );

10. Object typeとResolversを作成

GraphQLの実装にはCode firstとSchema firstの2種類の手法があります。今回はコードからスキーマを作成するCode firstの手順を用いました。

src/models/user.model.ts
import { ObjectType, Field, Int, HideField } from '@nestjs/graphql';	

@ObjectType()	
export class User {	
  @Field((type) => Int)	
  id: number;	
  @Field({ name: 'registeredAt' })	
  createdAt?: Date;	
  updatedAt?: Date;	
  email: string;	
  @HideField()	
  password: string;	
  name?: string;	
}
src/resolvers/user.resolver.ts
import { Resolver, Query } from '@nestjs/graphql';	
import { User } from '../models/user.model';	
import { PrismaService } from '../prisma.service';	

@Resolver((of) => User)	
export class UserResolver {	
  constructor(private prisma: PrismaService) {}	

  @Query((returns) => [User])	
  async users() {	
    return this.prisma.user.findMany();	
  }	
}

この時、nest-cli.jsoncompilerOptionsを追加してGraphQL pluginを効かせておくと、ある程度コード量を減らすことができて便利です。

nest-cli.json
"compilerOptions": {
    "plugins": [
      {
        "name": "@nestjs/graphql/plugin",
        "options": {
          "typeFileNameSuffix": [".input.ts", ".model.ts"]
        }
      }
    ]
  }

また、6で作成したschema.prismaからNestJS用のコードを自動生成するGeneraterがありますが、公式でのサポートを受けているわけでもなく、まだなかなか採用するには難しいように感じています。

11.AppModuleでPrismaService, GraphQLModule, UserResolverをインポートする

8で作成したPrismaService, 9でインストールしたGraphQLModule, 10で作成したUserResolver をAppModuleでインポートします。

src/app.module.ts
@Module({
  imports: [
    GraphQLModule.forRoot({	
      autoSchemaFile: join(process.cwd(), 'src/schema.gql'),	
      debug: true,	
      playground: true,	
    }),
  ],
  controllers: [AppController],
  providers: [AppService, PrismaService],
})

動作確認

ここまで作成したら、npm run startでNestサーバーを起動します。
http://localhost:3000/graphql にアクセスするとGraphQLプレイグラウンドが表示されます。
image.png

また、この時autoSchemaFileに定義したように、スキーマ定義ファイルであるsrc/schema.gqlが自動的に生成されます。

最後に

Nexus1.0のリリースノートを見てから、なんとかNexusを組み込めないか考えてみましたが、現状のNest+PrismaにNexusを使う意味は薄そうというのが私の所感です。
https://www.prisma.io/blog/announcing-the-release-of-nexus-schema-v1-b5eno5g08d0b
本記事の間違っている箇所や、他に良さげなプラクティスがあればご指摘いただけますと幸いです。
ここまで読んでくださり、ありがとうございました。

30
14
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?