GraphQL with DynamoDB: AppSync, Apollo and Amplify Gen 2

Written by Chameera Dulanga
Last updated: October 23rd, 2026
DynamoDB does not expose a GraphQL endpoint itself: use AWS AppSync resolvers, an Apollo Server application, or Amplify Gen 2 to put a GraphQL API in front of it. AppSync manages the API service, Apollo gives you control of the server, and Amplify generates and manages an AppSync-backed data layer from TypeScript. In every approach, design DynamoDB keys around the operations your API must support.
Choose AppSync, Apollo or Amplify
| Approach | Best fit | Your responsibility |
|---|---|---|
| AppSync GraphQL | Managed GraphQL with direct DynamoDB data sources | Schema, authorization, resolvers and database design |
| Apollo Server | A custom server combining DynamoDB with other systems | Hosting, scaling, authentication, resolvers and database access |
| Amplify Gen 2 | An application backend defined in TypeScript with generated clients | Models, authorization rules, custom operations and deployment lifecycle |
These options overlap: Amplify Data uses AppSync, and an Apollo client can call an AppSync GraphQL API when configured with compatible authentication and transport. Apollo Server is the backend server, not a requirement for using Apollo Client.
Set up DynamoDB with AppSync GraphQL
1. Create the table and API
For the standalone AppSync and Apollo examples, create a table named Articles with a string partition key id. These are alternative API implementations over that table:
In AppSync, create a GraphQL API, configure authentication, and add the existing table as a DynamoDB data source. Grant the AppSync service role only the table operations used by its resolvers. An API key can support a deliberately public demonstration, but it is not user identity or per-user authorization. Use IAM, Cognito user pools, OIDC or a Lambda authorizer as appropriate for the application.
2. Define a schema that matches your table
This schema keeps the examples consistent. id is the DynamoDB key; publications is a list attribute. The list operation is paginated, and counters and list updates are explicit mutations:
Defining a field does not implement its database behavior. Attach a resolver to each operation you expose. A GraphQL selection set controls the API response fields; it does not automatically reduce DynamoDB read capacity.
3. Attach JavaScript resolvers
Choose the APPSYNC_JS runtime and the Articles data source. A resolver for Query.getArticle is:
For Mutation.createArticle, use a conditional put to avoid overwriting an existing ID:
These resolvers run in AppSync's JavaScript runtime, not Node.js. They produce DynamoDB requests through AppSync's built-in helpers; they do not create an AWS SDK client. Use the AppSync DynamoDB modules for supported operations and runtime behavior. Add item-level authorization before exposing them to users who should not access every article.
4. Test a mutation and query
Run these as separate operations in the AppSync query editor with an authorized identity:
Verify the item in DynamoDB and test a duplicate create. Check both authorization failures and DynamoDB conditional errors; a request returning HTTP success can still contain GraphQL errors.
Set up DynamoDB with Apollo Server
Apollo Server runs your resolvers in your own application process. Use the current @apollo/server package rather than the old apollo-server v2 package. Follow Apollo's setup documentation for supported Node.js and GraphQL versions.
Configure credentials and dependencies
Use AWS IAM Identity Center or an assumed role for local development. On AWS, attach an execution role to the compute service. Do not create an administrator access key just to run a GraphQL tutorial. This example needs GetItem, PutItem and Scan on Articles.
In a new example project, install the dependencies and enable ESM:
Save this as server.js. It implements a small subset of the earlier schema, including explicit pagination for the demonstration list operation:
Run it with an authorized local profile and a Region:
This local example intentionally has no application authentication. Before hosting it, authenticate requests, authorize each operation and tenant, validate input sizes, limit query complexity and configure error reporting. SDK credentials belong on the server, never in a browser bundle.
The list resolver uses Scan to preserve a simple all-articles demonstration. It is not the right production access pattern for every request: model an author, tenant or publication key and use Query for those lists. The cursor is encoded, not encrypted or signed. Treat it as untrusted input, and bind production cursors to the authorized query scope.
Use Amplify Gen 2 for a generated data API
Amplify Gen 2 defines the backend in TypeScript. The old amplify import storage and amplify push workflow belongs to Gen 1. Gen 1 entered maintenance on May 1, 2026 and is scheduled to reach end of life on May 1, 2027, according to the maintainers' notice.
In an initialized Gen 2 project with Amplify Auth configured, define amplify/data/resource.ts:
Register the resources in amplify/backend.ts:
Use npx ampx sandbox for the development environment. This provisions AWS resources; it is not merely an offline GraphQL emulator. Configure the frontend using the generated outputs and create a typed client after the user signs in:
Adjust imports to your frontend's directory layout. Owner authorization makes this a per-user model; it does not make every article public. Amplify creates its own table and generated operations, so this model is an alternative to the manually managed Articles schema above. Follow Amplify Data setup for initialization and deployment.
Connect an existing DynamoDB table
For an existing table, use a custom data source rather than assuming a.model() adopts it. In amplify/backend.ts, replace the bare backend declaration with:
Define these entries in amplify/data/resource.ts and include ...existingOperations inside its a.schema({ ... }) object:
Put the earlier AppSync getArticle JavaScript resolver in amplify/data/get-existing-article.js. Authenticated access allows every signed-in caller of this operation to request a known ID; add tenant or ownership checks if that is too broad. Importing the table does not infer its authorization model or convert its items into generated Amplify models. The external DynamoDB data-source guide covers custom operation wiring.
Common GraphQL and DynamoDB operations
The following examples use the manually defined AppSync schema. GraphQL has no universal _inc, append() or SQL-style where syntax; those names only work if a particular schema and resolver implement them.
Increment a counter
Attach this request function to incrementViews, with the same error-checking response function used above:
This is an atomic numeric update, not an auto-incrementing primary key. Retrying the mutation can increment again; use an idempotency mechanism if each logical event must count once.
Batch get items
A schema field such as batchGetArticles(ids: [ID!]!) needs a BatchGetItem resolver. The request names the physical table and converts each ID into a key. Handle missing items and unprocessed keys explicitly; a partial batch is not proof every requested item was absent or returned. AWS provides the AppSync batch-get request and response format.
DynamoDB batch get supports up to 100 items and a 16 MB response, and can return less. Deduplicate input keys and define how the API reports partial results. Do not silently return an empty list on a database error.
Query a string list
A list attribute can be returned as [String!]. Searching every article containing a publication is a different access pattern. Model an index or related items for that query instead of assuming GraphQL can turn arbitrary list membership into an efficient DynamoDB key lookup.
Append to a list
For appendPublication, attach this request function and the shared response function:
The create resolver initializes the list. Appending does not deduplicate values, and repeated mutations can append twice. Choose a set or separate membership items if uniqueness is required.
Paginate with a cursor
Attach this resolver to the demonstration listArticles field. The limit cap is an application choice; for production keyed lists, use query with the same pagination pattern:
Pass the returned token into the next request until it is absent. AppSync pagination tokens and the Apollo example's encoded keys are different formats and are not interchangeable. Filtering can produce an empty page with a continuation token. See DynamoDB pagination.
Access patterns, N+1 queries and real-time updates
Design each API field around a known database operation. A single-table design can place related entities together for one query, but it is not mandatory for every GraphQL application. Multiple tables or generated Amplify models can be appropriate. Use single-table design where it simplifies actual access patterns.
The N+1 problem occurs when a parent list causes an extra database call for every child. Batch or prefetch related records, and consider request-scoped loaders in Apollo. Avoid treating it as merely the opposite of over-fetching: one request can suffer from both. Returning fewer GraphQL fields does not automatically change the number or capacity cost of DynamoDB requests.
AppSync GraphQL subscriptions are triggered through configured mutations. An SDK write made directly to the table does not automatically publish a GraphQL subscription event. If external writes must notify clients, use a DynamoDB stream consumer that calls a suitable authorized mutation or an explicitly designed event-publishing path. Prevent that path from writing back and recursively triggering itself. See AppSync subscriptions.
Test authorization with two different users, paginate beyond one page, simulate conditional-write failures and verify duplicate mutation behavior. Monitor resolver errors and DynamoDB throttling alongside API latency. Successful schema deployment alone does not prove the data access or ownership rules are correct.
Frequently Asked Questions
Does DynamoDB support GraphQL directly?
Do AppSync DynamoDB resolvers require Lambda?
Can Amplify Gen 2 use an existing DynamoDB table?
Do direct DynamoDB writes trigger AppSync GraphQL subscriptions?
Is single-table design required for GraphQL?
Dynobase is a Professional GUI Client for DynamoDB
Start your 7-day free trial today