Migrate from DataStore
Understanding DataStore
AWS Amplify DataStore provided a local-first data layer that automatically synchronized data between your app and the cloud. When you used DataStore, you got several powerful capabilities without writing any synchronization logic yourself: a local database that persisted data across sessions, automatic bidirectional sync with your AppSync backend, built-in conflict resolution using version tracking, full offline support with mutation queuing and replay, and real-time updates through observe() and observeQuery().
DataStore abstracted away the complexity of GraphQL operations, network state management, and data consistency. You worked with simple save, query, and delete methods on local models, and DataStore handled everything else behind the scenes.
This guide shows you how to use Apollo Client for queries, mutations, and caching, combined with the Amplify library's built-in subscription support for real-time updates. Depending on how much of DataStore's feature set your app actually uses, you may find the migration simpler than expected.
Once frontend clients have been migrated off DataStore, disable conflict resolution on the backend to remove the sync infrastructure, simplify mutations (no more _version tracking), and switch from soft deletes to hard deletes.
What this guide covers
This guide walks you through the migration path from DataStore to Apollo Client. You will set up Apollo Client with your AppSync endpoint, migrate all CRUD operations and relationships, and optionally add persistent caching and optimistic updates.
Quick comparison: before and after
Here is a quick look at how common DataStore operations translate to Apollo Client:
| DataStore Operation | Apollo Client Equivalent |
|---|---|
DataStore.save(new Post({...})) | apolloClient.mutate({ mutation: CREATE_POST, variables: { input: {...} } }) |
DataStore.query(Post) | apolloClient.query({ query: LIST_POSTS }) |
DataStore.query(Post, id) | apolloClient.query({ query: GET_POST, variables: { id } }) |
DataStore.delete(post) | apolloClient.mutate({ mutation: DELETE_POST, variables: { input: { id, _version } } }) |
DataStore.observe(Post) | amplifyClient.graphql({ query: onCreatePost }).subscribe(...) |
DataStore.observeQuery(Post) | useQuery(LIST_POSTS) with subscription-triggered refetch() |
Who should use this guide
This guide is for developers who have an existing Amplify Gen 1 application that uses DataStore and want to replace DataStore with Apollo Client. It covers the frontend migration (replacing DataStore with Apollo Client) and the backend step of disabling conflict resolution once all clients have been migrated. After completing these steps, you can proceed to migrate your backend to Amplify Gen 2.
It assumes you are familiar with:
- React and React hooks
- Basic GraphQL concepts (queries, mutations, subscriptions)
- Amplify configuration and the
amplifyconfiguration.json(oraws-exports.js) file - Your app's data model and how it uses DataStore today
You do not need prior experience with Apollo Client. The guide covers Apollo Client setup from scratch.
How to use this guide
Follow the pages in order. Each step builds on the previous one.
-
Set up Apollo Client. Install dependencies, define your GraphQL operations, configure Apollo Client with your AppSync endpoint and Cognito authentication, and set up real-time subscriptions with Amplify.
-
Migrate CRUD operations. Replace
DataStore.save(),DataStore.query(), andDataStore.delete()with Apollo Client queries and mutations. Migrate predicates, pagination, and sorting. -
Migrate relationships. Migrate
hasMany,belongsTo,hasOne, andmanyToManyrelationships from DataStore's lazy-loading model to Apollo Client's GraphQL selection sets. -
Optional: Add local caching. Enhance your setup with persistent caching (data survives page refreshes), optimistic updates (instant UI feedback), and intelligent fetch policies. This step is optional -- skip it if your app does not need data persistence across page refreshes or instant optimistic UI.
-
Advanced patterns. Handle composite keys, set up GraphQL codegen for type safety, migrate React components, and review DataStore features with no direct Apollo equivalent.
-
Disable conflict resolution. Once ALL frontend clients are migrated off DataStore, remove the sync infrastructure, simplify mutations, and switch from soft deletes to hard deletes.
-
Migrate your backend to Gen 2 (optional). After completing the frontend migration and disabling conflict resolution, you can migrate your backend from Gen 1 to Amplify Gen 2.
Migration checklists
Use these checklists to plan and track your migration from DataStore to Apollo Client. Check items off as you complete them.
Pre-migration checklist
Complete these steps before writing any migration code:
- Verify your backend is deployed and
amplifyconfiguration.json(oraws-exports.js) is generated (amplify push) - Confirm Amplify is configured with
Amplify.configure(config)running at app startup before any API calls - Inventory all DataStore usage in your codebase:
DataStore.save(),DataStore.query(),DataStore.delete(),DataStore.observe(),DataStore.observeQuery() - Identify all models and relationships (
hasMany,belongsTo,hasOne,manyToMany), noting which models have custom or composite primary keys - Write GraphQL operations for each model, including
_version,_deleted, and_lastChangedAtin all fragments - Install Apollo Client:
npm install @apollo/client@^3.14.0 graphql - Set up Apollo Client and verify the connection works by running a simple list query against your AppSync endpoint
- Set up the Amplify subscription client using
generateClient()
During migration checklist
Follow these steps while migrating each feature. Work through one model at a time to keep changes manageable and testable.
For each DataStore model:
- Define a GraphQL fragment including all business fields plus
_version,_deleted, and_lastChangedAt - Define all GraphQL operations (list, get, create, update, delete) using the fragment
- Migrate list queries, filtering out soft-deleted records (
_deleted: true) in the results - Migrate single-item queries
- Migrate creates (no
_versionneeded for creates) - Migrate updates (include
_versionfrom the latest query result in the mutation input) - Migrate deletes (include both
idand_versionin the mutation input) - Migrate
observeusing Amplify subscription plus the refetch pattern - Migrate
observeQueryusinguseQuerycombined with subscription-triggeredrefetch() - Update error handling for Apollo's error link and component-level error states
- Test each migrated operation before moving to the next model
For predicates and filters:
- Convert DataStore predicates to GraphQL filter objects
- Migrate sorting to client-side
.sort()or server-side@indexqueries - Migrate pagination from page-based to cursor-based (
nextTokenandlimit)
Post-migration checklist
Verification:
- Verify all CRUD operations work for every migrated model
- Verify real-time updates fire for all three event types (create, update, delete) on every model
- Verify authentication flow including sign-in, authenticated operations, and sign-out
- Verify sign-out cleanup clears the Apollo cache
- Verify
_versionhandling succeeds withoutConditionalCheckFailedExceptionerrors - Verify soft-delete filtering so deleted records no longer appear in list views
Cleanup:
- Remove all DataStore imports from your codebase
- Remove generated DataStore model files
- Remove DataStore configuration calls (
DataStore.configure(),DataStore.start(),DataStore.stop()) - Remove
@aws-amplify/datastorefrom your dependencies - Run the app end-to-end with a full user workflow
- Monitor for errors post-deployment
If you added local caching
If you followed the optional Add local caching step, add these items to your migration plan:
- Set up
apollo3-cache-persistfor persistent cache storage - Configure
fetchPolicyfor each query (for example,cache-and-networkfor lists,cache-firstfor detail views) - Implement optimistic updates for mutations using Apollo's
optimisticResponseoption - Update the sign-out flow to purge the persistent cache in addition to clearing the in-memory cache