
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
You need to build a GraphQL API with AWS AppSync when your application requires real-time data synchronization, offline capabilities, or flexible querying without managing server infrastructure. Unlike traditional REST endpoints on EC2, AppSync decouples the API layer from compute, handling caching, subscriptions, and authorization at the edge. This guide walks through provisioning a production-grade AppSync API using Terraform, connecting it to DynamoDB, and securing it with IAM, following the same infrastructure-as-code principles outlined in my practical Terraform guide.
How do you define an AppSync GraphQL schema?
The schema is the contract. When you build a GraphQL API with AWS AppSync, you define types in Schema Definition Language (SDL). A common mistake is copying REST resource structures directly into GraphQL; instead, model your types around client use cases. AppSync supports standard SDL with directives like @aws_cognito_user_pools for field-level authorization.
type Post {
id: ID!
title: String!
content: String
authorId: ID!
createdAt: AWSDateTime!
}
type Query {
getPost(id: ID!): Post
listPosts(limit: Int, nextToken: String): PostConnection
}
type Mutation {
createPost(input: CreatePostInput!): Post
}
type Subscription {
onCreatePost: Post @aws_subscribe(mutations: ["createPost"])
}
type PostConnection {
items: [Post]
nextToken: String
} - AWS Scalars: Use
AWSDateTime,AWSJSON, andAWSEmailfor built-in validation and serialization. - Pagination: Always implement cursor-based pagination (
nextToken) rather than offset-based for DynamoDB compatibility. - Subscriptions: Link mutations to subscriptions using
@aws_subscribe; AppSync handles the WebSocket lifecycle automatically.
How do you provision AppSync infrastructure with Terraform?
Never click through the console for production APIs. Infrastructure as Code ensures reproducibility and audit trails. If you are new to this workflow, review AWS IAM best practices before creating roles. The following Terraform configuration provisions the API, data source, and resolver atomically.
resource "aws_appsync_graphql_api" "api" {
name = "blog-api"
authentication_type = "AMAZON_COGNITO_USER_POOLS"
user_pool_config {
aws_region = var.aws_region
user_pool_id = aws_cognito_user_pool.pool.id
default_action = "ALLOW"
}
log_config {
cloudwatch_logs_role_arn = aws_iam_role.appsync_log.arn
field_log_level = "ERROR"
}
}
resource "aws_appsync_datasource" "posts_table" {
api_id = aws_appsync_graphql_api.api.id
name = "PostsTable"
type = "AMAZON_DYNAMODB"
service_role_arn = aws_iam_role.appsync_dynamo.arn
dynamodb_config {
table_name = aws_dynamodb_table.posts.name
}
} Key considerations for 2026 deployments:
- Logging: Enable CloudWatch Logs at ERROR level minimum. ALL level generates excessive costs in high-traffic environments.
- IAM Roles: Create separate roles for logging and data source access. Never reuse the account root or admin roles.
- State Management: Store Terraform state in S3 with DynamoDB locking, never locally.
How do you write VTL resolvers for DynamoDB?
Velocity Template Language (VTL) remains the default for AppSync resolvers in 2026, though JavaScript resolvers are now GA. VTL is verbose but predictable. The request template transforms GraphQL arguments into DynamoDB operations; the response template maps results back to your schema.
Request Mapping Template
{
"version": "2018-05-29",
"operation": "PutItem",
"key": {
"id": $util.dynamodb.toDynamoDBJson($util.autoId())
},
"attributeValues": {
"title": $util.dynamodb.toDynamoDBJson($ctx.args.input.title),
"content": $util.dynamodb.toDynamoDBJson($ctx.args.input.content),
"authorId": $util.dynamodb.toDynamoDBJson($ctx.identity.sub),
"createdAt": $util.dynamodb.toDynamoDBJson($util.time.nowISO8601())
},
"condition": {
"expression": "attribute_not_exists(id)"
}
} Response Mapping Template
#if($ctx.error)
$util.error($ctx.error.message, $ctx.error.type)
#end
$util.toJson($ctx.result) In practice, always include condition expressions to prevent accidental overwrites. The $util.autoId() function generates ULIDs, which sort chronologically unlike UUIDs. For complex transformations, consider moving logic to Lambda, but remember that each Lambda invocation adds latency and cost.
How does AppSync compare to API Gateway with Lambda?
Choosing between AppSync and API Gateway depends on your data access patterns. I have deployed both extensively; neither is universally superior. Refer to when serverless actually makes sense for broader context on this decision.
| Criteria | AWS AppSync | API Gateway + Lambda |
|---|---|---|
| Data Fetching | Client-driven, single endpoint | Server-defined REST endpoints |
| Real-time | Built-in WebSocket subscriptions | Requires API Gateway WebSocket API |
| Caching | Per-field TTL caching at edge | Full-response caching only |
| Offline Sync | DataStore SDK handles conflicts | Custom implementation required |
| Resolver Logic | VTL/JS templates or Lambda | Lambda only |
| Cost Model | Per-query + data transfer | Per-request + Lambda duration |
| Best For | CRUD apps, mobile, real-time dashboards | Complex business logic, integrations |
If your frontend team needs flexibility and you have straightforward CRUD operations, AppSync wins. If your backend requires heavy processing, third-party integrations, or non-HTTP protocols, stick with API Gateway.
How do you secure and monitor AppSync in production?
Security cannot be an afterthought. Configure Cognito User Pools for user-facing APIs or IAM for service-to-service calls. Enable WAF integration if exposing public endpoints. For monitoring, rely on CloudWatch Metrics for latency and error rates, but also enable X-Ray tracing to visualize resolver chains. In Nepal-based projects serving global users, deploy AppSync in the region closest to your primary user base; latency matters more than marginal cost differences.
- Field-Level Auth: Use
@aws_authdirectives to restrict sensitive fields to specific Cognito groups. - Rate Limiting: AppSync has no native throttling; place CloudFront in front with rate-based rules.
- Schema Validation: Run
amplify codegenor similar in CI to catch breaking changes before deployment. - Backup: Enable DynamoDB PITR; AppSync itself is stateless and requires no backup.
Next Steps for Your AppSync Deployment
When you build a GraphQL API with AWS AppSync correctly, you gain a managed, scalable data layer that frees your team from boilerplate. Start with the Terraform configuration above, iterate on your schema with real client feedback, and instrument observability before your first production release. If you need help architecting a compliant, audit-ready serverless backend or optimizing an existing deployment, reach out to discuss your project.