Home›DevOps›AWS AppSync: Real-Time GraphQL APIs and Offline Data Sync
Advanced
4 min · July 12, 2026
AWS AppSync: Real-Time GraphQL APIs and Offline Data Sync
A comprehensive guide to AWS AppSync: Real-Time GraphQL APIs and Offline Data Sync on AWS, covering core concepts, configuration, best practices, and real-world use cases..
N
NarenFounder & Principal Engineer
20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.
✓Basic understanding of AWS services and cloud computing concepts.
✦ Definition~90s read
What is AWS AppSync?
AWS AppSync is a managed GraphQL service that enables real-time data synchronization and offline data access for web and mobile applications. It matters because it eliminates the need to manage WebSocket infrastructure and conflict resolution logic, providing built-in support for subscriptions, data caching, and offline mutations.
★
AWS AppSync: Real-Time GraphQL APIs and Offline Data Sync is like having a smart assistant that handles the heavy lifting so you don't have to manage servers yourself.
Use it when you need to build collaborative apps, live dashboards, or any application requiring instant updates across devices while maintaining a consistent data model.
Plain-English First
AWS AppSync: Real-Time GraphQL APIs and Offline Data Sync is like having a smart assistant that handles the heavy lifting so you don't have to manage servers yourself.
⚙ Browser compatibility
Latest versions — ✓ supported
Chrome
Firefox
Safari
Edge
✓
✓
✓
✓
You've built a real-time dashboard. Users love it—until the WebSocket server crashes during a demo, taking down all active connections. That's the moment you realize managing real-time infrastructure at scale is a nightmare. AWS AppSync solves this by providing a managed GraphQL endpoint with built-in WebSocket support, automatic conflict resolution, and offline data sync. It's not just a GraphQL server; it's a state management platform that handles the hard parts of distributed data synchronization. If you're tired of stitching together Lambda, API Gateway, DynamoDB Streams, and custom WebSocket logic, AppSync is your escape hatch. But be warned: it comes with its own set of sharp edges, like complex resolver mapping templates and unexpected costs from data transfer. This article cuts through the hype and shows you how to use AppSync in production without shooting yourself in the foot.
Why Real-Time GraphQL Matters in Production
Real-time data is no longer a nice-to-have; it's a requirement for modern applications like collaborative editing, live dashboards, and chat systems. AWS AppSync provides managed GraphQL with built-in real-time subscriptions and offline data sync, eliminating the need to cobble together WebSocket servers and conflict resolution logic. In production, the key advantage is reduced operational overhead: AppSync handles connection management, scaling, and data synchronization out of the box. However, real-time systems introduce complexity around data consistency, latency, and cost. This article focuses on building a production-grade real-time API with AppSync, covering subscriptions, offline sync, conflict resolution, and monitoring. We assume you're already familiar with GraphQL basics and AWS services like DynamoDB and Lambda.
schema.graphqlGRAPHQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type Todo {
id: ID!
title: String!
completed: Boolean!
version: Int!
}
type Mutation {
updateTodo(id: ID!, title: String, completed: Boolean, expectedVersion: Int!): Todo
}
type Subscription {
onUpdateTodo(id: ID!): Todo
@aws_subscribe(mutations: ["updateTodo"])
}
type Query {
getTodo(id: ID!): Todo
listTodos: [Todo]
}
Output
Schema deployed successfully.
🔥Version Field for Conflict Resolution
Always include a version field in your models when using offline sync. It's the foundation for optimistic concurrency control.
📊 Production Insight
We once saw a team omit the version field and rely on timestamps for conflict resolution. Timestamps are unreliable across devices with skewed clocks, leading to silent data loss. Use a monotonically increasing version number instead.
🎯 Key Takeaway
AppSync subscriptions enable real-time updates with minimal backend code, but require careful schema design for conflict resolution.
thecodeforge.io
Aws Appsync Graphql
Configuring Real-Time Subscriptions with AppSync
AppSync subscriptions are triggered by mutations. When a mutation completes, AppSync publishes the result to all connected clients subscribed to that mutation. In production, you need to consider authorization: subscriptions can be filtered by arguments (e.g., only receive updates for a specific todo ID). This reduces bandwidth and processing on the client. To set up a subscription, define it in the schema with the @aws_subscribe directive, specifying which mutations trigger it. On the client, use the AWS AppSync SDK to subscribe. The SDK handles reconnection and backoff automatically. However, you must handle stale subscriptions when the client's auth token expires. Implement token refresh logic in the subscription handler to avoid silent disconnections.
If your subscription doesn't filter by owner, any authenticated user can receive updates for all todos. Use @aws_auth or custom resolvers to restrict access.
📊 Production Insight
In a production chat app, we forgot to filter subscriptions by conversation ID. Every client received all messages across all conversations, causing a 10x increase in WebSocket traffic and cost. Always scope subscriptions.
🎯 Key Takeaway
Subscriptions are mutation-driven; filter by arguments to minimize data transfer and enforce authorization.
Implementing Offline Data Sync with Conflict Resolution
Offline sync allows users to continue working without connectivity. AppSync's DataStore library handles local storage, queueing mutations, and syncing when online. The critical piece is conflict resolution: when two clients modify the same record offline, AppSync uses a conflict detection mechanism. The default is 'optimistic concurrency' using a version field. When a mutation is applied, the client sends the expected version. If the server's version is higher, the mutation fails, and the client must reconcile. You can implement custom conflict resolution via a Lambda function. In production, always use version-based detection. Avoid 'last writer wins' as it causes data loss. Test conflict scenarios thoroughly, especially with long offline periods.
conflict-resolution.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Custom conflict resolver LambdaconstAWS = require('aws-sdk');
const docClient = newAWS.DynamoDB.DocumentClient();
exports.handler = async (event) => {
const { payload, existing } = event;
// payload: incoming mutation arguments// existing: current item in DynamoDB// Auto-merge: take the latest version of each fieldconst merged = {
...existing,
...payload,
version: existing.version + 1
};
return {
item: merged,
conflictResolution: 'MERGE'
};
};
Simulate offline mode by disabling network in your browser dev tools. Verify that mutations are queued and synced correctly when reconnected.
📊 Production Insight
We once used 'last writer wins' for simplicity. A user lost an important edit because their offline changes were overwritten by a newer version. Always use version checks.
AppSync has limits: maximum payload size is 4 MB for requests and responses, and each WebSocket connection can have up to 100 active subscriptions. In production, you may need to handle large payloads (e.g., file uploads) or many subscriptions per client. For large payloads, use S3 presigned URLs and store references in GraphQL. For many subscriptions, consider batching updates or using a single subscription with a filter. Also, monitor connection limits: AppSync allows up to 2,000 concurrent connections per API by default (can be increased). If you exceed this, clients will fail to connect. Implement exponential backoff on the client and set up CloudWatch alarms for connection count.
batch-subscription.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
// Subscribe to all updates with a filterconst subscription = API.graphql(
graphqlOperation(onUpdateTodo, { id: null }) // null means all
).subscribe({
next: (data) => {
const todo = data.value.data.onUpdateTodo;
if (todo.userId === currentUser.id) {
// Process only relevant updates
}
}
});
Output
Received update for todo 123, but filtered out because userId doesn't match.
AppSync default connection limit is 2,000. If you expect more, request a limit increase or use multiple API endpoints.
📊 Production Insight
A client's app failed during a marketing campaign because they hit the 2,000 connection limit. We had to quickly increase the limit and add client-side backoff. Monitor proactively.
🎯 Key Takeaway
Design for payload and connection limits; use S3 for large files and filter subscriptions to reduce connections.
Monitoring and Debugging Real-Time APIs
Production monitoring for AppSync involves CloudWatch metrics (latency, errors, connection count) and logging (request/response logs). Enable detailed logging for mutations and subscriptions to debug issues. Use X-Ray tracing to visualize end-to-end requests. Common issues: subscription failures due to auth token expiry, high latency from slow resolvers, and throttling from DynamoDB. Set up CloudWatch alarms for error rate > 1% and connection count > 80% of limit. For debugging, use the AppSync console's Queries tab to test subscriptions. Also, log subscription lifecycle events on the client (connected, disconnected, error) to detect network issues.
X-Ray tracing helps identify slow resolvers. Enable it in the AppSync settings and look for high latency in DynamoDB or Lambda calls.
📊 Production Insight
We once had a silent failure where subscriptions disconnected due to token expiry, but no error was logged because the SDK auto-reconnected. We added explicit logging of connection state changes to catch this.
🎯 Key Takeaway
Monitor connection counts, error rates, and latency; use CloudWatch alarms and X-Ray for proactive debugging.
Scaling Real-Time Subscriptions with AppSync
As your user base grows, you need to scale subscriptions. AppSync automatically scales WebSocket connections across multiple nodes, but there are bottlenecks: each subscription is tied to a single mutation resolver. If you have many clients subscribing to the same mutation, the resolver is called once per mutation, and the result is broadcast to all subscribers. This is efficient. However, if you have many distinct subscriptions (e.g., per-user), the number of subscriptions can become large. Use subscription filtering to reduce the number of subscriptions. For high-throughput scenarios, consider using a fan-out pattern with SNS or Kinesis to trigger mutations in batches. Also, use DynamoDB streams to trigger mutations for bulk updates.
fan-out.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Lambda triggered by DynamoDB Stream to fan-out updatesconstAWS = require('aws-sdk');
const appsync = newAWS.AppSync({ region: 'us-east-1' });
exports.handler = async (event) => {
for (const record of event.Records) {
if (record.eventName === 'MODIFY') {
const todo = AWS.DynamoDB.Converter.unmarshall(record.dynamodb.NewImage);
// Call AppSync mutation to trigger subscriptionawait appsync.graphql({
apiId: process.env.API_ID,
query: `mutation { updateTodo(id: "${todo.id}", title: "${todo.title}", completed: ${todo.completed}, expectedVersion: ${todo.version}) { id } }`
}).promise();
}
}
};
For bulk updates, use DynamoDB Streams to trigger a Lambda that calls AppSync mutations. This avoids multiple clients calling mutations directly.
📊 Production Insight
We tried to create a subscription per user for a live feed. With 10,000 users, we hit connection limits and high cost. We switched to a single subscription with a filter, reducing connections by 90%.
🎯 Key Takeaway
Scale subscriptions by filtering and using fan-out patterns; avoid per-client subscriptions for high-volume updates.
Security Best Practices for Real-Time APIs
Security for real-time APIs involves authentication, authorization, and data validation. Use AWS Cognito for user pools and federated identities. For subscriptions, enforce authorization at the resolver level using @aws_auth or custom resolvers that check the caller's identity. Never expose sensitive data in subscription payloads. Use field-level authorization to restrict access. Also, validate input in mutations to prevent injection attacks. For offline sync, ensure that local data is encrypted at rest on the device. Use AWS AppSync's built-in support for AWS WAF to protect against DDoS attacks. Regularly rotate API keys if using API key auth (not recommended for production).
auth-schema.graphqlGRAPHQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type Todo @aws_auth(cognito_groups: ["Admin"]) {
id: ID!
title: String!
completed: Boolean!
owner: String
}
type Mutation {
updateTodo(id: ID!, title: String, completed: Boolean, expectedVersion: Int!): Todo
@aws_auth(cognito_groups: ["User"])
}
type Subscription {
onUpdateTodo(id: ID!): Todo
@aws_subscribe(mutations: ["updateTodo"])
@aws_auth(cognito_groups: ["User"])
}
Output
Schema with authorization directives deployed.
⚠ Never Use API Key in Production
API keys are for development only. In production, use Cognito User Pools or IAM for authentication.
📊 Production Insight
A team used API keys in production because it was 'easier'. When the key was exposed in a client bundle, attackers could subscribe to all updates. Always use proper auth.
🎯 Key Takeaway
Use Cognito for auth, enforce authorization at resolver level, and encrypt local data for offline sync.
Cost Optimization for Real-Time APIs
AppSync pricing is based on query and mutation requests, plus data transfer for subscriptions. Real-time subscriptions incur charges per message delivered. To optimize costs: reduce subscription payload size by selecting only needed fields, use subscription filtering to avoid broadcasting to all clients, and batch mutations when possible. For offline sync, minimize the number of sync queries by using delta sync (only fetch changed records). Monitor usage with Cost Explorer and set budgets. Consider using a caching layer (e.g., DynamoDB Accelerator) to reduce read costs. Also, use reserved capacity for DynamoDB if you have predictable traffic.
Use delta sync to fetch only changed records instead of full sync. This reduces data transfer and costs significantly.
📊 Production Insight
We had a client that did a full sync every 5 minutes for 100,000 records. This cost $500/month in AppSync and DynamoDB reads. Switching to delta sync reduced it to $50.
🎯 Key Takeaway
Optimize costs by reducing payload size, using delta sync, and monitoring usage with budgets.
Testing Real-Time and Offline Behavior
Testing real-time and offline features requires specialized approaches. Use integration tests with a real AppSync API and DynamoDB table. For offline testing, simulate network failures using tools like Charles Proxy or browser dev tools. Write unit tests for conflict resolution logic. For subscriptions, test that mutations trigger the correct subscription events. Use the AppSync console to manually test subscriptions. Automate tests with AWS SDK to call mutations and verify subscription delivery via WebSocket. Also, test edge cases: concurrent mutations, long offline periods, and reconnection storms.
Use graphql-ws library to programmatically test subscriptions in your CI pipeline. This catches regressions early.
📊 Production Insight
We skipped testing offline conflict resolution and discovered in production that merging two offline edits caused data corruption. Now we have a suite of tests that simulate concurrent offline edits.
🎯 Key Takeaway
Test real-time and offline behavior with integration tests, network simulation, and automated subscription verification.
thecodeforge.io
Aws Appsync Graphql
Production Deployment and Rollback Strategies
Deploying AppSync changes requires careful planning. Use Infrastructure as Code (e.g., AWS CDK, Terraform) to manage schema, resolvers, and data sources. Implement canary deployments by creating a new API endpoint and gradually shifting traffic. For schema changes, avoid breaking changes: add fields instead of removing them, and deprecate fields before removal. Use versioning for your API (e.g., v1, v2) if breaking changes are unavoidable. For rollbacks, keep previous schema versions in source control and use CloudFormation stack rollback. Monitor deployment with CloudWatch alarms. Also, test offline clients with the new schema to ensure backward compatibility.
cdk-stack.tsTYPESCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import * as appsync from'@aws-cdk/aws-appsync';
import * as dynamodb from'@aws-cdk/aws-dynamodb';
const api = new appsync.GraphqlApi(this, 'TodoApi', {
name: 'todo-api',
schema: appsync.Schema.fromAsset('schema.graphql'),
authorizationConfig: {
defaultAuthorization: {
authorizationType: appsync.AuthorizationType.USER_POOL,
userPoolConfig: { userPool: myUserPool }
}
}
});
const table = new dynamodb.Table(this, 'TodoTable', { ... });
api.addDynamoDbDataSource('TodoDataSource', table);
Output
Stack deployed successfully. API endpoint: https://xxxx.appsync-api.us-east-1.amazonaws.com/graphql
Never remove a field from your schema without deprecating it first. Clients may still be using it, causing errors.
📊 Production Insight
We once removed a field that was still used by a mobile app version. The app crashed on launch. Now we deprecate fields for at least two release cycles before removal.
🎯 Key Takeaway
Use IaC for deployments, avoid breaking schema changes, and have a rollback plan with versioned APIs.
⚙ Quick Reference
10 commands from this guide
File
Command / Code
Purpose
schema.graphql
type Todo {
Why Real-Time GraphQL Matters in Production
subscribe.js
const subscription = API.graphql(
Configuring Real-Time Subscriptions with AppSync
conflict-resolution.js
const AWS = require('aws-sdk');
Implementing Offline Data Sync with Conflict Resolution
batch-subscription.js
const subscription = API.graphql(
Handling Large Payloads and Connection Limits
monitoring.js
const subscription = API.graphql(
Monitoring and Debugging Real-Time APIs
fan-out.js
const AWS = require('aws-sdk');
Scaling Real-Time Subscriptions with AppSync
auth-schema.graphql
type Todo @aws_auth(cognito_groups: ["Admin"]) {
Security Best Practices for Real-Time APIs
delta-sync.js
async function syncTodos() {
Cost Optimization for Real-Time APIs
test-subscription.js
const AWS = require('aws-sdk');
Testing Real-Time and Offline Behavior
cdk-stack.ts
const api = new appsync.GraphqlApi(this, 'TodoApi', {
Production Deployment and Rollback Strategies
Key takeaways
1
Managed Real-Time Infrastructure
AppSync eliminates the need to operate WebSocket servers and handle connection state, but you must still manage client-side subscription lifecycle to prevent resource leaks.
2
Offline-First with Conflict Resolution
The SDK automatically queues mutations offline and replays them on reconnect. Define conflict resolution strategies (e.g., last writer wins or custom merge logic) to handle concurrent edits.
3
Resolvers Are the Bottleneck
VTL resolvers are powerful but easy to misuse. Use caching, batch operations, and efficient queries to avoid high latency and costs. Always test with production-like data volumes.
4
Costs Can Surprise You
AppSync pricing includes per-operation fees and data transfer. Real-time subscriptions add per-connection charges. Monitor usage and set budget alerts to avoid unexpected bills.
INTERVIEW PREP · PRACTICE MODE
Interview Questions on This Topic
Q01JUNIOR
What is AWS AppSync: Real-Time GraphQL APIs and Offline Data Sync and wh...
Q02SENIOR
How do you secure AWS AppSync: Real-Time GraphQL APIs and Offline Data S...
Q03SENIOR
What are the cost optimization strategies for AWS AppSync: Real-Time Gra...
Q01 of 03JUNIOR
What is AWS AppSync: Real-Time GraphQL APIs and Offline Data Sync and when would you use it?
ANSWER
AWS AppSync: Real-Time GraphQL APIs and Offline Data Sync is an AWS service that helps manage cloud infrastructure efficiently. Use it when you need scalable, reliable cloud solutions.
Q02 of 03SENIOR
How do you secure AWS AppSync: Real-Time GraphQL APIs and Offline Data Sync in production?
ANSWER
Follow the principle of least privilege, enable encryption at rest and in transit, and use AWS IAM roles with appropriate policies.
Q03 of 03SENIOR
What are the cost optimization strategies for AWS AppSync: Real-Time GraphQL APIs and Offline Data Sync?
ANSWER
Use reserved instances for steady-state workloads, auto-scaling for variable demand, and right-size resources based on CloudWatch metrics.
01
What is AWS AppSync: Real-Time GraphQL APIs and Offline Data Sync and when would you use it?
JUNIOR
02
How do you secure AWS AppSync: Real-Time GraphQL APIs and Offline Data Sync in production?
SENIOR
03
What are the cost optimization strategies for AWS AppSync: Real-Time GraphQL APIs and Offline Data Sync?
SENIOR
FAQ · 6 QUESTIONS
Frequently Asked Questions
01
How does AppSync handle real-time subscriptions under the hood?
AppSync uses a persistent WebSocket connection between the client and the service. When a mutation occurs, AppSync evaluates the subscription resolvers and pushes the updated data to all connected clients that match the subscription filter. The service manages connection pooling and reconnection automatically, but you must handle subscription lifecycle in your client code to avoid memory leaks.
Was this helpful?
02
What happens when a client goes offline and comes back?
AppSync's offline data sync uses a local store (e.g., AWS AppSync SDK with SQLite) to queue mutations. When the client reconnects, it replays mutations in order and resolves conflicts using a configurable strategy (e.g., Optimistic Concurrency, Last Writer Wins). You must define conflict resolution handlers in your schema to avoid data loss.
Was this helpful?
03
Can I use AppSync with existing REST APIs?
Yes, you can use HTTP resolvers to connect AppSync to any REST endpoint. However, this bypasses AppSync's native data source optimizations like caching and conflict detection. For production, prefer using AWS data sources (DynamoDB, Lambda, RDS) to get full benefits.
Was this helpful?
04
How do I secure AppSync APIs?
AppSync supports multiple auth modes: API key (dev only), AWS IAM, Amazon Cognito User Pools, and OpenID Connect. For production, use Cognito or OIDC with fine-grained access control via resolvers. Never use API keys in production—they're easily leaked and provide no user-level control.
Was this helpful?
05
What are the common pitfalls with AppSync resolvers?
Resolvers use VTL (Velocity Template Language) which is verbose and error-prone. Common mistakes include not handling pagination correctly, forgetting to set TTL on DynamoDB queries, and writing inefficient scan operations. Always test resolvers with the AppSync console's query simulator before deploying.
Was this helpful?
06
How does AppSync pricing work?
You pay per million queries, mutations, and subscription events. Data transfer costs apply for responses larger than 4KB. Real-time subscriptions incur additional charges per connection minute. For high-throughput apps, costs can balloon—monitor CloudWatch metrics and consider caching with Amazon ElastiCache to reduce resolver calls.