目次

AWS AppSync 完全ガイド v2.0

GraphQL・WebSocket・リアルタイムデータ統合の実装

AWS AppSync は、「フルマネージドの GraphQL・Pub/Sub API サービスで、複数のデータソース(DynamoDB・Lambda・RDS・HTTP・OpenSearch)を単一エンドポイントで統合し、リアルタイム WebSocket サブスクリプションをネイティブサポートするサービス」 です。2025年3月の AppSync Events 機能リリース以降、WebSocket ベースの Pub/Sub API で Kafka・SQS 連携の高度なイベント駆動アーキテクチャが実現可能になりました。本ドキュメントは初心者から実務者向けに、GraphQL スキーマ・リゾルバー・リアルタイム同期・2025-2026 動向を体系的に解説する包括的ガイドです。

ドキュメントの目的

本ガイドは以下を対象としています。

  • 初心者向け: GraphQL とは何か、AppSync の基本を学びたい方
  • フロントエンド開発者向け: React / Vue.js から AppSync API を呼び出したい方
  • バックエンド開発者向け: リゾルバー・パイプラインの実装、データソース統合
  • モバイル開発者向け: AWS Amplify DataStore でオフライン同期を実現したい方
  • 意思決想者向け: AppSync vs REST API Gateway vs Apollo Server の選定

2025-2026 年の AppSync エコシステム

  • AppSync Events(2025年3月 GA): WebSocket Pub/Sub API で Kafka・EventBridge 統合
  • Merged GraphQL API(2023年 GA): マイクロサービス Federation による複数 API 統合
  • JavaScript リゾルバー(推奨): VTL より学習コスト低く、デバッグ容易
  • ORM 統合(Amplify Studio): Visual Schema Editor で型安全な GraphQL スキーマ生成
  • Caching 拡張(2025年): TTL・Invalidation by Key のきめ細かいキャッシュ戦略

目次

  1. 概要
  2. AppSync が解決する課題
  3. 主な特徴
  4. アーキテクチャ
  5. コアコンポーネント
  6. GraphQL スキーマ設計
  7. リゾルバーと Mapping Template
  8. データソース統合
  9. リアルタイム機能
  10. 認証・認可
  11. 主要ユースケース
  12. 設定・操作の具体例
  13. 類似サービス比較表
  14. ベストプラクティス
  15. トラブルシューティング
  16. 2025-2026 最新動向
  17. 学習リソース・参考文献
  18. 実装例
  19. チェックリスト
  20. まとめ

概要

初心者向けメモ: AppSync は「REST API では複数エンドポイントへの複数リクエストが必要なデータを、GraphQL なら 1 つのクエリで必要なフィールドだけ効率的に取得できる」という GraphQL の利点を実現するサービスです。通常は Apollo Server・Hasura などで GraphQL サーバーを自分で構築・運用しますが、AppSync はこれを AWS がフルマネージド化。リアルタイムサブスクリプション(WebSocket)も標準装備で、モバイル・Web アプリのバックエンド API として最適化されています。

AWS AppSync 公式定義:

“AWS AppSync is a managed service that uses GraphQL to make it easy for applications to get exactly the data they need, with built-in features like real-time capabilities and offline access.”

このサービスを選ぶ理由

なぜ AppSync でないといけないのか?

理由 詳細
N+1 問題の解決 REST なら /users → /posts → /comments で 3 リクエスト必要。GraphQL なら 1 クエリで完全なデータグラフ取得
リアルタイム標準装備 WebSocket サブスクリプションで Mutation 実行時に自動 Push、チャット・ライブダッシュボード・通知機能
複数データソース統合 DynamoDB・RDS・Lambda・HTTP・OpenSearch を 1 GraphQL エンドポイントで統合
オフライン同期(Amplify DataStore) モバイルアプリでオフライン中に書き込み、オンライン時に自動同期・競合解決
セキュリティ・管理 IAM・Cognito・OIDC など複数認証方式、CloudWatch Logs・X-Ray トレーシング標準装備

具体的なユースケース

  • React / Next.js アプリのデータフェッチ最適化(REST から GraphQL への移行)
  • iOS / Android モバイルアプリのバックエンド API(Amplify DataStore でオフライン対応)
  • チャット・リアルタイム通知機能(GraphQL サブスクリプション)
  • 複数 API(Salesforce / DynamoDB / Lambda)を GraphQL で統合したデータゲートウェイ
  • マイクロサービス Federation(Merged GraphQL API で複数チームの API を統合)

AppSync が解決する課題

課題 従来(REST API Gateway) AppSync 解決方法
複数 API エンドポイント管理 /users / /posts / /comments で複数エンドポイント、クライアント側で複合リクエスト GraphQL なら 1 エンドポイント、クライアントが必要フィールド指定
N+1 クエリ問題 リスト取得後に各レコードの関連データ個別取得で RPC 増加 リゾルバーの並列化、DataLoader パターンで効率化
オーバーフェッチ API が不要なフィールドも含めて返す、bandwidth 浪費 GraphQL で必要フィールドのみ指定・取得
リアルタイム実装の複雑さ WebSocket サーバー自分で構築、接続管理が複雑 AppSync サブスクリプション標準装備、管理不要
オフライン対応 キャッシング・同期ロジック自分で実装 Amplify DataStore で自動同期・競合解決
データソース統合 Lambda で複数データベース・API を個別接続 AppSync リゾルバーで宣言的に統合
型安全 REST は Swagger / OpenAPI で定義、型チェック弱い GraphQL スキーマで強力な型チェック

