.NET on AWS Blog

Building a GraphQL API with AWS AppSync Using Direct Lambda Resolvers in .NET

GraphQL APIs enables clients to request specific data sets, reducing over-fetching compared to traditional REST APIs, which return fixed data structures with unnecessary fields. Unlike REST APIs that require multiple endpoints and round trips to gather related data, GraphQL provides a single endpoint. This enables clients to fetch exactly what they need in one request, improving performance and reducing bandwidth usage. AWS AppSync is a fully managed service that lets you build GraphQL APIs by securely connecting to the data sources like Amazon DynamoDB, AWS Lambda, and other AWS services. AWS AppSync previously required Velocity Template Language (VTL) or JavaScript resolvers to map GraphQL queries to data sources. VTL’s complex syntax creates maintenance challenges, especially for new developers. Now with Direct Lambda Resolvers, you can use your preferred programming languages, including .NET, without VTL templates.

This post demonstrates how to build a serverless GraphQL API using AWS AppSync and Direct Lambda Resolvers with .NET Lambda functions. This solution uses AWS Cloud Development Kit (AWS CDK) for infrastructure deployment and Amazon DynamoDB for storage. By following this approach, you will create a scalable API solution while adhering to infrastructure-as-code best practices. The complete solution, including source code and deployment instructions, are available in our GitHub repository at aws-samples/sample-appsync-dotnet-lambda-resolvers.

Solution Overview

This solution implements a serverless Todo API that supports Create, Read, Update, and Delete (CRUD) operations through following components:

  • AWS AppSync provides the GraphQL API
  • .NET Lambda functions handle all the backend logic
  • Amazon DynamoDB stores the application data
  • AWS CDK handles infrastructure provisioning and component integration
Architecture diagram showing AWS AppSync requests and responses to AWS Lambda service, which communicates with Amazon DynamoDB service.

Figure 1: AWS AppSync with Direct Lambda Resolvers and .NET backend

The architecture diagram in Figure 1 shows integration between AWS AppSync and Lambda using Direct Lambda resolver. While VTL/JavaScript mapping templates remain available as an option, Direct Lambda resolvers simplify request/response handling by enabling you to work directly in .NET without additional mapping logic.

Prerequisites

To implement this solution, you need the following prerequisites:

Deploying with AWS CDK

The sample application demonstrates serverless architecture with the following key components:

  • GraphQL API powered by AWS AppSync
  • .NET Lambda functions implementing business logic and serving as resolvers
  • Amazon DynamoDB for data persistence
  • Infrastructure as Code using AWS CDK for provisioning all required AWS resources

Deployment Steps

1. Clone the Repository:

git clone https://github.com/aws-samples/sample-appsync-dotnet-lambda-resolvers.git
cd sample-appsync-dotnet-lambda-resolvers

2. Build the .NET Lambda functions:

cd src/TodoApp.Api
dotnet lambda package
cd ../..

3. Deploy using CDK. First-time CDK users must bootstrap their environment:

cdk bootstrap  # Only needed the first time you use CDK in an account/region

4. Then deploy the stack:

cdk deploy

This CDK stack will provision the following AWS resources:

  • AWS AppSync API with GraphQL schema
  • Amazon DynamoDB table for storing todos
  • Five .NET Lambda functions for CRUD operations
  • Direct resolvers that connect GraphQL operations to Lambda functions
Screenshot of AWS CloudFormation console showing application stack deployment

Figure 2: Application stack deployment using CDK

After deployment, the CDK outputs the GraphQL API URL and API key for accessing your API.

Exploring the GraphQL Schema

The GraphQL schema is the foundation of any GraphQL server, defining the contract between your client and server.

In this project, the schema is defined declaratively in the CDK stack by referencing a schema.graphql file stored in the repository:

var api = new GraphqlApi(this, "TodoApi", new GraphqlApiProps
{
    Name = "dotnet-appsync-todo-api",
    Definition = Definition.FromFile("src/TodoApp.Cdk/graphql/schema.graphql"),
});

The CDK stack loads the GraphQL schema definition from src/TodoApp.Cdk/graphql/. Storing the schema in a separate file simplifies maintenance, version control, and extensions.

The following schema defines a simple structure for managing tasks, including create, update, delete and query tasks.

# Queries for fetching data
type Query {
    getTodoById(id: ID!): Todo
    listTodos: [Todo]
}

# Mutations for data manipulation
type Mutation {
    createTodo(title: String!, description: String): Todo
    updateTodo(
        id: ID!,
        title: String!,
        description: String,
        completed: Boolean!
    ): Todo
    deleteTodo(id: ID!): Todo
}

# Input types for mutations
input CreateTodoItem {
    title: String!
    description: String
}

input UpdateTodoItem {
    id: ID!
    title: String!
    description: String
    completed: Boolean!
}

type Todo {
    id: ID!
    title: String!
    description: String
    completed: Boolean!
    createdAt: AWSDateTime
    updatedAt: AWSDateTime
}

schema {
    query: Query
    mutation: Mutation
}

GraphQL uses a schema to describe the shape of your available data.

1. Type

a. Todo type represents a todo item with fields for title, description, completed, createdAt, and updatedAt.

b. CreateTodoItem type is defined as input type.

c. UpdateTodoItem type is defined as input type.

2. Queries

a. getTodoById: Fetch a single todo item by its ID.

b. listTodos: Fetch all todo items.

3. Mutations

a. createTodo: Add a new todo item.

