はじめに
こんにちは。
GMOソリューションパートナーのzakisanbaimanです。
GraphQLのOSSフレームワークであるApollo Serverのバージョン3が2024年10月22をもってEOLを迎えます。
それに伴って現時点(2024年3月)で最新バージョンである Apollo Server 4 へのアップデートが必要となるため、本記事で変更点および注意点について解説します。
この記事の要点
- Apollo Server3が2024年10月22日にEOLを迎える
- apollo-serverやapollo-server-codeなどは@apollo/serverパッケージに統合される
- サーバ起動の関数が変更となる
環境
- Node.js: 20
- Apollo Server: 3 → 4
変更点
1. パッケージ構成の変更
各パッケージが @apollo/~
へ統合されます。
- apollo-server → @apollo/server
- apollo-server-core → @apollo/server/pluginの配下にサブパッケージされている
- apollo-server-express → @apollo/server/express4
公式ドキュメントのパッケージ移行手順に従い、移行していきましょう。
ほとんどは置換でいけるはずですが、一部処理内容が変わっているものもあるため以下で説明します。
2. Apollo Server起動方法の変更
Apollo Server3においてapollo-serverを利用していたケースではApolloServerをnewし、listen関数で起動させていました。
(apollo-server-expressを利用していた場合は少し変更方法が変わるため、こちらをご参照ください。)
1 2 3 4 5 6 7 8 9 |
import { ApolloServer } from 'apollo-server'; const server = new ApolloServer({ typeDefs, resolvers, context: async ({ req }) => ({ token: req.headers.token }), }); const { url } = await server.listen(4000); console.log(`Server ready at ${url}`); |
Apollo Server4では上記の代替としてstartStandaloneServer関数で起動させます。
1 2 3 4 5 6 7 8 9 |
import { ApolloServer } from '@apollo/server'; import { startStandaloneServer } from '@apollo/server/standalone'; const server = new ApolloServer({ typeDefs, resolvers }); const { url } = await startStandaloneServer(server, { context: async ({ req }) => ({ token: req.headers.token }), listen: { port: 4000 }, }); console.log(`Server ready at ${url}`); |
※CORS設定に関して
注意点として、3系ではApolloServerのコンストラクタでCORS用のの設定を入れることができましたが、4系ではそれができなくなりました。
代わりにexpressMiddleware関数の引数に入れる必要があります。
(これに伴い、弊社ではstartStandaloneServer関数からapollo-server-expressパッケージのexpressMiddleware関数への変更を行いました。)
HTTP body parsing and CORS
3. 関連パッケージのアップデート(主にgraphql-tools)
Apollo Server4へのアップデート伴い、依存関係があるパッケージもアップデートする必要がありました。
その中で影響が大きかったのがgraphql-toolsの7系から10系へのアップデートです。
(個人的にここが一番苦労しました)
まずgraphql-toolsもバージョン10からは@graphql-tools/[機能別]の形式のみの配布となりました。
よく使うものとして、以下関数の移行先となります。
- makeExecutableSchema → @graphql-tools/schema
- mapSchema → @graphql-tools/utils
続いてgraphql-toolsを利用してカスタムディレクティブを作成する際、7系まではSchemaDirectiveVisitorクラスを継承したオブジェクトをmakeExecutableSchemaの引数に渡していました。
ただし8系からSchemaDirectiveVisitorが削除されたことで、クラスの代わりに自身でスキーマ変更関数を作成し、適用する必要があります。
(makeExecutableSchemaの引数からもschemaDirectivesが削除されました。)
参考までに @upperCase
というカスタムディレクティブを作成する例で比較してみます。
以下のようなtypeDefsが前提にあるとします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
const typeDefs = gql` directive @upperCase on FIELD_DEFINITION type Query { hello: String @upperCase } `; const resolvers = { Query: { hello() { return "Hello"; } } }; |
▼graphql-tools7系までの書き方
7系まではSchemaDirectiveVisitorを継承したクラスを作成し、ApolloServerコンストラクタの引数に渡していました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
const { ApolloServer, gql, SchemaDirectiveVisitor } = require("apollo-server"); const { defaultFieldResolver } = require("graphql"); import { makeExecutableSchema } from 'graphql-tools' const { SchemaDirectiveVisitor } = require("apollo-server"); class UpperCaseDirective extends SchemaDirectiveVisitor { visitFieldDefinition(field) { const { resolve = defaultFieldResolver } = field; field.resolve = async function (...args) { const result = await resolve.apply(this, args); if (typeof result === "string") { return result.toUpperCase(); } return result; }; } } const schema = makeExecutableSchema({ typeDefs, resolvers, schemaDirectives: { uppercase: UpperCaseDirective, } }); |
▼graphql-tools8系からの書き方
8系からは自身でスキーマ変更関数を作る必要があります。
同じmakeExecutableSchemaが登場していますが、schemaDirectivesが引数に取れなくなっているのが大きな違いです。
ここではupperDirectiveという関数を新たに作っています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
import { defaultFieldResolver, GraphQLSchema } from 'graphql' import { makeExecutableSchema } from '@graphql-tools/schema' import { getDirective, MapperKind, mapSchema } from '@graphql-tools/utils' function upperDirective(directiveName: string): (schema: GraphQLSchema) => GraphQLSchema { return schema => mapSchema(schema, { [MapperKind.OBJECT_FIELD]: fieldConfig => { const upperDirective = getDirective(schema, fieldConfig, directiveName)?.[0] if (upperDirective) { const { resolve = defaultFieldResolver } = fieldConfig return { ...fieldConfig, resolve: async function (source, args, context, info) { const result = await resolve(source, args, context, info) if (typeof result === 'string') { return result.toUpperCase() } return result } } } } }) } const applyUpperSchemaTransform = upperDirective('upperCase') let schema = makeExecutableSchema({ typeDefs, resolvers }); // カスタムディレクティブごとにschemaをTransformさせる schema = applyUpperSchemaTransform(schema) |
4. その他
あとは軽微な変更について紹介します。
- apollo-serverからApolloErrorが削除されたため、代わりにgraphqlのGraphQLErrorを継承。
- apollo-serverからgql関数が削除されたため、代わりにgraphql-tagをインポート。
おわりに
Apollo Serverパッケージ自体のアップデートはそれほど苦労はありませんでしたが、周辺パッケージとの依存関係やそれに関わる設計変更に手を焼きました。
また自身は今回で初めてGraphQLを触りましたが、当対応の中でGraphQLに対する理解を深められました。
引き続き各ツールのメンテは積極的に行っていければと思います。
参考
Migrating to Apollo Server 4
GraphQL Tools ドキュメント
採用情報
GMOソリューションパートナーではエンジニアを募集しています。
ご興味があればぜひエントリーしてみてください!
採用サイト