ザッと読める基礎シリーズ【GraphQL編】

投稿日:2023/03/18 最終更新日:2023/03/19

ザッと読める基礎シリーズ【GraphQL編】

GraphQLとは

・Graph+QL=GraphQL
→ Graph:データがグラフのように表される(node、edge)ためGraphという名前を利用
→ QL:クエリー言語の略でAPIで使用するクエリー言語であるということ
データベースをクエリーで操作するための言語ではない

・データはオブジェクトから構成される

REST APIの代替となる規格
→ GraphQLもクライアントからサーバに対しCRUD(Create, Delete, Update, Delete)を行う
GraphQLはエンドポイントが1つしか持たない(REST APIは複数)ためサーバーからのデータ取得を一括でできる

・Apollo Serverがよく利用されている
→ GraphQL サーバーを実装するためのライブラリ

GraphQLの導入

1. 必要なパッケージをインストールする

npm install graphql apollo-server-micro micro-cors
npm install --save-dev @types/micro-cors

2. GraphQLスキーマを作成

import { gql } from 'apollo-server-micro';

export const typeDefs = gql`
 type User {
  id: Int
  title: String
 }

 type Query {
  users: [User]!
 } 
`;

・スキーマ(構造)はgqlの中に記述する
→ []!はNon-null assertion operatorでここではUserにある型以外にはならないよという意味

・アプリケーションで扱うデータのフィールドや型を定義
→ ここではあくまで定義の設定でデータの操作はしない

・スキーマ→リゾルバの順で書くことをスキーマファーストというらしい
→ Nexusを使用する方がコードファーストのため便利になりがち(後々書き換えます)

3. GraphQLリゾルバーを作成

export const resolvers = {
 Query: {
  users: () => {
   return [
    {
     id: 1,
     title: name1',
    },
    {
     id: 2,
     title: 'name2',
    },
    {
     id: 3,
     title: 'name3',
    },
   ]
  }
 }
};

・中身のデータは仮で入れているもの

・実際にデータ操作を行うための設定を記述する
→ 特定のフィールドのデータを返す関数(メソッド)など

4. エンドポイントを作成

import { ApolloServer } from 'apollo-server-micro';
import { typeDefs } from '@/graphql/schema';
import { resolvers } from '@/graphql/resolvers';
import Cors from 'micro-cors';

const cors = Cors();
const apolloServer = new ApolloServer({ typeDefs, resolvers });
const startServer = apolloServer.start();

export default cors(async function handler(req, res) {
 if (req.method === 'OPTIONS') {
  res.end();
  return false;
 };
 await startServer;
 await apolloServer.createHandler({
  path: '/api/graphql',
 })(req, res)
});

export const config = {
 api: {
  bodyParser: false,
 }
};

・corsはオリジン間リソース共有のエラーを解決するためのミドルウェア
→ Apollo Studioを使用できるようにするために必要

・const apolloServer=の箇所ではGraphQLスキーマとリゾルバでApolloServerインスタンスを作成

・req.method === “OPTIONS”はプリフライトリクエストによるものを除外するため
→ 実際のリクエストはプリフライトリクエストの後に送信される
→ プリフライトリクエストはクロスオリジンに向けて送信して事前に安全かどうかを確かめる仕組み
→ プリフライトリクエストはメソッドがOPTIONSになる

・bodyParserはHTTP通信におけるメッセージボディをオブジェクトに変換するツール
→ falseにしないとGraphQLが扱えないため

5. npm run devを実行して/api/graphqlにアクセス

・ApolloStudioが使用出来るようになり、リゾルバに設定したデータが返ってくる

6. Prismaでクエリを操作する

※Prismaの基礎や導入方法についてはこちらをご覧ください

ザッと読める基礎シリーズ【Prisma編】

※既に読んでいる方は続きを進めてください

import { PrismaClient } from '@prisma/client';
import { prisma } from '@/prisma/prisma';

export type Context = { prisma: PrismaClient };

export async function createContext(): Promise<Context> { return { prisma, } };
import { ApolloServer } from 'apollo-server-micro';
import { typeDefs } from '@/graphql/schema';
import { resolvers } from '@/graphql/resolvers';
import { createContext } from "@/graphql/context";
import Cors from 'micro-cors';