主な特徴

1. GraphQL API

クエリ(読み取り)

  • 単一・複数エンティティ取得
  • ネストされた関連データ取得
  • フィルタリング・ソート・ページネーション

ミューテーション(書き込み)

  • 新規作成・更新・削除
  • トランザクション(複数リソース同時更新)

サブスクリプション(リアルタイム)

  • Mutation 実行時に自動通知
  • WebSocket による常時接続

2. リゾルバー

JavaScript(2022年 GA 推奨)

  • TypeScript 型定義で安全なコード
  • VTL より学習コスト低い
  • デバッグが容易

VTL(Velocity Template Language)

  • レガシー、新規開発では非推奨
  • テンプレート構文で DynamoDB / HTTP リクエスト生成

パイプラインリゾルバー

  • 複数 Function の連鎖実行
  • 各ステップで前ステップの結果を参照可能

3. データソース

データソース 最適用途
DynamoDB メインデータストア、スケーラビリティ必要
RDS(Aurora) リレーショナルデータ、複雑 Query
Lambda カスタムビジネスロジック、外部 API 呼び出し
HTTP エンドポイント REST API・外部サービス連携
OpenSearch 全文検索・ログ分析
Elasticsearch 類似度検索・アナリティクス

4. リアルタイム機能

GraphQL Subscriptions

  • WebSocket 接続で Mutation 実行時に自動 Push
  • チャット・ライブフィード・コラボ編集

AppSync Events(2025年3月新規)

  • Kafka / EventBridge / Kinesis からのイベント Pub/Sub
  • Serverless WebSocket で低遅延通信

アーキテクチャ

graph TD
    A["クライアント<br/>React / iOS / Android"]
    B["HTTPS / WebSocket<br/>AppSync Endpoint"]
    
    subgraph AppSync["AWS AppSync API"]
        C["GraphQL Schema"]
        D["Resolver<br/>Query / Mutation / Subscription"]
        E["Pipeline Function"]
    end
    
    subgraph DataSource["Data Sources"]
        F["DynamoDB"]
        G["RDS / Aurora"]
        H["Lambda"]
        I["HTTP / REST API"]
        J["OpenSearch"]
        K["Kafka / EventBridge<br/>Events"]
    end
    
    subgraph Security["Security & Auth"]
        L["API Key / Cognito / IAM / OIDC / Lambda Authorizer"]
        M["Field-level Authorization<br/>@aws_auth directive"]
    end
    
    subgraph Observability["Observability"]
        N["CloudWatch Logs"]
        O["X-Ray Tracing"]
        P["CloudWatch Metrics"]
    end
    
    A -->|HTTPS Query / Mutation| B
    A -->|WebSocket wss://| B
    B --> C
    C --> D
    D --> E
    E --> F
    E --> G
    E --> H
    E --> I
    E --> J
    E --> K
    B --> L
    L --> M
    B --> N
    B --> O
    B --> P
    
    style A fill:#e6f3ff
    style AppSync fill:#fff0e6
    style DataSource fill:#e6ffe6
    style Security fill:#ffe6e6
    style Observability fill:#f0e6ff

コアコンポーネント

1. GraphQL API

AppSync の主要なリソース。スキーマとリゾルバーを管理します。

  • API ID: GraphQL endpoint の一意識別子
  • Endpoint URL: https://xxx.appsync-api.ap-northeast-1.amazonaws.com/graphql
  • API Key / Cognito User Pool / IAM / OIDC / Lambda Authorizer: 認証方式選択

2. GraphQL Schema(型定義)

type Query {
  getUser(id: ID!): User
  listUsers(limit: Int): [User!]!
}

type User {
  id: ID!
  name: String!
  email: String!
  posts: [Post!]!
}

type Post {
  id: ID!
  title: String!
  content: String!
  author: User!
}

type Mutation {
  createPost(input: CreatePostInput!): Post!
  updatePost(id: ID!, input: UpdatePostInput!): Post!
}

input CreatePostInput {
  title: String!
  content: String!
  authorId: ID!
}

type Subscription {
  onPostCreated: Post @aws_subscribe(mutations: ["createPost"])
  onPostUpdated(id: ID!): Post @aws_subscribe(mutations: ["updatePost"])
}

3. Resolver(リゾルバー)

JavaScript Resolver 例:DynamoDB GetItem

import { util } from '@aws-appsync/utils';

export function request(ctx) {
  return {
    operation: 'GetItem',
    key: util.dynamodb.toMapValues({ id: ctx.args.id }),
  };
}

