空雲 Blog

Eye catchapollo-server(v3系)は非推奨となったので、@apollo/server(v4系)に移行しましょう

publication: 2022/12/30
update:2024/02/20

ApolloServer3 のサポート終了は 2023/10/22

以下公式サイトにApollo Server 3の終了と、Apollo Server 4 移行の説明が載っています。

https://www.apollographql.com/docs/apollo-server/migration

現在使用しているパッケージがapollo-serverだった場合は非推奨バージョンです。できるだけ早く準備を整えて@apollo/serverに乗り換えましょう。

何が変わったのか

バラバラに散っていた機能が一つのパッケージに集約されました。その関係で切られる機能はバッサリ切られ、自分で書かなければならないコードが増えました。

情報が少ない

ネット上の記事はほぼApollo Server 3の頃のものばかりなので、公式以外の情報はあまり期待できません。こういう時に必要なのは、情報が少ないときほどワクワクする心を持つことです。新雪に最初に足跡を突っ込んでやるヒャッホーという気持ちこそが必要なのです。

サンプルを作ってみる

Next.js の APIRoute からアクセス出来る GraphQL のエンドポイントを作ってみます。ただし、普通にやるだけなら公式を見れば良いだろうという話になるので、なぜか人々が作るのを嫌がるファイルのアップロード機能を入れてみます。

Next.js のサンプル

https://github.com/SoraKumo001/next-apollo-server

API Route に GraphQL のエンドポイントを用意

Apollo Server4では、GraphQL の処理に executeHTTPGraphQLRequest を使用します。httpGraphQLRequest に適切な情報を載せて呼び出します。

必要な部分はパッケージから利用出来るようになっています。変換に関する具体的な処理は以下のコードを参照してください。
https://github.com/ReactLibraries/next-apollo-server/blob/master/src/index.ts

  • src/pages/api/graphql

1import { promises as fs } from 'fs';
2import { ApolloServer } from '@apollo/server';
3import { executeHTTPGraphQLRequest, FormidableFile } from '@react-libraries/next-apollo-server';
4import type { IResolvers } from '@graphql-tools/utils';
5import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next';
6
7/**
8 * Type settings for GraphQL
9 */
10const typeDefs = `
11 # Return date
12 scalar Date
13 type Query {
14 date: Date!
15 }
16 # Return file information
17 type File {
18 name: String!
19 type: String!
20 value: String!
21 }
22 scalar Upload
23 type Mutation {
24 upload(file: Upload!): File!
25 }
26`;
27
28/**
29 * Set Context type
30 */
31type Context = { req: NextApiRequest; res: NextApiResponse };
32
33/**
34 * Resolver for GraphQL
35 */
36const resolvers: IResolvers<Context> = {
37 Query: {
38 date: async (_context, _args) => new Date(),
39 },
40 Mutation: {
41 upload: async (_context, { file }: { file: FormidableFile }) => {
42 return {
43 name: file.originalFilename,
44 type: file.mimetype,
45 value: await fs.readFile(file.filepath, { encoding: 'utf8' }),
46 };
47 },
48 },
49};
50
51/**
52 * apolloServer
53 */
54const apolloServer = new ApolloServer<Context>({
55 typeDefs,
56 resolvers,
57});
58apolloServer.start();
59
60/**
61 * APIRoute handler for Next.js
62 */
63const handler: NextApiHandler = async (req, res) => {
64 //Convert NextApiRequest to body format for GraphQL (multipart/form-data support).
65 return executeHTTPGraphQLRequest({
66 req,
67 res,
68 apolloServer,
69 context: async () => ({ req, res }),
70 options: {
71 //Maximum upload file size set at 10 MB
72 maxFileSize: 10 * 1024 * 1024,
73 },
74 });
75};
76
77export default handler;
78
79export const config = {
80 api: {
81 bodyParser: false,
82 },
83};

フロント側の処理

  • src/pages/_app.tsx

createUploadLink でUploadタイプのパラメータを multipart 形式に変換させる必要があります。また、ヘッダーにapollo-require-preflightが必要です。

1import type { AppType } from "next/app";
2import { ApolloClient, ApolloProvider, InMemoryCache } from "@apollo/client";
3import { createUploadLink } from "apollo-upload-client";
4const endpoint = "/api/graphql";
5const uri =
6 typeof window === "undefined"
7 ? `${
8 process.env.VERCEL_URL
9 ? `https://${process.env.VERCEL_URL}`
10 : "http://localhost:3000"
11 }${endpoint}`
12 : endpoint;
13
14const App: AppType = ({ Component, pageProps }) => {
15 const client = new ApolloClient({
16 cache: new InMemoryCache(),
17 // Upload用
18 link: createUploadLink({
19 uri,
20 headers: { "apollo-require-preflight": "true" },
21 }),
22 });
23
24 return (
25 <ApolloProvider client={client}>
26 <Component {...pageProps} />
27 </ApolloProvider>
28 );
29};
30
31export default App;

  • src/pages/index.tsx

アップロードの処理は variables に blob オブジェクトのデータを載せるだけなので簡単です。
こちらのサンプルでは、ドラッグドロップされたデータをバックエンドに送って、内容を戻してもらい表示する実装になっています。
また、日付表示はおまけで、ファイルのアップロードとは関係ありません。

1import { gql, useMutation, useQuery } from '@apollo/client';
2
3// Date retrieval
4const QUERY = gql`
5 query date {
6 date
7 }
8`;
9
10// Uploading files
11const UPLOAD = gql`
12 mutation Upload($file: Upload!) {
13 upload(file: $file) {
14 name
15 type
16 value
17 }
18 }
19`;
20
21const Page = () => {
22 const { data, refetch } = useQuery(QUERY);
23 const [upload, { data: file }] = useMutation(UPLOAD);
24 return (
25 <>
26 <a target="_blank" href="https://github.com/SoraKumo001/next-apollo-server" rel="noreferrer">
27 Source code
28 </a>
29 <hr />
30 {/* SSRedacted data can be updated by refetch. */}
31 <button onClick={() => refetch()}>Update date</button>
32 {
33 /* Dates are output as SSR. */
34 data?.date && new Date(data.date).toLocaleString('en-US', { timeZone: 'UTC' })
35 }
36 {/* File upload sample from here down. */}
37 <div
38 style={{
39 height: '100px',
40 width: '100px',
41 background: 'lightgray',
42 marginTop: '8px',
43 padding: '8px',
44 }}
45 onDragOver={(e) => {
46 e.preventDefault();
47 }}
48 onDrop={(e) => {
49 const file = e.dataTransfer.files[0];
50 if (file) {
51 upload({ variables: { file } });
52 }
53 e.preventDefault();
54 }}
55 >
56 Upload Area
57 </div>
58 {/* Display of information on returned file data to check upload operation. */}
59 {file && <pre>{JSON.stringify(file, undefined, ' ')}</pre>}
60 </>
61 );
62};
63
64export default Page;

まとめ

Apollo Server 3は非推奨パッケージなので、早々にApollo Server 4への移行をお勧めします。