NestJS+Prismaで作るGraphQLサーバー

## 概要
Node.jsのバックエンドフレームワーク`NestJS`とTypeScript界のORM`Prisma`を使って
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.json`の`compilerOptions`を追加してGraphQL pluginを効かせておくと、ある程度コード量を減らすことができて便利です。

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

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

- https://github.com/EndyKaufman/typegraphql-prisma-nestjs-example
- https://github.com/wSedlacek/prisma-generators/tree/master/libs/nestjs
- https://github.com/unlight/prisma-nestjs-graphql

### 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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478011/297ce97c-8892-654e-fd9a-add1e1463797.png)

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

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