b. updateTodo: Update the details of an existing todo item.

c. deleteTodo: Remove a todo item.

How Direct Lambda Resolvers differs from VTL or JavaScript resolvers

AWS AppSync offers multiple resolver types to handle GraphQL operations. While VTL and JavaScript resolvers have been the traditional choices, Direct Lambda Resolvers provide a more streamlined approach for Lambda integration. VTL and JavaScript resolvers require you to write mapping templates to transform data between GraphQL and data sources. Direct Lambda Resolvers simplify this process by supporting direct AWS Lambda function invocation without mapping templates.

When you configure a resolver in AppSync, you can choose between VTL/JavaScript mapping templates or a Direct Lambda Resolver. By selecting a Direct Lambda Resolver, you don’t need mapping templates, AppSync automatically forwards the full context object of the GraphQL request directly to the Lambda function.

The context object includes the following:

  1. Query Arguments (arguments field) – Contains all GraphQL query/mutation parameters and input variables.
  2. Authentication/Authorization Metadata – Provides details about the current user context and authorization scopes.
  3. Identity Claims – Includes user pool attributes and AWS Identity and Access Management (IAM) role details when using Amazon Cognito or IAM authentication.
  4. HTTP Headers – Contains request headers, API key information, and any custom headers.
  5. Info Object – Provides operation details including field name, parent type, and selection set information.

Understanding .NET Direct Lambda Resolvers

With Direct Lambda Resolvers, AppSync forwards the full GraphQL context to your Lambda functions eliminating the need for VTL or JavaScript resolvers. To handle AppSync events, you need to add the Amazon.Lambda.AppSyncEvents NuGet package to the project:

dotnet add package Amazon.Lambda.AppSyncEvents

This package provides access to the entire payload that AWS AppSync sends directly to your Lambda function. You get access to the HTTP Headers, and user’s identity for various authorization modes including AWS IAM, Amazon Cognito, OpenID Connect (OIDC) or Custom Lambda authorization.

The following code demonstrates this package implementation in a .NET Lambda function:

[LambdaFunction()]
[Logging(LogEvent = true)]
public async Task<Todo> CreateTodoItem(AppSyncResolverEvent<CreateTodoItem> appSyncEvent)
{
    return await _todoService.CreateTodoItem(appSyncEvent.Arguments);
}

This Lambda function uses AWS Lambda Powertools for .NET to enable structured logging and the .NET Lambda Annotations Framework for simplified function development. It processes GraphQL mutation requests to create new Todo items through AppSync resolver events, handling the asynchronous creation by delegating the request arguments to the Todo service layer.

For the complete Lambda function’s implementation, see the GitHub repository.

GraphQL API testing

You can test your AWS AppSync API using following methods:

1. The AWS AppSync Console provides a built-in query editor for interactive testing of GraphQL operations:

Screenshot of AWS AppSync console showing query testing

Figure 3: GraphQL API testing from AWS console

2. You can also test your API using popular API client tool like Postman or Insomnia:

Screenshot of GraphQL testing in Postman

Figure 4: GraphQL API testing from Postman

When testing with API clients like Postman or Insomnia, configure your requests with the appropriate headers (Content-Type and x-api-key) and include your GraphQL operations in the request body. While the complete collection of API request payloads for all Todo APIs are available in the GitHub repository, here’s an example mutation to help you get started:

mutation {
  createTodo(title: "Learn AWS AppSync", description: "Build GraphQL API with .NET Lambda resolvers") {
    id
    title
    description
    completed
    createdAt
  }
}

Local Lambda function testing with .NET Aspire

This project includes a .NET Aspire host project (TodoApp.AspireHost) for local Lambda function testing. While this is not directly related to AppSync testing, it provides a convenient way to debug and validate your Lambda function’s logic before deployment. For detailed setup instructions and examples of local Lambda function testing using .NET Aspire, refer to the GitHub repository.

Clean up

Clean up the resources that were created for the sample application to avoid incurring charges. To delete resources and prevent additional charges, use either of these methods:

Conclusion

In this post, we demonstrated how to build a serverless GraphQL API using AWS AppSync with Direct Lambda Resolvers in .NET. By leveraging Direct Lambda Resolvers, we simplified the development process by handling all resolver logic directly in Lambda functions, eliminating the complexity of VTL templates. This approach provides .NET developers with a familiar programming model, while maintaining the full power and flexibility of GraphQL APIs. For simplicity, we used API Key authorization in this implementation. In our next blog post, we will dive deep into AWS AppSync’s comprehensive authorization capabilities, including:

  • Amazon Cognito User Pools – for user-based authentication
  • OpenID Connect – for integration with external identity providers
  • IAM – for service-to-service authentication
  • Lambda authorizers – for custom authorization logic

Next Steps

Deploy the sample Todo API using the provided CDK commands and explore its capabilities using the AWS AppSync Console’s built-in query editor. Once familiar with the basic implementation, customize the schema to match your specific requirements by adding new fields or operations.

Pankaj Rawat

Pankaj Rawat

Pankaj Rawat is a Lead Consultant with AWS Professional Services. He is passionate about helping customers to build modern applications on AWS and guiding customers to migrate their .NET applications to AWS.

Sanjay Chaudhari

Sanjay Chaudhari

Sanjay Chaudhari is a Lead Consultant in AWS Professional Services. His expertise lies in guiding customers through the process of migrating and modernizing their .NET applications to leverage the full potential of the AWS.