Name:
interface
Value:
Amplify has re-imagined the way frontend developers build fullstack applications. Develop and deploy without the hassle.

Page updated May 2, 2026

Maintenance ModeYou are viewing Amplify Gen 1 documentation. Amplify Gen 1 has entered maintenance mode and will reach end of life on May 1, 2027. New project should use Amplify Gen 2. For existing Gen 1 projects, a migration guide and tooling are available to help you upgrade. Switch to the latest Gen 2 docs →

CRUD操作の移行

このページでは、すべてのDataStore CRUD操作とpredicate/filterパターンをApollo Clientに移行する方法について説明します。DataStoreはcreateとupdateを単一のsave()メソッドに統合し、_versionを内部的に処理します。Apollo Clientでは、各操作に対して個別のmutationを使用し、_versionを明示的に管理します。

このページで使用するGraphQL操作CREATE_POSTUPDATE_POSTDELETE_POSTGET_POSTLIST_POSTS、およびPOST_DETAILS_FRAGMENTフラグメント)は、Apollo Clientのセットアップページで定義されています。必要に応じてインポートします:

import { apolloClient } from './apolloClient';
import {
CREATE_POST, UPDATE_POST, DELETE_POST,
GET_POST, LIST_POSTS,
} from './graphql/operations';

Create(新しいレコードを保存)

DataStoreはnew Model()DataStore.save()を使用してレコードを作成します。Apollo ClientはCREATE_POST mutationを使用します。

DataStore(移行前):

const newPost = await DataStore.save(
new Post({
title: 'My First Post',
content: 'Hello world',
status: 'PUBLISHED',
rating: 5,
})
);

Apollo Client(移行後)-- 命令型:

const { data } = await apolloClient.mutate({
mutation: CREATE_POST,
variables: {
input: {
title: 'My First Post',
content: 'Hello world',
status: 'PUBLISHED',
rating: 5,
},
},
});
const newPost = data.createPost;
// newPost._version is 1 (set by AppSync automatically)

主な違い:

  • createでは_versionは不要。 AppSyncは新しいレコードで_versionを自動的に1に設定します。
  • **refetchQueries**はcreate後にリストビューを更新します。DataStoreはローカルストアを通じてこれを自動的に処理しました。Apollo Clientでは明示的なキャッシュ管理が必要です。

Update(既存レコードを変更)

DataStoreはModel.copyOf()をimmerベースのドラフトで不変更新に使用します。Apollo ClientはUPDATE_POST mutationをプレーンオブジェクトで使用します。変更されたフィールドのみをinputに含める必要があります。

updateでは_versionが必須です。 現在の_versionを取得するため、最初にレコードをクエリする必要があります。ConditionalCheckFailedExceptionが表示される場合は、_versionが不足しているか、古い値を渡しています。

DataStore(移行前):

const original = await DataStore.query(Post, '123');
const updated = await DataStore.save(
Post.copyOf(original, (draft) => {
draft.title = 'Updated Title';
draft.rating = 4;
})
);

Apollo Client(移行後)-- 命令型:

// Step 1: Query the current record to get _version
const { data: queryData } = await apolloClient.query({
query: GET_POST,
variables: { id: '123' },
});
const post = queryData.getPost;
// Step 2: Mutate with _version from query result
const { data } = await apolloClient.mutate({
mutation: UPDATE_POST,
variables: {
input: {
id: '123',
title: 'Updated Title',
rating: 4,
_version: post._version, // REQUIRED
},
},
});

主な違い:

  • copyOf()またはimmerパターンはありません。 Apolloはプレーンオブジェクトを使用します -- 変更したいフィールドのみを渡します。
  • 変更されたフィールド、id、_versionのみが必要です。 レコード全体を送信する必要はありません。
  • 2段階のプロセス: 最初にクエリ(_versionを取得)してからmutateします。DataStoreはこれを内部的に処理しました。

Delete(単一レコード)

deleteでは_versionが必須です。 IDをすでに持っていても、現在の_versionを取得するため、最初にレコードをクエリする必要があります。

DataStore(移行前):

const post = await DataStore.query(Post, '123');
await DataStore.delete(post);

Apollo Client(移行後)-- 命令型:

