その他

【GraphQL】ApolloServerを起動してPrismaからGraphQLクエリを実行する

その他
この記事は約9分で読めます。

implの藤谷です。
graphqlとPeismaを使用したクエリ発行について解説します。

ApolloServer起動

graphqlサーバを立ち上げる為のライブラリがApolloServerです。
server.tsに、ApolloServer起動に必要な設定を記述します。
ApolloServerクラスをインスタンス化し、listenでサーバをアプリケーションと接続しています。

const { ApolloServer } = require("apollo-server");

const server = new ApolloServer({});

server
  .listen()
  .then(({ url }: any) => console.log(`graphql server started port ${url}`));

pacage.jsonでgrapgqlサーバを起動する為のコマンドを設定。
デフォルトで4000番portが立ち上がります。

 "scripts": {
    "start": "ts-node server.ts",
    "watch": "nodemon server.ts",
    "gql": "nodemon graphql/server.ts",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
# npm run gql

> src@1.0.0 gql
> nodemon graphql/server.ts

[nodemon] 2.0.21
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: ts,json
[nodemon] starting `ts-node graphql/server.ts`
graphql server started port http://localhost:4000/

GraphQL Playground

http://localhost:4000/にアクセスして、graphql playgroundを開きます。
graphql playgroundは、ブラウザから実行できるgraphql専用のAPIテストツールです。
graphqlクエリのテスト自体はcurlやposmanからでもできるのですが、かっこいいのでplaygroundを使います。

Prisma連携

node.jsで使用できるORMのPrismaを、graphqlと繋げます。
ApolloServerクラスの引数にprismaのインスタンスをcontextとして取ることで、graphqlクエリでprismaの機能を使用できるようになります。

const { ApolloServer } = require("apollo-server");
import { prismaContext } from "../lib/prismaContext";

const server = new ApolloServer({
  context: {
    prismaContext,
  },
});

server
  .listen()
  .then(({ url }: any) => console.log(`graphql server started port ${url}`));

Schema

graphqlのshemaを実装します。
用語についての説明は行わないので、適宜公式を参照してください。

今回はprismaを介してpostsテーブルを操作します。
apollo-serverからgqlオブジェクトを取得して囲むことでshemaを定義できます。
type Postでpostsテーブル用のshemaを作成し、idとtitleは!で必須項目に設定します。

最後に、shema設定を格納したtypeDefsをApolloServerクラスの引数に取ることで、graphqlサーバから使用できるようにします。

const { ApolloServer, gql } = require("apollo-server");
import { Post } from "@prisma/client";
import { prismaContext } from "../lib/prismaContext";

const typeDefs = gql`
  type Post {
    id: Int!
    title: String!
    content: String
  }
`

const server = new ApolloServer({
  typeDefs,
  context: {
    prismaContext,
  },
});

server
  .listen()
  .then(({ url }: any) => console.log(`graphql server started port ${url}`));

Query

httpメソッドで言うGETのデータアクセスを、graphqlではQueryという単位で扱います。

shema定義内をtype Queryを追加して、resolvers内で実際のデータアクセスを実装します。
type Query内で定義した名前のQueryしかresolvers内のQueryで実装できないので注意が必要です。
オブジェクト指向のinterfaceのようなものですね。
resolvers内のpostsの引数には、ApolloServerで引数に取ったcontextを渡して、prismaへのアクセスを実装しています。

const { ApolloServer, gql } = require("apollo-server");
import { Post } from "@prisma/client";
import { prismaContext } from "../lib/prismaContext";

const typeDefs = gql`
  type Query {
    posts: [Post]
  }

  type Post {
    id: Int!
    title: String!
    content: String
  }
`

const resolvers = {
  Query: {
    posts: async (context: any): Promise<Post> => {
      const posts = context.prismaContext.post.findMany();
      return posts;
    },
  },

};

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: {
    prismaContext,
  },
});

server
  .listen()
  .then(({ url }: any) => console.log(`graphql server started port ${url}`));

graphqlのapiが一つ完成したので、playgroundから実際に叩いて検証します。

登録済だったtitle01の投稿が取得できています。
playgroundでは、画面を3分割した内の左がメニュー、真ん中がクエリを書く部分、右がレスポンス表示部分になります。

試しに、idとcontentを外してtitleだけを取得してみます。graphqlのメリットである「オーバーフェッチの防止」に繋がる検証です。

クエリ定義でtitleのみを残して実行すると、レスポンスもtitleのみになっています。
フロントエンドを実装する場合なら、graphqlクエリをソースコードとして定義して、取得したいカラムを絞るような流れになります

Mutation

GETメソッド以外のCREATE、PUT、DELETEなどの、テーブルに更新を掛ける処理はMutationとして定義されます。
Queryと同じようにShema内にMutationを定義して、resolvers内で使用できるようにします。
postメソッド内でargsを引数に取っています。argsはrest apiのres.bodyで取得できる引数と同じです。

const { ApolloServer, gql } = require("apollo-server");
import { Post } from "@prisma/client";
import { prismaContext } from "../lib/prismaContext";

const typeDefs = gql`
  type Query {
    posts: [Post]
  }

  type Post {
    id: Int!
    title: String!
    content: String
  }
 
  type Mutation {
    post(title: String!, content: String): Post!
  }
`

const resolvers = {
  Query: {
    posts: async (
      context: any
    ): Promise<Post> => {
      const posts = context.prismaContext.post.findMany();
      return posts;
    },
  },
  Mutation: {
    post: (
      args: { title: string; content: string },
      context: any
    ): Post => {
      const post = context.prismaContext.post.create({
        data: {
          title: args.title,
          content: args.content,
        },
      });
      return post;
    },
  },
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: {
    prismaContext,
  },
});

server
  .listen()
  .then(({ url }: any) => console.log(`graphql server started port ${url}`));

playgroundで叩いてみます。

postの処理に成功してレスポンスが返却されました。
この状態で一覧取得を叩いて追加されているかどうかを見てみます。

title02の投稿が追加されていることが確認できました。
これでApolloServer起動からQuery、Mutationの実装までOKです。

終わりに

今回は以上になります。
ここまで読んでいただき、ありがとうございました。