export function response(ctx) {
  if (ctx.error) {
    util.error(ctx.error.message);
  }
  return ctx.result;
}

JavaScript Resolver 例:Lambda 呼び出し

export function request(ctx) {
  const payload = {
    action: 'processData',
    data: ctx.args.input,
  };
  
  return {
    operation: 'Invoke',
    payload,
  };
}

export function response(ctx) {
  const result = JSON.parse(ctx.result.body);
  return result;
}

4. Pipeline Resolver

複数 Function を順序実行。

// Before Pipeline
export function request(ctx) {
  return ctx;
}

// Function 1: User 取得
// Function 2: User の posts 取得
// Function 3: Response キャッシング

export function response(ctx) {
  return ctx.prev.result;
}

GraphQL スキーマ設計

基本型定義

# スカラー型
type Post {
  id: ID!                    # 必須フィールド
  title: String!             # 必須文字列
  content: String            # オプション(null 許可)
  views: Int!                # 整数
  rating: Float              # 浮動小数点
  published: Boolean!        # 真偽値
  tags: [String!]!           # 文字列配列(必須)
  createdAt: AWSDateTime!    # AWS DateTime
  metadata: AWSJSON         # JSON オブジェクト
}

# 入力型
input CreatePostInput {
  title: String!
  content: String!
  tags: [String!]
}

# インターフェース
interface Node {
  id: ID!
}

type User implements Node {
  id: ID!
  name: String!
}

# ユニオン型
union SearchResult = Post | User | Comment

ディレクティブによる認可

type Post {
  id: ID!
  title: String!
  content: String!
  # Admin のみ閲覧可能
  internalNotes: String @aws_auth(cognito_groups: ["Admin"])
  # オーナーと Admin のみ編集可能
  draftContent: String @aws_auth(cognito_groups: ["Admin", "Author"])
}

type Query {
  # 認証済みユーザーのみ実行可能
  getPrivateData: String @aws_auth(cognito_user_pools: ["default"])
  # 複数認証方式対応
  getPublicData: String @aws_api_key @aws_iam
}

リゾルバーと Mapping Template

JavaScript リゾルバー(推奨)

DynamoDB Query

import { util } from '@aws-appsync/utils';

export function request(ctx) {
  const { userId, limit = 10, nextToken } = ctx.args;
  
  return {
    operation: 'Query',
    index: 'userIdIndex',
    query: {
      expression: 'userId = :userId',
      expressionValues: util.dynamodb.toMapValues({
        ':userId': userId,
      }),
    },
    limit,
    nextToken,
  };
}

export function response(ctx) {
  if (ctx.error) {
    util.error(`Query failed: ${ctx.error.message}`);
  }
  return {
    items: ctx.result.items || [],
    nextToken: ctx.result.nextToken,
  };
}

RDS Query(Aurora)

export function request(ctx) {
  const { userId } = ctx.args;
  
  return {
    version: '1',
    statements: [
      `SELECT * FROM users WHERE id = '${userId}'`,
    ],
  };
}

export function response(ctx) {
  if (ctx.errors.length > 0) {
    util.error(`RDS error: ${ctx.errors[0]}`);
  }
  return ctx.result.records[0][0];
}

HTTP エンドポイント(外部 API)

