【Apollo】GraphQLサーバーを立ててクエリを試す


前提

OS:macOS Monterey 12.4
Node.js:v18.12.0

ApolloもGraphQLも今回初めて使いました。

GraphQLとは

公式

graphql.org


以下の記事がわかりやすかったです。

zenn.dev

hasura.io

hasura.io

www.redhat.com

Apolloとは

ApolloはGraphQLを扱うためのライブラリを提供しています。例えばGraphQLサーバーの実装をしたい場合は「apollo-server」というライブラリが、iOSアプリでGraphQLサーバーとのやりとりを実装したい場合は「apollo-ios」というライブラリが提供されています。

公式チュートリアル実施

公式のチュートリアル通りに進めたら詰まることなくできました。一応ざっくり手順を残しておきます。最低限の説明やコードだけ記載するので、詳細は公式のチュートリアルをご覧ください。 www.apollographql.com

プロジェクト作成

任意の場所に今回用のディレクトリを作成・移動し、プロジェクトのセットアップを行います。

mkdir graphql-server-example
cd graphql-server-example
npm init --yes


ライブラリのインストール

GraphQLとApollo Serverのライブラリをインストールします。

npm install @apollo/server graphql


実装準備

index.jsを用意します。

touch index.js


package.jsonを更新します。

{
  "type": "module",
  "scripts": {
    "start": "node index.js"
  }
}


スキーマの定義

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';

const typeDefs = `
  type Book {
    title: String
    author: String
  }

  type Query {
    books: [Book]
  }
`;


レスポンス用データの定義

const books = [
  {
    title: 'The Awakening',
    author: 'Kate Chopin',
  },
  {
    title: 'City of Glass',
    author: 'Paul Auster',
  },
];


ゾルバーの定義

const resolvers = {
  Query: {
    books: () => books,
  },
};


GraphQLサーバー起動

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

const { url } = await startStandaloneServer(server, {
  listen: { port: 4000 },
});

console.log(`🚀  Server ready at: ${url}`);


下記コマンドを実行すればGraphQLサーバーが起動します。

npm start


Sandboxを使ってクエリを試す

GraphQLサーバーが起動していれば http://localhost:4000 にアクセスすることでApollo Sandboxを使用できます。 「ExampleQuery」を押下すると「query ExampleQuery〜」と書かれている内容でリクエストすることができます。



クエリは自由に弄って試せます。クエリを新しく書いて、本のタイトルだけ取得してみます。



クエリは名前なしでも定義し実行できます。「query」すら省略可能です。



公式チュートリアル実施後の状態から色々試す

引数の使用

クエリ実行時に引数を使用してみます。本の「title」を渡して一致する本の「auther」を返してもらうイメージで作ってみます。下記のようにスキーマのQueryとリゾルバーを修正します。

const typeDefs = `
  type Book {
    title: String
    author: String
  }

  type Query {
    books(title: String): [Book]
  }
`;

const resolvers = {
  Query: {
    books: (parent, args, context, info) => books.filter((book) => book.title === args.title),
  },
};


ゾルバーの引数についてはドキュメントをご覧ください。 www.apollographql.com

以下のように、本の「title」を渡して一致する本の「auther」を返してもらうことができました。



引数に変数を使用

これはApollo Serverの話ではなくGraphQLのリクエスト側の話ですが、以下のように変数を使用することができます。コードを弄る必要はありません。


デフォルト引数も使えます。



フラグメント

リクエストするフィールドの定義をフラグメントという形でひとまとめにして共通化することができます。コードを弄る必要はありません。



インラインフラグメントというのもあるのですが、ちょっとしっくりきてないのでまた別の機会に…

null非許容

型の後ろに「!」をつけるとnullが入らなくなります。例えば下記のように引数の型に「!」をつけるとnullを受け付けなくなります。

const typeDefs = `
  type Book {
    title: String
    author: String
  }

  type Query {
    books(title: String!): [Book]
  }
`;


当然ですがドキュメントにも記載があります。

www.apollographql.com

Queryを複数定義する

同じブロック内に複数書いても別で書いても問題なく動きました。

const typeDefs = `
  type Book {
    title: String
    author: String
  }

  type Query {
    books(title: String!): [Book],
    books2: [Book],
    books3: [Book]
  }

  type Query {
    books4: [Book]
  }
`;

const resolvers = {
  Query: {
    books: (parent, args, context, info) => books.filter((book) => book.title === args.title),
    books2: () => books,
    books3: () => books,
  },
  Query: {
    books4: () => books,
  },
};


Mutationを定義する

ここまでクエリのオペレーションタイプは全てQueryでしたので、Mutationも試してみます。とはいってもQueryと定義方法は変わりません。

const typeDefs = `
  type Book {
    title: String
    author: String
  }

  type Query {
    books(title: String!): [Book],
  }

  type Mutation {
    mutation_test_books: [Book]
  }
`;

const resolvers = {
  Query: {
    books: (parent, args, context, info) => books.filter((book) => book.title === args.title),
  },
  Mutation: {
    mutation_test_books: () => books,
  },
};



Subscriptionを試すのは別の機会に…

スキーマを外部ファイルに定義する

下記の記事が参考になりました。

zenn.dev
graphqlファイルにスキーマを定義し、読み込んで使用します。まずは必要なライブラリをインストールします。

npm install @graphql-tools/graphql-file-loader @graphql-tools/load @graphql-tools/schema


graphqlファイルを作り、スキーマを定義します。


schema.graphqlから読み込むように書き換えます。チュートリアルとの違いは、ライブラリを使ったschema.graphqlの読み込みと設定になります。

import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader';
import { loadSchemaSync } from '@graphql-tools/load';
import { addResolversToSchema } from '@graphql-tools/schema';

// 読み込み
const schema = loadSchemaSync('./schema.graphql', {
  loaders: [new GraphQLFileLoader()],
});

// 設定
const schemaWithResolvers = addResolversToSchema({ schema, resolvers });
const server = new ApolloServer({ schema: schemaWithResolvers });


全体としては以下のような感じになります。

type Book {
  title: String
  author: String
}

type Query {
  books(title: String): [Book]
}

type Mutation {
  mutation_test_books: [Book]
}
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader';
import { loadSchemaSync } from '@graphql-tools/load';
import { addResolversToSchema } from '@graphql-tools/schema';

const schema = loadSchemaSync('./schema.graphql', {
  loaders: [new GraphQLFileLoader()],
});

const books = [
  {
    title: 'The Awakening',
    author: 'Kate Chopin',
  },
  {
    title: 'City of Glass',
    author: 'Paul Auster',
  },
];

const resolvers = {
  Query: {
    books: (parent, args, context, info) => books.filter((book) => book.title === args.title),
  },
  Mutation: {
    mutation_test_books: () => books,
  },
};

const schemaWithResolvers = addResolversToSchema({ schema, resolvers });
const server = new ApolloServer({ schema: schemaWithResolvers });

const { url } = await startStandaloneServer(server, {
  listen: { port: 4000 },
});

console.log(`🚀  Server ready at: ${url}`);