目次
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 のきめ細かいキャッシュ戦略
目次
- 概要
- AppSync が解決する課題
- 主な特徴
- アーキテクチャ
- コアコンポーネント
- GraphQL スキーマ設計
- リゾルバーと Mapping Template
- データソース統合
- リアルタイム機能
- 認証・認可
- 主要ユースケース
- 設定・操作の具体例
- 類似サービス比較表
- ベストプラクティス
- トラブルシューティング
- 2025-2026 最新動向
- 学習リソース・参考文献
- 実装例
- チェックリスト
- まとめ
概要
初心者向けメモ: 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
学習リソース・参考文献
公式ドキュメント
-
AWS AppSync Developer Guide
-
AWS AppSync GraphQL API Reference
-
AppSync Events Developer Guide(2025年新規)
-
AWS Amplify Documentation
-
AWS AppSync Pricing
AWS ブログ・サンプルコード
-
AWS Mobile Blog - AppSync Articles
- リアルタイム同期・オフライン対応・ベストプラクティス
-
AWS Samples - AppSync Examples
- GitHub のサンプルコード集
OSS / サードパーティリソース
-
Apollo GraphQL Documentation
-
Hasura GraphQL Engine
-
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