TypeScript + Prisma + NestJSでGraphQLサーバを作ってみる

## はじめに
TypeScript + Prisma + NestJSでGraphQLサーバを作っていきます。


## 1.NestJSプロジェクトの作成
```
npm install -g @nestjs/cli
nest new my-app
```

インストール後、サーバを起動してみます。
```
cd my-app
npm run start
```
http://localhost:3000　
にアクセスすると　Hello World!　が表示されます。

## 2.Prismaのセットアップ

```
npm install prisma --save-dev　　　　　　//Prismaのパッケージをインストール
npm install @prisma/client
npx prisma init   　　　　　　　　　　　　//prismaの初期化
```
`prisma/schema.prisma`  と `.envファイル`が生成されるので接続したいデータベースに合わせて設定していきます。

 今回は、supabaseを使用します。

 ``` ruby:prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

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

```.env
DATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=SCHEMA"
```

## 3.テーブルの作成
今回はIDと本文とのみの Memo テーブルを作成します。

```ruby:prisma/schema.prisma
model Memo {
  id      Int    @id @default(autoincrement())
  content String
}
```

以下のコマンドを実行し、マイグレーションファイルの生成とマイグレーションの実行します。

```
npx prisma migrate dev --name memo
```
## 4.コマンドを使用してファイルを作成

```
nest g resource memo
```

コマンドを入力すると質問されるので、GraphQLのcode firstを選択します。

```
What transport layer do you use? GraphQL (code first)
Would you like to generate CRUD entry points? Yes
```

<details><summary>コードファーストとスキーマファースト</summary>
NestJS で GraphQL サーバーを開発の開発には、コードファーストとスキーマファーストのアプローチがあります。どちらを採用しても、「Schema、データ、Resolver を用意すれば GraphQL サーバーは動かせる」という原理に変わりありません。この選択は、Schema をどうやって作成するか という違いです。

今回はコードファーストのアプローチを取ります。
コードファーストのアプローチでは、TypeScriptのクラスとデコレータを使用してクラス定義からGraphQLのSDLを生成します。

</details>

下記フォルダ、ファイルが作成され、app.module.tsは更新されます。
```
CREATE src/memo/memo.module.ts (218 bytes)
CREATE src/memo/memo.resolver.spec.ts (515 bytes)
CREATE src/memo/memo.resolver.ts (1098 bytes)
CREATE src/memo/memo.service.spec.ts (446 bytes)
CREATE src/memo/memo.service.ts (623 bytes)
CREATE src/memo/dto/create-memo.input.ts (196 bytes)
CREATE src/memo/dto/update-memo.input.ts (243 bytes)
CREATE src/memo/entities/memo.entity.ts (187 bytes)
UPDATE src/app.module.ts (308 bytes)
```

## 5.NestJSでPrismaクライアントを使用する

NestJSでPrismaClientを使用するために、src 配下に prisma.service.ts というファイルを作成し、PrismaClientのインスタンス化とデータベースへ接続します。


[参考ドキュメント](https://docs.nestjs.com/recipes/prisma#:~:text=Inside%20the%20src%20directory%2C%20create%20a%20new%20file%20called%20prisma.service.ts%20and%20add%20the%20following%20code%20to%20it%3A)

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

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  async onModuleInit() {
    await this.$connect();
  }
}
```
これでPrismaServiceからDB操作のためのメソッドを呼び出すことができるようになりました。


## 6.GraphQLのパッケージをインストール
NestJSの [GraphQLのページ](https://docs.nestjs.com/graphql/quick-start#installation:~:text=Design%20and%20Development,Installation)を参考に必要なパッケージをインストールします。

```
npm install @nestjs/graphql @nestjs/apollo graphql apollo-server-express
```

## 7.app.module.tsの編集

graphqlモジュールをNestJSアプリケーションに組み込み初期化します。
AppModule に GraphQLModule と先程作成した PrismaServiceを追加します。

```ruby:src/app.module.ts
import { ApolloDriver } from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { join } from 'path';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { MemoModule } from './memo/memo.module';
import { PrismaService } from './prisma.service';

@Module({
  imports: [
    GraphQLModule.forRoot({
      driver: ApolloDriver,
      autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
    }),
    MemoModule,
  ],
  controllers: [AppController],
  providers: [AppService, PrismaService],
})
export class AppModule {}
```

GraphQLModuleの`forRoot()`には GraphQL のオプションを設定できます。
`autoSchemaFile: join(process.cwd(), 'src/schema.gql')` は `autoSchemaFileプロパティ`によって、プロジェクトのsrcディレクトリに自動的にGraphQLスキーマファイルが生成されるようにしています。

これでgraphqlが使用できるようになりました！

## 8.テーブルの各データをモデルとして定義

<details><summary>編集前</summary>

```
import { ObjectType, Field, Int } from '@nestjs/graphql';

@ObjectType()
export class Memo {
  @Field(() => Int, { description: 'Example field (placeholder)' })
  exampleField: number;
}
```
</details>


```ruby:src/memo/entities/memo.entity.ts
import { ObjectType, Field, ID } from '@nestjs/graphql';

@ObjectType()
export class Memo {
  @Field(() => ID, { description: 'メモのID' })
  id: number;

  @Field({ description: 'メモの内容' })
  content: string;
}
```

description には項目や補足を書くなど便利に使用できます。自動で生成されるschema.gqlにも反映されます。

## 9.サービス定義
<details><summary>編集前</summary>

```
import { Injectable } from '@nestjs/common';
import { CreateMemoInput } from './dto/create-memo.input';
import { UpdateMemoInput } from './dto/update-memo.input';

@Injectable()
export class MemoService {
  create(createMemoInput: CreateMemoInput) {
    return 'This action adds a new memo';
  }

  findAll() {
    return `This action returns all memo`;
  }

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

  update(id: number, updateMemoInput: UpdateMemoInput) {
    return `This action updates a #${id} memo`;
  }

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