// Step 1: Query to get current _version
const { data: queryData } = await apolloClient.query({
query: GET_POST,
variables: { id: '123' },
});
// Step 2: Delete with _version
await apolloClient.mutate({
mutation: DELETE_POST,
variables: {
input: {
id: '123',
_version: queryData.getPost._version,
},
},
refetchQueries: [{ query: LIST_POSTS }],
});

主な違い:

  • ID削除のショートハンドはありません。 Apolloはmutation入力オブジェクトで常にid_versionの両方を必要とします。
  • deleteはソフトデリートです(競合解決が有効になっている場合)。レコードの_deletedフィールドはDynamoDBでtrueに設定されますが、レコードは物理的には削除されません。

IDでクエリ

DataStore(移行前):

const post = await DataStore.query(Post, '123');
if (post) {
console.log(post.title);
}

Apollo Client(移行後):

const { data } = await apolloClient.query({
query: GET_POST,
variables: { id: '123' },
});
const post = data.getPost;
// Returns null instead of undefined when not found
if (post) {
console.log(post.title);
}

すべてのレコードをリスト

ソフトデリートされたレコードをフィルタリングする必要があります。 DataStoreはこれを自動的に処理しました。Apollo Clientは_deleted: trueを含むすべてのレコードを返します。これを忘れることが最も一般的な移行バグです。

DataStore(移行前):

const posts = await DataStore.query(Post);

Apollo Client(移行後):

const { data } = await apolloClient.query({ query: LIST_POSTS });
const posts = data.listPosts.items.filter((post) => !post._deleted);

バッチ削除(predicateベース)

DataStoreはpredicateで複数のレコードを削除することをサポートしていました。Apollo Clientに相当するものはありません -- 最初に一致するレコードをクエリしてから、各レコードを個別に削除する必要があります。

DataStore(移行前):

await DataStore.delete(Post, (p) => p.status.eq('DRAFT'));

Apollo Client(移行後):

// Step 1: Query posts matching the filter
const { data } = await apolloClient.query({
query: LIST_POSTS,
variables: { filter: { status: { eq: 'DRAFT' } } },
});
const drafts = data.listPosts.items.filter((post) => !post._deleted);
// Step 2: Delete each record individually
const results = await Promise.allSettled(
drafts.map((post) =>
apolloClient.mutate({
mutation: DELETE_POST,
variables: {
input: { id: post.id, _version: post._version },
},
})
)
);
// Step 3: Check for partial failures
const failures = results.filter((r) => r.status === 'rejected');
if (failures.length > 0) {
console.error(`${failures.length} of ${drafts.length} deletes failed`);
}
// Refresh the list
await apolloClient.refetchQueries({ include: [LIST_POSTS] });

Promise.allSettledを使用します(Promise.allではなく)。これにより、1つの失敗が残りの削除を中止しません。大規模なデータセット(100+レコード)の場合、10~25レコードのバッチで処理し、AppSync throttlingを回避するためにバッチ間で短い遅延を入れます。

CRUD クイックリファレンス

DataStoreメソッドApollo Client相当主な違い
DataStore.save(new Model({...}))apolloClient.mutate({ mutation: CREATE, variables: { input: {...} } })createでは_versionは不要
Model.copyOf(original, draft => {...}) + DataStore.save()apolloClient.mutate({ mutation: UPDATE, variables: { input: { id, _version, ...changes } } })_versionを渡す必要があります。immerドラフトの代わりにプレーンオブジェクト
DataStore.delete(instance)apolloClient.mutate({ mutation: DELETE, variables: { input: { id, _version } } })_versionを取得するため最初にクエリが必要
DataStore.query(Model, id)apolloClient.query({ query: GET, variables: { id } })見つからない場合、undefinedの代わりにnullを返す
DataStore.query(Model)apolloClient.query({ query: LIST })結果から_deletedレコードをフィルタリングする必要があります
DataStore.delete(Model, predicate)フィルタでクエリ + 個別に削除atomicity がありません。Promise.allSettledを使用

フィルタ演算子マッピング

DataStoreはコールバックベースのpredicateを使用します。Apollo ClientとAppSyncはクエリ変数として渡されたJSONフィルタオブジェクトを使用します。

