Apollo Server 5でGraphQL APIを作る - Queryと認証付きMutation

中級 | 20分 で読める | 2025.12.02

公式ドキュメント

今回やること

Apollo Server 5で、次の2つだけを持つGraphQL APIを作ります。

  • 誰でも表示名を読める publicProfile Query
  • 認証された本人の表示名だけを変える updateMyDisplayName Mutation

学習用のメモリーデータを使うため、データベースは不要です。GraphQLの基本構造と、認証情報をMutationへ渡す境界に集中します。

表示名を読む公開Queryと、本人確認・入力検証を経て本人の表示名を更新するMutationの二つの経路。更新対象のIDは認証結果から決める

前提条件

  • Node.js 20以上
  • npmが使える
  • TypeScriptの関数とオブジェクトが読める

Apollo Server 5はNode.js 20以上を必要とします。次のコマンドで確認してください。

node --version
npm --version

プロジェクトを準備する

mkdir graphql-profile-api
cd graphql-profile-api
npm init -y
npm install @apollo/server graphql
npm install --save-dev typescript tsx @types/node
mkdir src

package.jsontypedevスクリプトを追加します。

{
  "name": "graphql-profile-api",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "tsx src/index.ts"
  },
  "dependencies": {
    "@apollo/server": "^5.0.0",
    "graphql": "^16.11.0"
  },
  "devDependencies": {
    "@types/node": "^24.0.0",
    "tsx": "^4.0.0",
    "typescript": "^5.0.0"
  }
}

npm installが作った正確なバージョンは環境によって異なります。手入力で上書きせず、そのまま使って構いません。

APIを実装する

src/index.tsを作成します。

import { ApolloServer } from "@apollo/server";
import { startStandaloneServer } from "@apollo/server/standalone";
import { GraphQLError } from "graphql";

type User = {
  id: string;
  displayName: string;
  email: string;
};

type GraphQLContext = {
  userId: string | null;
};

const users = new Map<string, User>([
  [
    "user-1",
    {
      id: "user-1",
      displayName: "Ada",
      email: "ada@example.com",
    },
  ],
]);

const typeDefs = `#graphql
  type PublicProfile {
    id: ID!
    displayName: String!
  }

  type Query {
    publicProfile(id: ID!): PublicProfile
  }

  type Mutation {
    updateMyDisplayName(displayName: String!): PublicProfile!
  }
`;

const resolvers = {
  Query: {
    publicProfile: (
      _parent: unknown,
      { id }: { id: string },
    ): User | null => users.get(id) ?? null,
  },
  Mutation: {
    updateMyDisplayName: (
      _parent: unknown,
      { displayName }: { displayName: string },
      context: GraphQLContext,
    ): User => {
      if (context.userId === null) {
        throw new GraphQLError("Authentication required", {
          extensions: { code: "UNAUTHENTICATED" },
        });
      }

      const user = users.get(context.userId);
      if (user === undefined) {
        throw new GraphQLError("User not found", {
          extensions: { code: "NOT_FOUND" },
        });
      }

      const normalizedName = displayName.trim();
      if (normalizedName.length < 1 || normalizedName.length > 30) {
        throw new GraphQLError("Display name must be 1 to 30 characters", {
          extensions: { code: "BAD_USER_INPUT" },
        });
      }

      user.displayName = normalizedName;
      return user;
    },
  },
};

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

const { url } = await startStandaloneServer(server, {
  listen: { port: 4000 },
  context: async ({ req }) => {
    const authorization = req.headers.authorization;

    // 学習用: この固定tokenをuser-1という本人に対応させる
    const userId =
      authorization === "Bearer learning-token" ? "user-1" : null;

    return { userId };
  },
});

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

この例で重要なのは次の3点です。

  1. GraphQLへ公開するPublicProfileemailを含めない
  2. Mutationの引数でユーザーIDを受け取らず、認証結果のcontext.userIdを使う
  3. 認証後も入力値を検証する

固定トークンとメモリーデータは学習用です。本番では、署名を検証したセッションやアクセストークンからsubject(利用者ID)を決め、永続データベースを更新します。

成功を確認する

サーバーを起動します。

npm run dev

別のターミナルから公開Queryを送ります。

curl http://localhost:4000/ \
  -H 'content-type: application/json' \
  --data '{"query":"query { publicProfile(id: \"user-1\") { id displayName } }"}'

次のように表示されれば成功です。

{"data":{"publicProfile":{"id":"user-1","displayName":"Ada"}}}

次に、認証付きMutationを送ります。

curl http://localhost:4000/ \
  -H 'content-type: application/json' \
  -H 'authorization: Bearer learning-token' \
  --data '{"query":"mutation { updateMyDisplayName(displayName: \"Ada Lovelace\") { id displayName } }"}'
{"data":{"updateMyDisplayName":{"id":"user-1","displayName":"Ada Lovelace"}}}

authorizationヘッダーを外して同じMutationを送ると、UNAUTHENTICATEDエラーになります。公開Queryの結果にemailが出ないことも確認してください。

よくあるエラー

Node.jsのバージョンが古い

Apollo Server 5はNode.js 20以上が必要です。node --versionを確認し、古ければ更新します。

Cannot find packageと表示される

プロジェクト直下で依存関係を入れ直します。

npm install

MutationがUNAUTHENTICATEDになる

ヘッダーがauthorization: Bearer learning-tokenになっているか確認します。Bearerの後ろには半角スペースが1つ必要です。

練習

表示名の最大文字数を20文字へ変え、21文字のMutationがBAD_USER_INPUTになることを確認してください。

次のステップ

この例を理解したら、固定トークンを本物の認証処理へ置き換えます。先にJWTの仕組みを読み、署名検証とsubjectの扱いを確認してください。

参考リソース

(最終確認: 2026年7月25日)

← 一覧に戻る
PR
PR
PR
PR