0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

はじめに

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を使用します。

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 テーブルを作成します。

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

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

下記フォルダ、ファイルが作成され、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のインスタンス化とデータベースへ接続します。

参考ドキュメント

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のページを参考に必要なパッケージをインストールします。

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

7.app.module.tsの編集

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

src/memo.service.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.テーブルの各データをモデルとして定義

編集前
import { ObjectType, Field, Int } from '@nestjs/graphql';

@ObjectType()
export class Memo {
  @Field(() => Int, { description: 'Example field (placeholder)' })
  exampleField: number;
}
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.サービス定義

編集前
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~のところを編集していきましょう。

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を追加。

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 {}
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
  }
}
0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?