演算子DataStore構文GraphQL構文メモ
eqp.field.eq(value){ field: { eq: value } }完全一致
nep.field.ne(value){ field: { ne: value } }不一致
gtp.field.gt(value){ field: { gt: value } }より大きい
gep.field.ge(value){ field: { ge: value } }以上
ltp.field.lt(value){ field: { lt: value } }より小さい
lep.field.le(value){ field: { le: value } }以下
containsp.field.contains(value){ field: { contains: value } }部分文字列一致
notContainsp.field.notContains(value){ field: { notContains: value } }部分文字列が不存在
beginsWithp.field.beginsWith(value){ field: { beginsWith: value } }文字列プレフィックス一致
betweenp.field.between(lo, hi){ field: { between: [lo, hi] } }範囲(両端含む)
inp.field.in([v1, v2])利用不可or + eqを使用
notInp.field.notIn([v1, v2])利用不可and + neを使用

フィルタ例

eq -- 完全一致:

// DataStore
const published = await DataStore.query(Post, (p) => p.status.eq('PUBLISHED'));
// Apollo Client
const { data } = await apolloClient.query({
query: LIST_POSTS,
variables: { filter: { status: { eq: 'PUBLISHED' } } },
});
const published = data.listPosts.items.filter((p) => !p._deleted);

contains -- 部分文字列一致:

// DataStore
const reactPosts = await DataStore.query(Post, (p) => p.title.contains('React'));
// Apollo Client
const { data } = await apolloClient.query({
query: LIST_POSTS,
variables: { filter: { title: { contains: 'React' } } },
});
const reactPosts = data.listPosts.items.filter((p) => !p._deleted);

between -- 範囲(両端含む):

// DataStore
const midRated = await DataStore.query(Post, (p) => p.rating.between(2, 4));
// Apollo Client
const { data } = await apolloClient.query({
query: LIST_POSTS,
variables: { filter: { rating: { between: [2, 4] } } },
});
const midRated = data.listPosts.items.filter((p) => !p._deleted);

その他の演算子negtgeltlenotContainsbeginsWith)は上記のeqと同じパターンに従います -- 演算子名と値を置き換えます。完全な構文リファレンスはフィルタ演算子マッピングテーブルを参照してください。

andで条件を組み合わせる:

// DataStore
const posts = await DataStore.query(Post, (p) =>
p.and((p) => [p.rating.gt(4), p.status.eq('PUBLISHED')])
);
// Apollo Client
const { data } = await apolloClient.query({
query: LIST_POSTS,
variables: {
filter: {
and: [{ rating: { gt: 4 } }, { status: { eq: 'PUBLISHED' } }],
},
},
});

AppSyncのトップレベルフィルタフィールドは暗黙的にAND-edです。これは{ status: { eq: 'PUBLISHED' }, rating: { gt: 4 } }が明示的なandを使用するのと同じであることを意味します。or内にネストする場合は明示的なandを使用します。

orで条件を組み合わせる:

const { data } = await apolloClient.query({
query: LIST_POSTS,
variables: {
filter: {
or: [
{ title: { contains: 'React' } },
{ title: { contains: 'Apollo' } },
],
},
},
});

notで否定する:

const { data } = await apolloClient.query({
query: LIST_POSTS,
variables: {
filter: { not: { status: { eq: 'DRAFT' } } },
},
});

innotInの回避策

innotIn演算子はAppSyncのModelFilterInput型に存在しません{ field: { in: [...] } }を使用しようとすると、AppSyncは検証エラーでクエリを拒否します。

inor + eqで置き換える:

// DataStore: p.status.in(['PUBLISHED', 'DRAFT'])
// Apollo: combine multiple eq conditions with or
const { data } = await apolloClient.query({
query: LIST_POSTS,
variables: {
filter: {
or: [{ status: { eq: 'PUBLISHED' } }, { status: { eq: 'DRAFT' } }],
},
},
});
inとnotInのヘルパー関数
function buildInFilter(field: string, values: string[]) {
return {
or: values.map((value) => ({ [field]: { eq: value } })),
};
}
function buildNotInFilter(field: string, values: string[]) {
return {
and: values.map((value) => ({ [field]: { ne: value } })),
};
}
// Usage:
const { data } = await apolloClient.query({
query: LIST_POSTS,
variables: { filter: buildInFilter('status', ['PUBLISHED', 'DRAFT']) },
});

ページネーション移行

DataStoreはページベースのページネーション(ゼロインデックスpage番号 + limit)を使用します。AppSyncはカーソルベースのページネーション(nextToken + limit)を使用します。これは名前変更ではなく -- 根本的なセマンティック変更です。

