Simplify AWS Appsync GraphQL API creation with strongly typed Typescript resolvers
Tags: aws, appsync, typescript, cdk
Published: 2023-07-30 07:20:46

> This post assumes familiarity with [AWS Appsync](https://aws.amazon.com/appsync/) and [AWS CDK](https://aws.amazon.com/cdk/) in general

## Introduction

In this post, we'll look into creating an AWS Appsync GraphQL API with TypeScript using [cdk-appsync-typescript-resolver](https://constructs.dev/packages/cdk-appsync-typescript-resolver) AWS CDK construct. This powerful construct simplifies the creation of AWS AppSync resolvers in your CDK project, making it easier and more efficient for developers to work with AppSync.

> Complete code is available in [Github](https://github.com/sudokar/cdk-appsync-typescript-resolver-demo) for reference

## Codegen

We can generate Typescript types using [GraphQL Code Gen](https://the-guild.dev/graphql/codegen) easily.

These [types](https://github.com/sudokar/cdk-appsync-typescript-resolver-demo/blob/main/src/types/appsync.ts) are generated using Codegen CLI command `graphql-codegen` for this [graphql schema](https://github.com/sudokar/cdk-appsync-typescript-resolver-demo/blob/main/schema.graphql) using this [codegen](https://github.com/sudokar/cdk-appsync-typescript-resolver-demo/blob/main/codegen.ts) config file.

**Note:** Alternatively, We can use [Amplify codegen](https://github.com/aws-amplify/amplify-codegen) as well

## Resolver in Typescript

We can start writing our resolvers in Typescript using the Codegen generated types and the Appsync's [utils package](https://www.npmjs.com/package/@aws-appsync/utils)

Lets look at an example resolver. [Link to File](https://github.com/sudokar/cdk-appsync-typescript-resolver-demo/blob/main/src/resolvers/addTodo.ts)

```typescript
import { Context, DynamoDBPutItemRequest, util } from "@aws-appsync/utils";
import { MutationAddTodoArgs, Todo } from "../types/appsync";

export function request(
  ctx: Context<MutationAddTodoArgs>
): DynamoDBPutItemRequest {
  const { id, ...attrValues } = ctx.args;

  return {
    operation: "PutItem",
    key: {
      id: util.dynamodb.toDynamoDB(id),
    },
    attributeValues: util.dynamodb.toMapValues({
      ...attrValues,
    }),
  };
}

export function response(
  ctx: Context<MutationAddTodoArgs, object, object, object, Todo>
) {
  return ctx.result;
}
```

This looks a lot better than writing resolvers in Javascript or VTL :sweat: 

We can use types for context, various data sources supported by Appsync and [built-in utilities](https://docs.aws.amazon.com/appsync/latest/devguide/built-in-util-js.html) along with generated types for GraphQL schema, which enhances the reliability and maintainability code through static type checking

## Transpiling and Bundling

Unfortunately, we can't just sent the resolvers written in Typescript to Appsync yet. Like we transpile Typescirpt to Javascript for AWS Lambda, We will have to do the same for Appsync.

Unfortunately, as of today there is no built-in capability in CDK to do this for us. We will have to do it ourself using [Esbuild](https://esbuild.github.io/) or something similar, which transpiles and bundles code from Typescript to Javascript.

Using Esbuild's [Javascript API](https://esbuild.github.io/api/#js-sync), We have a custom CDK construct [cdk-appsync-typescript-resolver](https://constructs.dev/packages/cdk-appsync-typescript-resolver) that will automate the transpilation and bundling process for us.

An example of creating Typescript function using the custom construct [AppsyncTypescriptFunction](https://constructs.dev/packages/cdk-appsync-typescript-resolver/v/0.0.4/api/AppsyncTypescriptFunction?lang=typescript). This abstracts the transpiling and bundling process and setting default properties.

```typescript
const addTodo = new AppsyncTypescriptFunction(this, "AddTodoFunction", {
      name: "addTodo",
      api: graphqlApi,
      dataSource: dynamoDataSource,
      path: path.join(__dirname, "addTodo.ts"),
      replaceStrings: {
          ENV: "prod",
      },
    });
```

JS resolvers doesn't support unit resolvers, supports only [pipeline resolvers](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-reference-overview-js.html#anatomy-of-a-pipeline-resolver-js). When we want to create a unit resolver, we will have to use bit of boilerplate code and settings to make the pipeline resolver as unit resolver.

An added functionality to the construct is to define a property `replaceStrings`, which takes a map of from and to strings, used to replace strings in the transpiled and bundled code.

We have another CDK construct [TSExpressPipelineResolver](https://constructs.dev/packages/cdk-appsync-typescript-resolver/v/0.0.4/api/TSExpressPipelineResolver?lang=typescript) to use the [AppsyncTypescriptFunction](https://constructs.dev/packages/cdk-appsync-typescript-resolver/v/0.0.4/api/AppsyncTypescriptFunction) and sets up required boilerplate for us.

An example of using `TSExpressPipelineResolver` construct.

```typescript
new TSExpressPipelineResolver(this, "AddTodoResolver", {
      api: graphqlApi,
      typeName: "Mutation",
      fieldName: "addTodo",
      tsFunction: addTodo,
    });
```
> **Note:** We can use instance of `AppsyncTypescriptFunction` construct, which extends `appsync.AppsyncFunction`, in a regular `appsync.Resolver` construct too

## Deploying the stack

You can find the complete code of this demo on [GitHub](https://github.com/sudokar/cdk-appsync-typescript-resolver-demo).

```typescript
cdk deploy
```

## Tip

Catch invalid syntax (things not supported by Appsync's JS Resolver) while writing resolvers using the Eslint plugin [@aws-appsync/eslint-plugin](https://www.npmjs.com/package/@aws-appsync/eslint-plugin)

## Conclusion

By using the constructs from [cdk-appsync-typescript-resolver](https://constructs.dev/packages/cdk-appsync-typescript-resolver), we can now take advantage of static typing and code editor's IntelliSense & inlay hints to make Appsync's resolvers easy and efficient. Generation of types from GraphQL schema makes it even more efficient. And also, With `@aws-appsync/eslint-plugin` we can get the benefit of finding invalid syntax while writing code.

Hope this was helpful!