```
This action~のところを編集していきましょう。

</details>

```ruby:src/memo/memo.service.ts
import { Injectable } from '@nestjs/common';
import { CreateMemoInput } from './dto/create-memo.input';
import { UpdateMemoInput } from './dto/update-memo.input';
import { PrismaService } from 'src/prisma.service';

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

  async create(createMemoInput: CreateMemoInput) {
    return this.prisma.memo.create({ data: createMemoInput });
  }

  findAll() {
    return this.prisma.memo.findMany();
  }

  findOne(id: number) {
    return this.prisma.memo.findUnique({ where: { id } });
  }

  update(id: number, updateMemoInput: UpdateMemoInput) {
    return this.prisma.memo.update({
      where: { id: id },
      data: updateMemoInput,
    });
  }

  remove(id: number) {
    return this.prisma.memo.delete({ where: { id: id } });
  }
}
```

```src/memo/dto/create-memo.input.ts
import { InputType, Field } from '@nestjs/graphql';

@InputType()
export class CreateMemoInput {
  @Field({ description: '記事の追加' })
  content: string;
}
```

```src/memo/dto/update-memo.input.ts
import { CreateMemoInput } from './create-memo.input';
import { InputType, Field, PartialType, ID } from '@nestjs/graphql';

@InputType()
export class UpdateMemoInput extends PartialType(CreateMemoInput) {
  @Field(() => ID, { description: '更新するID' })
  id: number;

  @Field({ description: '更新の内容' })
  content: string;
}
```
number はデフォルトではGraphQLの Float に変換されるため、ここでは ID になるよう明示的に @Field(() => ID) を指定しています。


memo.module.tsにPrismaServiceを追加。

```ruby:memo/memo.module.ts
import { Module } from '@nestjs/common';
import { MemoService } from './memo.service';
import { MemoResolver } from './memo.resolver';
import { PrismaService } from 'src/prisma.service';

@Module({
  providers: [MemoResolver, MemoService, PrismaService],
})
export class MemoModule {}
```

```ruby:memo/memo.resolver.ts
import { Resolver, Query, Mutation, Args, Int, ID } from '@nestjs/graphql';
import { MemoService } from './memo.service';
import { Memo } from './entities/memo.entity';
import { CreateMemoInput } from './dto/create-memo.input';
import { UpdateMemoInput } from './dto/update-memo.input';

@Resolver(() => Memo)
export class MemoResolver {
  constructor(private readonly memoService: MemoService) {}

  @Mutation(() => Memo, { description: '記事の作成' })
  createMemo(@Args('createMemoInput') createMemoInput: CreateMemoInput) {
    return this.memoService.create(createMemoInput);
  }

  @Query(() => [Memo], { description: '記事をすべて取得する' })
  findAll() {
    return this.memoService.findAll();
  }

  @Query(() => Memo, { description: '特定の記事の取得' })
  findOne(@Args('id', { type: () => ID }) id: number) {
    return this.memoService.findOne(id);
  }

  @Mutation(() => Memo, { description: '記事の更新' })
  updateMemo(@Args('updateMemoInput') updateMemoInput: UpdateMemoInput) {
    return this.memoService.update(updateMemoInput.id, updateMemoInput);
  }

  @Mutation(() => Memo, { description: '記事の削除' })
  removeMemo(@Args('id', { type: () => ID }) id: number) {
    return this.memoService.remove(id);
  }
}
```
resolverはコマンドを打った時にほとんどコードが書かれています。　descriptionの追加をしました。

これで、完了です。サーバーを立ち上げましょう。

```
npm run start
```

## 10.動作確認
http://localhost:3000/graphql にアクセスするとGraphQL Playgroundが表示されます。


```:記事の作成
mutation {
  createMemo(createMemoInput: { content: "aiu" }) {
    content
  }
}
```

```:全ての記事の取得
query {
  findAll {
    id
    content
  }
}
```

```:記事の更新
mutation {
  updateMemo(updateMemoInput: { id: 2, content: "記事の更新" }) {
    id
    content
  }
}
```

```:記事の削除
mutation {
  removeMemo(id: 2) {
    id
    content
  }
}
```

```:特定の記事の取得
query {
  findOne(id: 1) {
    id
    content
  }
}
```