export function request(ctx) {
  const { query } = ctx.args;
  
  return {
    method: 'GET',
    resourcePath: '/search',
    params: {
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${ctx.request.headers.authorization}`,
      },
      queryString: {
        q: query,
        limit: 10,
      },
    },
  };
}

export function response(ctx) {
  const result = JSON.parse(ctx.result.body);
  return {
    results: result.items.map(item => ({
      id: item.id,
      title: item.name,
    })),
  };
}

VTL リゾルバー(レガシー)

## Request Mapping Template
{
  "version": "2017-02-28",
  "operation": "GetItem",
  "key": {
    "id": $util.dynamodb.toDynamoDBJson($ctx.args.id)
  }
}

## Response Mapping Template
#if ($ctx.result)
  $util.toJson($ctx.result)
#else
  $util.error("User not found")
#end

データソース統合

DynamoDB テーブル

# IAM ロール(AppSync 実行ロール)に権限を付与
{
  "Effect": "Allow",
  "Action": [
    "dynamodb:GetItem",
    "dynamodb:Query",
    "dynamodb:PutItem",
    "dynamodb:UpdateItem"
  ],
  "Resource": "arn:aws:dynamodb:ap-northeast-1:123456789012:table/Users"
}

Lambda データソース

# Lambda 関数の定義
import json

def lambda_handler(event, context):
    payload = json.loads(event.get('payload', '{}'))
    
    # ビジネスロジック
    result = {
        'status': 'success',
        'data': payload
    }
    
    return json.dumps(result)

RDS データソース

# RDS Data API で接続(IAM 認証)
ARN of Secret: arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:rds-postgres-prod
Database: myapp

リアルタイム機能

GraphQL Subscription

スキーマ定義

type Subscription {
  onOrderCreated(userId: ID!): Order
    @aws_subscribe(mutations: ["createOrder"])
  
  onPostUpdated(id: ID!): Post
    @aws_subscribe(mutations: ["updatePost"])
}

クライアント実装(React)

import { API, graphqlOperation } from 'aws-amplify';

const onOrderCreatedSubscription = `
  subscription OnOrderCreated($userId: ID!) {
    onOrderCreated(userId: $userId) {
      id
      status
      totalPrice
      createdAt
    }
  }
`;

export function useOrderSubscription(userId) {
  const [orders, setOrders] = useState([]);
  
  useEffect(() => {
    const subscription = API.graphql(
      graphqlOperation(onOrderCreatedSubscription, { userId })
    ).subscribe({
      next: ({ value }) => {
        const newOrder = value.data.onOrderCreated;
        setOrders(prev => [newOrder, ...prev]);
      },
      error: (error) => console.error('Subscription error:', error),
    });
    
    return () => subscription.unsubscribe();
  }, [userId]);
  
  return orders;
}

AppSync Events(2025年3月 GA)

Kafka からのイベント Pub/Sub

# AppSync Events で Kafka cluster 連携
- Kafka Topic: orders.created
- Consumer Group: appsync-orders
- Payload: JSON

# クライアント側:WebSocket で subscribe
subscription {
  onOrderCreated {
    orderId
    customerId
    amount
  }
}

認証・認可

認証方式

方式 用途 設定
API Key 開発・パブリック読み取り専用 最大 365 日有効期限
AWS IAM サービス間通信・Lambda 権限 SigV4 署名
Amazon Cognito ユーザー認証済み API JWT トークン
OIDC Okta / Auth0 連携 OpenID Connect provider
Lambda Authorizer カスタム認証ロジック 独自トークン

マルチ認証

type Query {
  # 公開データ(API Key)
  getPublicUser(id: ID!): User @aws_api_key
  
  # 認証済みユーザーのみ(Cognito)
  getPrivateProfile: User @aws_cognito_user_pools
  
  # IAM ロール / Cognito(複数認証)
  getProfile: User @aws_iam @aws_cognito_user_pools
}

フィールドレベル認可

type User {
  id: ID!
  name: String!
  # Public
  email: String!
  # Admin のみ
  internalId: String! @aws_auth(cognito_groups: ["Admin"])
  # オーナーのみ
  ssn: String! @aws_auth(cognito_user_pools: ["default"])
}

主要ユースケース

1. React / Next.js フロントエンド

シナリオ

  • React Query で AppSync GraphQL を使用
  • Server-Side Rendering(SSR)で初期データ取得
  • リアルタイム購読でダッシュボード更新

実装

import { useQuery } from 'react-query';
import { API } from 'aws-amplify';

const getPostsQuery = `
  query GetPosts($limit: Int!) {
    listPosts(limit: $limit) {
      items {
        id
        title
        content
        author {
          id
          name
        }
        comments {
          id
          text
        }
      }
    }
  }
`;

export function PostsList() {
  const { data, isLoading } = useQuery('posts', async () => {
    const result = await API.graphql({
      query: getPostsQuery,
      variables: { limit: 20 },
    });
    return result.data.listPosts.items;
  });
  
  return (
    <div>
      {isLoading ? <Spinner /> : (
        <ul>
          {data?.map(post => (
            <li key={post.id}>
              <h2>{post.title}</h2>
              <p>By {post.author.name}</p>
              <p>{post.content}</p>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

2. モバイルアプリ(iOS / Android)

シナリオ

  • Amplify DataStore でオフライン同期
  • GraphQL サブスクリプションでリアルタイム更新
  • モバイルネットワーク対応(接続断時の自動キューイング)

実装(iOS Swift)

import Amplify
import AWSDataStorePlugin

// Model 定義
struct Post: Model {
  let id: String
  let title: String
  let content: String
}

// オフライン同期
func syncPosts() async {
  do {
    // ローカルキャッシュから読み込み
    let posts = try await Amplify.DataStore.query(Post.self)
    
    // サーバーと同期
    try await Amplify.DataStore.start()
  } catch {
    print("Failed to sync posts: \(error)")
  }
}

// リアルタイム購読
func subscribeToPostUpdates() {
  let subscription = Amplify.API.subscribe(
    request: .subscription(onPostUpdated)
  ) { event in
    switch event {
    case .connection(let subscriptionConnection):
      print("Subscription connected: \(subscriptionConnection)")
    case .data(let result):
      switch result {
      case .success(let data):
        print("Received update: \(data)")
      case .failure(let error):
        print("Subscription error: \(error)")
      }
    }
  }
}

3. チャット・リアルタイム通知

シナリオ

  • GraphQL Subscription で メッセージ/通知リアルタイム配信
  • WebSocket 接続継続維持

スキーマ

type Message {
  id: ID!
  conversationId: ID!
  senderId: ID!
  text: String!
  createdAt: AWSDateTime!
}

type Subscription {
  onMessageCreated(conversationId: ID!): Message
    @aws_subscribe(mutations: ["createMessage"])
}

type Mutation {
  createMessage(input: CreateMessageInput!): Message
}

4.複数データソース統合(データゲートウェイ)

シナリオ

  • Salesforce / DynamoDB / RDS を GraphQL で統合
  • クライアント側は 1 API で統一アクセス

リゾルバー設計

type Customer {
  id: ID!
  name: String!
  # Salesforce から
  salesforceId: String!
  accounts: [Account!]!
  # DynamoDB から
  orders: [Order!]!
  # RDS から
  profile: Profile!
}

type Query {
  getCustomer(id: ID!): Customer
}

設定・操作の具体例

CLI 例 1:GraphQL API の作成

# AWS CLI で GraphQL API 作成
aws appsync create-graphql-api \
  --name my-app-api \
  --authentication-type API_KEY \
  --api-type GRAPHQL \
  --region ap-northeast-1

# API ID を取得
API_ID=$(aws appsync list-graphql-apis --region ap-northeast-1 | jq -r '.graphqlApis[0].apiId')
echo "API ID: $API_ID"

CLI 例 2:GraphQL Schema アップロード

# schema.graphql を AppSync にアップロード
aws appsync start-schema-creation \
  --api-id $API_ID \
  --definition fileb://schema.graphql \
  --region ap-northeast-1

schema.graphql

type Query {
  getPost(id: ID!): Post
  listPosts(limit: Int): [Post!]!
}

type Post {
  id: ID!
  title: String!
  content: String!
  author: String!
  createdAt: AWSDateTime!
}

type Mutation {
  createPost(input: CreatePostInput!): Post
}

input CreatePostInput {
  title: String!
  content: String!
  author: String!
}

type Subscription {
  onPostCreated: Post @aws_subscribe(mutations: ["createPost"])
}

CLI 例 3:DynamoDB データソース作成

# IAM ロール作成
ROLE_ARN=$(aws iam create-role \
  --role-name AppSyncDynamoDBRole \
  --assume-role-policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Principal": {"Service": "appsync.amazonaws.com"},
      "Action": "sts:AssumeRole"
    }]
  }' | jq -r '.Role.Arn')

# DynamoDB ポリシー付与
aws iam put-role-policy \
  --role-name AppSyncDynamoDBRole \
  --policy-name DynamoDBAccess \
  --policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Action": ["dynamodb:*"],
      "Resource": "arn:aws:dynamodb:ap-northeast-1:123456789012:table/Posts"
    }]
  }'

# データソース作成
aws appsync create-data-source \
  --api-id $API_ID \
  --name PostsTable \
  --type AMAZON_DYNAMODB \
  --dynamo-db-config tableName=Posts,awsRegion=ap-northeast-1 \
  --service-role-arn $ROLE_ARN \
  --region ap-northeast-1

CLI 例 4:リゾルバー作成

# Query.getPost リゾルバー(JavaScript)
aws appsync create-resolver \
  --api-id $API_ID \
  --type-name Query \
  --field-name getPost \
  --data-source-name PostsTable \
  --code fileb://resolver.js \
  --runtime-version '1.0' \
  --kind UNIT \
  --region ap-northeast-1

resolver.js

import { util } from '@aws-appsync/utils';

export function request(ctx) {
  return {
    operation: 'GetItem',
    key: util.dynamodb.toMapValues({ id: ctx.args.id }),
  };
}

export function response(ctx) {
  return ctx.result;
}

SDK 例(JavaScript)

import { AWSAppSyncClient } from 'aws-appsync';
import { gql } from '@apollo/client';
import AWSAppSyncAuth from 'aws-appsync-auth-link';

// Client 初期化
const client = new AWSAppSyncClient({
  url: 'https://xxx.appsync-api.ap-northeast-1.amazonaws.com/graphql',
  region: 'ap-northeast-1',
  auth: {
    type: 'AWS_IAM',
    credentials: null,
  },
  complexObjectsCredentials: () => Auth.currentCredentials(),
});

// Query 実行
const getPostQuery = gql`
  query GetPost($id: ID!) {
    getPost(id: $id) {
      id
      title
      content
      author {
        id
        name
      }
    }
  }
`;

async function fetchPost(postId) {
  const result = await client.query({
    query: getPostQuery,
    variables: { id: postId },
  });
  
  return result.data.getPost;
}

IaC 例(CloudFormation)

AWSTemplateFormatVersion: '2010-09-09'
Description: 'AppSync GraphQL API'

Resources:
  AppSyncAPI:
    Type: AWS::AppSync::GraphQLApi
    Properties:
      Name: my-app-api
      AuthenticationType: AMAZON_COGNITO_USER_POOLS
      UserPoolConfig:
        UserPoolId: !Ref UserPool
        AwsRegion: !Ref AWS::Region
        DefaultAction: ALLOW

  GraphQLSchema:
    Type: AWS::AppSync::GraphQLSchema
    Properties:
      ApiId: !GetAtt AppSyncAPI.ApiId
      Definition: |
        type Query {
          getPost(id: ID!): Post
        }
        type Post {
          id: ID!
          title: String!
        }

  DynamoDBDataSource:
    Type: AWS::AppSync::DataSource
    Properties:
      ApiId: !GetAtt AppSyncAPI.ApiId
      Name: PostsTable
      Type: AMAZON_DYNAMODB
      DynamoDBConfig:
        TableName: !Ref PostsTable
        AwsRegion: !Ref AWS::Region
      ServiceRoleArn: !GetAtt AppSyncRole.Arn

  QueryGetPostResolver:
    Type: AWS::AppSync::Resolver
    Properties:
      ApiId: !GetAtt AppSyncAPI.ApiId
      TypeName: Query
      FieldName: getPost
      DataSourceName: !GetAtt DynamoDBDataSource.Name
      Code: |
        import { util } from '@aws-appsync/utils';
        export function request(ctx) {
          return {
            operation: 'GetItem',
            key: util.dynamodb.toMapValues({ id: ctx.args.id }),
          };
        }
        export function response(ctx) {
          return ctx.result;
        }
      Runtime: !Sub
        - arn:aws:appsync:${AWS::Region}:${AWS::AccountId}:runtime/appsync-js/${Version}
        - Version: '1.0'

  AppSyncRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: appsync.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: DynamoDBAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - dynamodb:GetItem
                  - dynamodb:Query
                Resource: !GetAtt PostsTable.Arn

  PostsTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: Posts
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH
      BillingMode: PAY_PER_REQUEST

IaC 例(Terraform)

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

resource "aws_appsync_graphql_api" "main" {
  name                = "my-app-api"
  authentication_type = "AMAZON_COGNITO_USER_POOLS"
  
  user_pool_config {
    user_pool_id      = aws_cognito_user_pool.main.id
    aws_region        = var.aws_region
    default_action    = "ALLOW"
  }
}

resource "aws_appsync_graphql_schema" "main" {
  api_id = aws_appsync_graphql_api.main.id
  
  definition = <<EOF
type Query {
  getPost(id: ID!): Post
  listPosts(limit: Int): [Post!]!
}

type Post {
  id: ID!
  title: String!
  content: String!
}

type Mutation {
  createPost(input: CreatePostInput!): Post
}

input CreatePostInput {
  title: String!
  content: String!
}

type Subscription {
  onPostCreated: Post @aws_subscribe(mutations: ["createPost"])
}
EOF
}

resource "aws_appsync_data_source" "posts_table" {
  api_id           = aws_appsync_graphql_api.main.id
  name             = "PostsTable"
  type             = "AMAZON_DYNAMODB"
  service_role_arn = aws_iam_role.appsync.arn
  
  dynamodb_config {
    table_name = aws_dynamodb_table.posts.name
  }
}

resource "aws_appsync_resolver" "query_get_post" {
  api_id      = aws_appsync_graphql_api.main.id
  type_name   = "Query"
  field_name  = "getPost"
  data_source = aws_appsync_data_source.posts_table.name
  
  code = <<EOF
import { util } from '@aws-appsync/utils';

export function request(ctx) {
  return {
    operation: 'GetItem',
    key: util.dynamodb.toMapValues({ id: ctx.args.id }),
  };
}

export function response(ctx) {
  return ctx.result;
}
EOF

  runtime {
    name            = "APPSYNC_JS"
    runtime_version = "1.0.0"
  }
  
  kind = "UNIT"
}

類似サービス比較表

観点 AppSync Apollo Server Hasura GraphQL Yoga Postgraphile
管理方式 フルマネージド 自分で構築・管理 自分で構築・管理 自分で構築・管理 自分で構築・管理
リアルタイム WebSocket 標準 要実装 WebSocket WebSocket WebSocket
複数データソース統合 ネイティブサポート Lambda で実装 複数 DB サポート 要実装 PostgreSQL 特化
オフライン対応 Amplify DataStore 要実装 要実装 要実装 要実装
認証・認可 多様(IAM / Cognito / OIDC) 要実装 ロールベース 要実装 PostgreSQL Role
パフォーマンス キャッシング・自動最適化 キャッシング設定要 キャッシング設定要 キャッシング設定要 キャッシング設定要
学習曲線 中(AWS 知識必要) 低~中 高(SQL 知識)
料金モデル 従量課金(リクエスト数) セルフホスト(サーバー代) セルフホスト セルフホスト セルフホスト
スケーラビリティ 自動スケール 手動スケール 手動スケール 手動スケール 手動スケール
推奨用途 モバイル / AWS 環境 Node.js フルスタック PostgreSQL ベース Node.js API PostgreSQL ベース

ベストプラクティス

チェックリスト(優先順)

✅ 推奨事項

  • [ ] GraphQL スキーマを厳密に型定義 → null / non-null ディレクティブで API 契約明確化
  • [ ] JavaScript リゾルバー推奨(VTL は非推奨) → 学習コスト低く、デバッグ容易、型安全
  • [ ] パイプラインリゾルバーで複数 Function 組織化 → 各 Function は単一責任
  • [ ] 認証方式を複数定義 → Query / Mutation / Subscription ごとに最適な方式選択
  • [ ] DynamoDB は GSI 定義で Query 最適化 → N+1 問題防止
  • [ ] キャッシングを活用 → TTL・Invalidation by Key でメモリ効率化
  • [ ] Subscription で WebSocket 接続管理 → 接続数・メッセージレート監視
  • [ ] CloudWatch Logs で詳細ログ出力 → リゾルバーエラー・パフォーマンス分析
  • [ ] X-Ray トレーシング有効化 → エンドツーエンド Request Flow 追跡
  • [ ] Cognito / OIDC で本番認証 → API Key は開発・テスト環境のみ

❌ アンチパターン

アンチパターン 問題 代替
VTL リゾルバーで複雑ロジック実装 テンプレート構文理解が困難、デバッグしにくい JavaScript リゾルバーで型安全に実装
API Key を本番環境で使用 365 日で有効期限切れ、セキュリティリスク Cognito / IAM / OIDC / Lambda Authorizer
リゾルバー内で複数データソース順次実行 N+1 問題・遅延増加 パイプラインリゾルバー・DataLoader パターン
Subscription なしで高頻度ポーリング クライアント負荷・API コスト増加 GraphQL Subscription で WebSocket
認可を Lambda で全て実装 複雑化・保守性低下 ディレクティブ(@aws_auth)で宣言的に定義
キャッシング未設定 メモリ効率低下・遅延増加 Response キャッシュ・Field キャッシュ活用

トラブルシューティング

症状 原因 解決策
「Query が null を返す」 リゾルバー Response 関数で null 返却 Response 関数で ctx.result を確認、エラーハンドリング追加
「N+1 クエリ問題(DynamoDB 過剰呼び出し)」 Query ネスト時に各レコード個別に Query 実行 DataLoader パターン・バッチ処理・パイプラインリゾルバー使用
「Subscription が接続されない」 WebSocket エンドポイント接続エラー、認証失敗 CloudWatch Logs で接続エラー確認、認証設定見直し
「リゾルバー timeout(デフォルト 30秒)」 データソース応答遅延、複雑 Transform 処理 タイムアウト延長(最大 300秒)、データソース最適化
「DynamoDB から空結果」 Key スキーマ不一致、GSI 定義誤り テーブルスキーマ・GSI 定義確認、Query 式修正
「Lambda リゾルバーが JSON パース失敗」 Lambda が JSON 以外形式を返す Lambda 関数で JSON.stringify() で返却
「認可エラー(Unauthorized)」 トークン有効期限切れ、権限不足 トークン更新、@aws_auth ディレクティブ確認
「Merged API で スキーマ衝突」 複数 API の type 名重複 @federation ディレクティブで namespace 分離

2025-2026 最新動向

2025年3月:AppSync Events GA

新機能

WebSocket ベースの Pub/Sub API で Kafka・EventBridge・Kinesis からのイベント購読が可能に。

type Subscription {
  onOrderEvent: OrderEvent
}

type OrderEvent {
  orderId: ID!
  status: String!
  timestamp: AWSDateTime!
}

利用例

# Kafka トピックを AppSync Events で購読
aws appsync create-event-source \
  --api-id $API_ID \
  --name kafka-orders \
  --type KAFKA

2025年4月:Merged GraphQL API 拡張

新機能

  • サブグラフ間での reference 解決(Federation pattern)
  • マイクロサービス Gateway として複数チームの API を統合
  • Schema composition の自動化

2026年1月:JavaScript リゾルバー v2

改善点

  • async/await native support
  • improved error handling
  • better debugging experience

キャッシング戦略拡張(2025年)

TTL・Invalidation by Key

// Response caching with TTL
export function response(ctx) {
  return {
    ...ctx.result,
    ttl: 300, // 5 分キャッシュ
  };
}

// Field-level caching
@aws_cache(ttl: 3600)
getUser(id: ID!): User

学習リソース・参考文献

公式ドキュメント

  1. AWS AppSync Developer Guide

  2. AWS AppSync GraphQL API Reference

  3. AppSync Events Developer Guide(2025年新規)

  4. AWS Amplify Documentation

  5. AWS AppSync Pricing

AWS ブログ・サンプルコード

  1. AWS Mobile Blog - AppSync Articles

    • リアルタイム同期・オフライン対応・ベストプラクティス
  2. AWS Samples - AppSync Examples

    • GitHub のサンプルコード集

OSS / サードパーティリソース

  1. Apollo GraphQL Documentation

  2. Hasura GraphQL Engine

  3. GraphQL Yoga(Node.js GraphQL サーバー)


実装例

例 1:React + AppSync + Amplify DataStore(オフライン同期)

React Component

import React, { useEffect, useState } from 'react';
import { DataStore } from 'aws-amplify';
import { Post } from './models';

export function PostsList() {
  const [posts, setPosts] = useState([]);
  
  useEffect(() => {
    // ローカルキャッシュから読み込み
    const subscription = DataStore.observeQuery(Post)
      .subscribe(snapshot => {
        setPosts(snapshot.items);
      });
    
    return () => subscription.unsubscribe();
  }, []);
  
  const handleCreatePost = async (title, content) => {
    await DataStore.save(new Post({
      title,
      content,
      createdAt: new Date().toISOString(),
    }));
    // 自動的に AppSync にも同期
  };
  
  return (
    <div>
      <input type="text" placeholder="Title" />
      <textarea placeholder="Content" />
      <button onClick={() => handleCreatePost('Title', 'Content')}>
        Create Post
      </button>
      <ul>
        {posts.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

例 2:チャットアプリケーション(WebSocket Subscription)

GraphQL Schema

type Message {
  id: ID!
  conversationId: ID!
  senderId: ID!
  text: String!
  createdAt: AWSDateTime!
}

type Conversation {
  id: ID!
  participants: [String!]!
  messages: [Message!]!
}

type Query {
  getConversation(id: ID!): Conversation
}

type Mutation {
  sendMessage(input: SendMessageInput!): Message
}

input SendMessageInput {
  conversationId: ID!
  text: String!
}

type Subscription {
  onMessageCreated(conversationId: ID!): Message
    @aws_subscribe(mutations: ["sendMessage"])
}

React コンポーネント

import { API, graphqlOperation } from 'aws-amplify';

const sendMessageMutation = `
  mutation SendMessage($conversationId: ID!, $text: String!) {
    sendMessage(input: { conversationId: $conversationId, text: $text }) {
      id
      text
      createdAt
    }
  }
`;

const onMessageCreatedSubscription = `
  subscription OnMessageCreated($conversationId: ID!) {
    onMessageCreated(conversationId: $conversationId) {
      id
      senderId
      text
      createdAt
    }
  }
`;

export function ChatWindow({ conversationId, userId }) {
  const [messages, setMessages] = useState([]);
  
  useEffect(() => {
    // WebSocket subscription
    const subscription = API.graphql(
      graphqlOperation(onMessageCreatedSubscription, { conversationId })
    ).subscribe({
      next: ({ value }) => {
        setMessages(prev => [...prev, value.data.onMessageCreated]);
      },
    });
    
    return () => subscription.unsubscribe();
  }, [conversationId]);
  
  const handleSendMessage = async (text) => {
    await API.graphql(
      graphqlOperation(sendMessageMutation, {
        conversationId,
        text,
      })
    );
  };
  
  return (
    <div>
      {messages.map(msg => (
        <div key={msg.id}>{msg.text}</div>
      ))}
      <input onKeyPress={(e) => {
        if (e.key === 'Enter') {
          handleSendMessage(e.target.value);
          e.target.value = '';
        }
      }} />
    </div>
  );
}

チェックリスト

スキーマ設計

  • [ ] Query / Mutation / Subscription を明確に定義
  • [ ] null / non-null 修飾子を適切に設定
  • [ ] Input 型で Mutation パラメータを定義
  • [ ] ディレクティブ(@auth / @cache)で運用ルール記載

リゾルバー実装

  • [ ] JavaScript リゾルバーで型安全に実装
  • [ ] Error handling で ctx.error をチェック
  • [ ] Response で util.dynamodb / util.rds を活用
  • [ ] CloudWatch Logs で debug ログ出力

認証・認可

  • [ ] 複数認証方式を定義(API Key / Cognito / IAM)
  • [ ] 本番環境で API Key は使用しない
  • [ ] @aws_auth ディレクティブでフィールドレベル認可
  • [ ] Lambda Authorizer でカスタムロジック実装

パフォーマンス

  • [ ] キャッシング設定(TTL / Invalidation)
  • [ ] DynamoDB で GSI 定義・Query 最適化
  • [ ] Subscription 接続数・メッセージレート監視
  • [ ] 複雑リゾルバーをパイプライン化

運用

  • [ ] CloudWatch Logs で detailed logs 有効化
  • [ ] X-Ray トレーシング有効化
  • [ ] CloudWatch Alarms で監視(エラーレート・遅延)
  • [ ] 定期的なスキーマレビュー

まとめ

AWS AppSync は 「フルマネージド GraphQL API サービスで、複数データソース統合・WebSocket リアルタイムサブスクリプション・Amplify DataStore オフライン同期を実現するサービス」 です。

AppSync が向いている場合

  • React / Next.js / Vue.js の Web アプリバックエンド
  • iOS / Android モバイルアプリ(オフライン対応)
  • チャット・リアルタイム通知機能
  • マイクロサービス Federation(Merged GraphQL API)
  • AWS 環境での複数データソース統合

2025-2026 動向

  • AppSync Events: Kafka・EventBridge・Kinesis との Pub/Sub 統合
  • Merged GraphQL API: マイクロサービス Federation パターン確立
  • JavaScript v2: async/await native support・improved debugging

GraphQL スキーマを厳密に型定義し、JavaScript リゾルバーで実装、複数認証方式とキャッシング戦略を活用することで、スケーラブルで高性能なリアルタイム API を構築してください。


最終更新:2026-04-27 バージョン:v2.0