Event Handler for AWS AppSync GraphQL APIs simplifies routing and processing of events in AWS Lambda functions. It allows you to define resolvers for GraphQL types and fields, making it easier to handle GraphQL requests without the need for complex VTL or JavaScript templates.
stateDiagram-v2
direction LR
EventSource: AWS Lambda Event Sources
EventHandlerResolvers: AWS AppSync invocation
LambdaInit: Lambda invocation
EventHandler: Event Handler
EventHandlerResolver: Route event based on GraphQL type/field keys
YourLogic: Run your registered resolver function
EventHandlerResolverBuilder: Adapts response to Event Source contract
LambdaResponse: Lambda response
state EventSource {
EventHandlerResolvers
}
EventHandlerResolvers --> LambdaInit
LambdaInit --> EventHandler
EventHandler --> EventHandlerResolver
state EventHandler {
[*] --> EventHandlerResolver: app.resolve(event, context)
EventHandlerResolver --> YourLogic
YourLogic --> EventHandlerResolverBuilder
}
EventHandler --> LambdaResponse
Direct Lambda Resolver. A custom AppSync Resolver that bypasses Apache Velocity Template (VTL) and JavaScript templates, and automatically maps your function's response to a GraphQL field.
Batching resolvers. A technique that allows you to batch multiple GraphQL requests into a single Lambda function invocation, reducing the number of calls and improving performance.
You must have an existing AppSync GraphQL API and IAM permissions to invoke your Lambda function. That said, there is no additional permissions to use Event Handler as routing requires no dependency (standard library).
This is the sample infrastructure we will be using for the initial examples with an AppSync Direct Lambda Resolver.
AWSTemplateFormatVersion:"2010-09-09"Transform:AWS::Serverless-2016-10-31Description:Hello world Direct Lambda ResolverGlobals:Function:Timeout:5MemorySize:256Runtime:nodejs22.xEnvironment:Variables:# Powertools for AWS Lambda (TypeScript) env vars: https://docs.powertools.aws.dev/lambda/typescript/latest/environment-variables/POWERTOOLS_LOG_LEVEL:INFOPOWERTOOLS_LOGGER_SAMPLE_RATE:0.1POWERTOOLS_LOGGER_LOG_EVENT:truePOWERTOOLS_SERVICE_NAME:exampleResources:TodosFunction:Type:AWS::Serverless::FunctionProperties:Handler:index.handlerCodeUri:hello_world# IAM Permissions and RolesAppSyncServiceRole:Type:"AWS::IAM::Role"Properties:AssumeRolePolicyDocument:Version:"2012-10-17"Statement:-Effect:"Allow"Principal:Service:-"appsync.amazonaws.com"Action:-"sts:AssumeRole"InvokeLambdaResolverPolicy:Type:"AWS::IAM::Policy"Properties:PolicyName:"DirectAppSyncLambda"PolicyDocument:Version:"2012-10-17"Statement:-Effect:"Allow"Action:"lambda:invokeFunction"Resource:-!GetAttTodosFunction.ArnRoles:-!RefAppSyncServiceRole# GraphQL APITodosApi:Type:"AWS::AppSync::GraphQLApi"Properties:Name:TodosApiAuthenticationType:"API_KEY"XrayEnabled:trueTodosApiKey:Type:AWS::AppSync::ApiKeyProperties:ApiId:!GetAttTodosApi.ApiIdTodosApiSchema:Type:"AWS::AppSync::GraphQLSchema"Properties:ApiId:!GetAttTodosApi.ApiIdDefinitionS3Location:../src/getting_started_schema.graphqlMetadata:cfn-lint:config:ignore_checks:-W3002# allow relative path in DefinitionS3Location# Lambda Direct Data Source and ResolverTodosFunctionDataSource:Type:"AWS::AppSync::DataSource"Properties:ApiId:!GetAttTodosApi.ApiIdName:"HelloWorldLambdaDirectResolver"Type:"AWS_LAMBDA"ServiceRoleArn:!GetAttAppSyncServiceRole.ArnLambdaConfig:LambdaFunctionArn:!GetAttTodosFunction.ArnListTodosResolver:Type:"AWS::AppSync::Resolver"Properties:ApiId:!GetAttTodosApi.ApiIdTypeName:"Query"FieldName:"listTodos"DataSourceName:!GetAttTodosFunctionDataSource.NameGetTodoResolver:Type:"AWS::AppSync::Resolver"Properties:ApiId:!GetAttTodosApi.ApiIdTypeName:"Query"FieldName:"getTodo"DataSourceName:!GetAttTodosFunctionDataSource.NameCreateTodoResolver:Type:"AWS::AppSync::Resolver"Properties:ApiId:!GetAttTodosApi.ApiIdTypeName:"Mutation"FieldName:"createTodo"DataSourceName:!GetAttTodosFunctionDataSource.NameOutputs:TodosFunction:Description:"HelloWorldLambdaFunctionARN"Value:!GetAttTodosFunction.ArnTodosApi:Value:!GetAttTodosApi.GraphQLUrl
You can register functions to match GraphQL types and fields with one of three methods:
onQuery() - Register a function to handle a GraphQL Query type.
onMutation() - Register a function to handle a GraphQL Mutation type.
resolver() - Register a function to handle a GraphQL type and field.
What is a type and field?
A type would be a top-level GraphQL Type like Query, Mutation, Todo. A GraphQL Field would be listTodos under Query, createTodo under Mutation, etc.
The function receives the parsed arguments from the GraphQL request as its first parameter. We also take care of parsing the response or catching errors and returning them in the expected format.
When registering a resolver for a Query type, you can use the onQuery() method. This method allows you to define a function that will be invoked when a GraphQL Query is made.
Registering a resolver for a Query type
1 2 3 4 5 6 7 8 9101112131415161718192021
import{AppSyncGraphQLResolver}from'@aws-lambda-powertools/event-handler/appsync-graphql';import{Logger}from'@aws-lambda-powertools/logger';importtype{Context}from'aws-lambda';constlogger=newLogger({serviceName:'TodoManager',});constapp=newAppSyncGraphQLResolver({logger});app.onQuery<{id:string}>('getTodo',async({id})=>{logger.debug('Resolving todo',{id});// Simulate fetching a todo from a database or external servicereturn{id,title:'Todo Title',completed:false,};});exportconsthandler=async(event:unknown,context:Context)=>app.resolve(event,context);
Similarly, you can register a resolver for a Mutation type using the onMutation() method. This method allows you to define a function that will be invoked when a GraphQL Mutation is made.
Registering a resolver for a Mutation type
1 2 3 4 5 6 7 8 910111213141516171819202122232425
import{AppSyncGraphQLResolver,makeId,}from'@aws-lambda-powertools/event-handler/appsync-graphql';import{Logger}from'@aws-lambda-powertools/logger';importtype{Context}from'aws-lambda';constlogger=newLogger({serviceName:'TodoManager',});constapp=newAppSyncGraphQLResolver({logger});app.onMutation<{title:string}>('createTodo',async({title})=>{logger.debug('Creating todo',{title});consttodoId=makeId();// Simulate creating a todo in a database or external servicereturn{id:todoId,title,completed:false,};});exportconsthandler=async(event:unknown,context:Context)=>app.resolve(event,context);
When you want to have more control over the type and field, you can use the resolver() method. This method allows you to register a function for a specific GraphQL type and field including custom types.
import{AppSyncGraphQLResolver}from'@aws-lambda-powertools/event-handler/appsync-graphql';import{Logger}from'@aws-lambda-powertools/logger';importtype{Context}from'aws-lambda';constlogger=newLogger({serviceName:'TodoManager',});constapp=newAppSyncGraphQLResolver({logger});app.resolver(async()=>{logger.debug('Resolving todos');// Simulate fetching a todo from a database or external servicereturn[{id:'todo-id',title:'Todo Title',completed:false,},{id:'todo-id-2',title:'Todo Title 2',completed:true,},];},{fieldName:'listTodos',typeName:'Query',});exportconsthandler=async(event:unknown,context:Context)=>app.resolve(event,context);
If you prefer to use the decorator syntax, you can instead use the same methods on a class method to register your handlers. Learn more about how Powertools for TypeScript supports decorators.
importtype{LambdaInterface}from'@aws-lambda-powertools/commons/types';import{AppSyncGraphQLResolver,makeId,}from'@aws-lambda-powertools/event-handler/appsync-graphql';import{Logger}from'@aws-lambda-powertools/logger';importtype{Context}from'aws-lambda';constlogger=newLogger({serviceName:'TodoManager',});constapp=newAppSyncGraphQLResolver({logger});classLambdaimplementsLambdaInterface{@app.onMutation('createTodo')publicasynccreateTodo({title}:{title:string}){logger.debug('Creating todo',{title});consttodoId=makeId();// Simulate creating a todo in a database or external servicereturn{id:todoId,title,completed:false,};}@app.onQuery('getTodo')publicasyncgetTodo({id}:{id:string}){logger.debug('Resolving todo',{id});// Simulate fetching a todo from a database or external servicereturn{id,title:'Todo Title',completed:false,};}@app.resolver({fieldName:'listTodos',typeName:'Query',})publicasynclistTodos(){logger.debug('Resolving todos');// Simulate fetching a todo from a database or external servicereturn[{id:'todo-id',title:'Todo Title',completed:false,},{id:'todo-id-2',title:'Todo Title 2',completed:true,},];}asynchandler(event:unknown,context:Context){returnapp.resolve(event,context,{scope:this});// (1)!}}constlambda=newLambda();exportconsthandler=lambda.handler.bind(lambda);
It's recommended to pass a refernce of this to ensure the correct class scope is propageted to the route handler functions.
import{AppSyncGraphQLResolver,awsDate,awsDateTime,awsTime,awsTimestamp,makeId,}from'@aws-lambda-powertools/event-handler/appsync-graphql';importtype{Context}from'aws-lambda';constapp=newAppSyncGraphQLResolver();app.resolver(async({title,content})=>{// your business logic herereturn{title,content,id:makeId(),createdAt:awsDateTime(),updatedAt:awsDateTime(),timestamp:awsTimestamp(),time:awsTime(),date:awsDate(),};},{fieldName:'createTodo',typeName:'Mutation',});exportconsthandler=async(event:unknown,context:Context)=>app.resolve(event,context);
Here's a table with their related scalar as a quick reference:
By default, the utility uses the global console logger and emits only warnings and errors.
You can change this behavior by passing a custom logger instance to the AppSyncGraphQLResolver or Router and setting the log level for it, or by enabling Lambda Advanced Logging Controls and setting the log level to DEBUG.
When debug logging is enabled, the resolver will emit logs that show the underlying handler resolution process. This is useful for understanding how your handlers are being resolved and invoked and can help you troubleshoot issues with your event processing.
For example, when using the Powertools for AWS Lambda logger, you can set the LOG_LEVEL to DEBUG in your environment variables or at the logger level and pass the logger instance to the constructor to enable debug logging.
import{AppSyncGraphQLResolver}from'@aws-lambda-powertools/event-handler/appsync-graphql';import{Logger}from'@aws-lambda-powertools/logger';import{correlationPaths,search,}from'@aws-lambda-powertools/logger/correlationId';importtype{Context}from'aws-lambda';constlogger=newLogger({serviceName:'TodoManager',logLevel:'DEBUG',correlationIdSearchFn:search,});constapp=newAppSyncGraphQLResolver({logger});app.onQuery<{id:string}>('getTodo',async({id})=>{logger.debug('Resolving todo',{id});// Simulate fetching a todo from a database or external servicereturn{id,title:'Todo Title',completed:false,};});exportconsthandler=async(event:unknown,context:Context)=>{logger.setCorrelationId(event,correlationPaths.APPSYNC_RESOLVER);returnapp.resolve(event,context);};
[{"level":"DEBUG","message":"Adding resolver for field Query.getTodo","timestamp":"2025-07-02T13:39:36.017Z","service":"service_undefined","sampling_rate":0},{"level":"DEBUG","message":"Looking for resolver for type=Query, field=getTodo","timestamp":"2025-07-02T13:39:36.033Z","service":"service_undefined","sampling_rate":0,"xray_trace_id":"1-68653697-0f1223120d19409c38812f01","correlation_id":"Root=1-68653697-3623822a02e171272e2ecfe4"},{"level":"DEBUG","message":"Resolving todo","timestamp":"2025-07-02T13:39:36.033Z","service":"service_undefined","sampling_rate":0,"xray_trace_id":"1-68653697-0f1223120d19409c38812f01","correlation_id":"Root=1-68653697-3623822a02e171272e2ecfe4","id":"42"}]