側面DataStore(ページベース)Apollo/AppSync(カーソルベース)
ナビゲーションランダムアクセス -- 任意のページにジャンプ順序のみ -- ページを順番に走査する必要があります
パラメータ{ page: 0, limit: 10 }{ limit: 10, nextToken: '...' }
最初のページpage: 0nextTokenを省略(またはnullを渡す)
次のページpage: page + 1前の応答からnextTokenを使用
終了検出items.length < limitnextToken === null

Apollo Clientカーソルベースページネーション:

// Page 1 (first 10 items) -- no nextToken needed
const { data: page1Data } = await apolloClient.query({
query: LIST_POSTS,
variables: { limit: 10 },
});
const page1Items = page1Data.listPosts.items.filter((p) => !p._deleted);
const nextToken = page1Data.listPosts.nextToken;
// Page 2 -- use nextToken from previous response
if (nextToken) {
const { data: page2Data } = await apolloClient.query({
query: LIST_POSTS,
variables: { limit: 10, nextToken },
});
}

フィルタでnextTokenを使用する場合、AppSyncはlimitより少ないアイテムを返す可能性があります。結果の終了を確認するために常にnextToken === nullをチェックしてください -- 結果終了のインジケータとしてitems.length < limitを使用しないでください

ソート移行

DataStoreはSortDirection.ASCENDINGSortDirection.DESCENDINGをサポートしています。AppSyncの基本的なlistModelsクエリにはデフォルトでは**sortDirection引数がありません**。

クライアント側のソート(推奨)

ほとんどの場合、結果をフェッチしてJavaScriptでソートします:

// DataStore
const posts = await DataStore.query(Post, Predicates.ALL, {
sort: (s) => s.createdAt(SortDirection.DESCENDING),
});
// Apollo Client
const { data } = await apolloClient.query({ query: LIST_POSTS });
const posts = [...data.listPosts.items]
.filter((p) => !p._deleted)
.sort((a, b) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
);
サーバー側のソート(@indexディレクティブが必須)

モデルに@indexディレクティブで定義されたグローバルセカンダリインデックス(GSI)がある場合、AppSyncはsortDirectionサポートを備えたクエリを生成します:

type Post @model {
id: ID!
title: String!
status: String! @index(name: "byStatus", sortKeyFields: ["createdAt"])
createdAt: AWSDateTime!
}

これはsortDirectionを受け入れるpostsByStatusクエリを生成します:

const LIST_POSTS_BY_STATUS = gql`
query PostsByStatus(
$status: String!
$sortDirection: ModelSortDirection
$limit: Int
$nextToken: String
) {
postsByStatus(
status: $status
sortDirection: $sortDirection
limit: $limit
nextToken: $nextToken
) {
items { ...PostDetails }
nextToken
}
}
`;
const { data } = await apolloClient.query({
query: LIST_POSTS_BY_STATUS,
variables: { status: 'PUBLISHED', sortDirection: 'DESC', limit: 10 },
});

サーバー側のソートはバックエンドスキーマの変更が必要で、インデックスのパーティションキーでクエリする場合のみ機能します。汎用のソートにはクライアント側のソートを使用します。

一般的なミス

一般的なCRUD移行ミス

1. updateまたはdelete mutationで_versionを忘れる

最も頻繁な移行エラー。DataStoreは_versionを内部的に処理しました。Apolloでは自分で含める必要があります。

2. updateにCREATE mutationを使用する

DataStoreのsave()はcreateとupdateの両方を処理しました。Apolloでは、正しいmutationを呼び出す必要があります。

3. リスト結果から_deletedレコードをフィルタリングしない

DataStoreはソフトデリートされたレコードを自動的に非表示にしました。Apolloはソフトデリートされたレコードを含むすべてのレコードを返します。常にリストクエリ結果に対して.filter(item => !item._deleted)を使用します。

4. mutation後に refetchQueriesを使用しない

DataStoreのローカルストアはmutation後にクエリを自動的に更新しました。Apolloのキャッシュはリストクエリを自動的に更新しないことがあります。リストビューに影響するmutationにrefetchQueries: [{ query: LIST_POSTS }]を追加します。

5. 古い_versionの値を使用する

レコードの_versionをキャッシュして別のユーザーまたはプロセスがレコードを更新した場合、mutationは失敗します。新鮮さが重要な場合は、mutate前にfetchPolicy: 'network-only'で再クエリします。