const cors = Cors();
const apolloServer = new ApolloServer({ typeDefs, resolvers, context: createContext });
const startServer = apolloServer.start();
...

・これでresolverにてPrismaClientを使用可能になる

export const resolvers = {
 Query: {
  tasks: (_parent: any, _args: any, ctx: any) => {
   return ctx.prisma.user.findMany();
  },
 },
};

・厳密に言えばanyの使用は良くないですがとりあえずここでは当てています

・ctxはcontextで指定した値を返し、ctx.prismaはPrismaClientを指している
→ つまりPrismaを使用してデータベースからデータを取得できるようになる

7. Nexusを導入する

・今まではスキーマとリゾルバーを別々に書いていた
→ 別々だと書くのが面倒くさいし内容も異なるためややこしい
→ GraphQLのSDL(Schema Definition Language)で書いていたためタイプミスしやすい
→ そもそもTS/JSと記述の仕方が違う
→ エディタの補完が効かない

・Nexusを導入することで1つのファイルで定義することが出来る
→ 1本化されるので楽
→ TS/JSで書くことが出来る(コードファースト)
→ エディタの補完が効く

import { objectType, extendType } from 'nexus';

export const Task = objectType({
 name: 'User', definition(t) {
  t.nonNull.int('id')
  t.nonNull.string('title')
 },
});

export const TasksQuery = extendType({
 type: 'Query',
 definition(t) {
  t.nonNull.list.field('users', {
   type: 'User',
   resolve(_parent, _args, ctx) {
    return ctx.prisma.user.findMany()
   },
  })
 },
});

・Nexusを下記でインストールする

npm install nexus

・スキーマをNexusに置き換える

import { makeSchema } from 'nexus';
import { join } from 'path';
import * as types from './types';

export const schema = makeSchema({
 types,
 outputs: {
  typegen: join(process.cwd(), 'node_modules', '@types', 'nexus-typegen', 'index.d.ts'),
  schema: join(process.cwd(), 'graphql', 'schema.graphql'),
 },
 contextType: {
  export: 'Context',
  module: join(process.cwd(), 'graphql', 'context.ts'),
 },
});

・typesではobject typesの配列を指定する

・process.cwd()はスクリプトが実行時のディレクトリを取得する
→ typegen: node_modules/@types/nexus-typegen/index.d.ts
※型定義ファイルを指定ディレクトリに作成する
→ schema: graphql/schema.graphql
※GraphQLのSDKファイルを指定ディレクトリに作成する

・contextTypeではgraphql/context.tsを指定

import { ApolloServer } from 'apollo-server-micro';
import { schema } from "@/graphql/schema";
import { createContext } from "@/graphql/context";
import Cors from 'micro-cors';

const cors = Cors();
const apolloServer = new ApolloServer({ schema, context: createContext });
const startServer = apolloServer.start();
...

・typeDefsとresolversをschemaに置き換える
→ Nexusに置き換え

8. Apollo Clientを導入

npm install @apollo/client

・パッケージをインストールする

import { ApolloClient, InMemoryCache } from '@apollo/client';

const apolloClient = new ApolloClient({
 uri: 'http://localhost:3000/api/graphql',
 cache: new InMemoryCache(),
});

export default apolloClient;

・クライアント側でGraphQLクエリを使用出来るようにApolloServerのApolloインスタンスを設置

・uriにはGraphQLのエンドポイントを設定する

・cacheにinMemoryCacheを設定することで結果をキャッシュに保存してくれるようになる

import type { AppProps } from 'next/app';
import { ApolloProvider } from '@apollo/client';
import apolloClient from '@/graphql/apollo';

export default function App({ Component, pageProps }: AppProps) {
 return (
  <ApolloProvider client={ apolloClient }>
   <Component {...pageProps} />
  </ApolloProvider>
 );
};

・_app.tsにて変更を加える
→ 上記で各コンポーネントからApolloClientを使ってGraphQLクエリを送ることができる

参考資料

https://maku.blog/p/q7q4ahp/

https://reffect.co.jp/html/graphql

https://qiita.com/NagaokaKenichi/items/86272f2f654070b06488

https://qiita.com/zigenin/items/364264a6cf635b962542

https://woshidan.hatenablog.com/entry/2018/11/26/215356