Unleashing Strongly Typed GraphQL with TypeScript in AppSync

Eric Bach
AMA Technology Blog
5 min readNov 20, 2023

In this blog post, we’ll dive into incorporating TypeScript with AWS AppSync JavaScript resolvers. As we’ve previously explored, AWS AppSync JavaScript resolvers greatly improves the development experience AppSync GraphQL APIs. TypeScript further enhances this experience by introducing static typing and advanced tooling capabilities, allowing developers to identify errors during compilation, write cleaner code, and construct robust applications.

We’ll also explore how to make use of graphql-codegen to automatically generate types from our GraphQL schema, integrate them into our AppSync TypeScript resolvers, transpile and bundle the code for the AppSync JavaScript runtime (APPSYNC_JS), and facilitate deployment using CDK — further streamlining the developer experience!

Photo by Kristian Strand on UnSplash

Whether you’re new to TypeScript or an experienced developer, we can use the power of AppSync resolvers with TypeScript to harness the benefits, setup process, and building strongly typed resolvers. By the end, we’ll understand the process and be equipped to build type-safe resolvers that enhance developer experience and application reliability. Let’s dive into the world of TypeScript and its seamless integration with AWS AppSync!

Project Structure

In order to transpile and bundle our TypeScript code for the APPSYNC_JS runtime, we will need to make use of esbuild. The code that we will generate will resemble a typical CDK app (cdk init app --language typescript) but it will have a couple extra folders for using TypeScript with AppSync:

  • graphql — where the TypeScript code will live for the AppSync resolvers
  • graphql/build —the transpiled and bundled AppSync resolvers for the APPSYNC_JS runtime
  • graphql/type — where the graphql-codegen auto-generate types will live
  • build-appsync.js — a custom script that runs esbuild to transpile our TypeScript resolvers to JavaScript
├── app
│ ├── bin
│ │ ├── cdk.ts
│ ├── graphql
│ │ ├── build // Auto-generated bundled APPSYNC_JS resolvers
│ │ ├── types
│ │ ├── ├──appsync.ts // Auto-generated AppSync types
│ │ ├── *.ts // AppSync Typscript Resolvers
│ │ ├── schema.graphql // AppSync GraphSQL schema
│ ├── helpers
│ │ ├── build-appsync.ts // Code to transpile TypeScript resolvers
│ ├── lib
│ │ ├── api-stack.ts
├── cdk.json
├── codegen.ts // codegen file to auto-generate AppSync types
├── packagejson

For more information about bundling TypeScript code for the APPSYNC_JS runtime, refer to the official AWS documentation here

Automatic Type Generation with Codegen

GraphQL Code Generator is a powerful tool that allows you to optimize your GraphQL stack by automating the generation of code and types. We’ll explore how to utilize GraphQL Code Generator to automatically generate types for your AppSync GraphQL schemas.

To get started, we need to install the necessary dependencies. Open your terminal and run the following command:

$ npm i -D @graphql-codegen/cli @graphql-codegen/typescript

Next, we’ll create a codegen.ts file to configure the code generation process. This file specifies the schema location, custom scalar types, and the output file path for the generated types. Here's an example configuration:

// codegen.ts
import { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = {
overwrite: true,
schema: [
'graphql/schema.graphql',
`
scalar AWSDate
scalar AWSTime
scalar AWSDateTime
scalar AWSTimestamp
scalar AWSEmail
scalar AWSJSON
scalar AWSURL
scalar AWSPhone
scalar AWSIPAddress
`,
],
config: {
scalars: {
AWSJSON: 'string',
AWSDate: 'string',
AWSTime: 'string',
AWSDateTime: 'string',
AWSTimestamp: 'number',
AWSEmail: 'string',
AWSURL: 'string',
AWSPhone: 'string',
AWSIPAddress: 'string',
},
},
generates: {
'graphql/types/AppSync.ts': {
plugins: ['typescript'],
},
},
};

export default config;

Once the configuration is set, we can add a script to our package.json file to run the codegen:

"scripts": {
"codegen": "graphql-codegen"
}

Now, execute the following command in your terminal to generate the types:

 npm run codegen

After running the codegen command, you’ll find a new file named graphql/types/appsync.ts in your project directory. This file contains the auto-generated types based on your AppSync GraphQL schema.

By automating the generation of types with GraphQL Code Generator, you can save time and ensure type safety within your AppSync applications. This approach streamlines development and the benefits of type safety and efficient code generation.

Create TypeScript resolvers

  1. Begin by importing the necessary dependencies and types for your resolver. Then write the resolver function to handle the request and the response.
// /app/graphql/Mutation.createItem.ts
import { AppSyncIdentityCognito, Context, DynamoDBPutItemRequest, util } from '@aws-appsync/utils';
import { Item, MutationCreateItemArgs } from './types/appsync';

export function request(ctx: Context<MutationCreateItemArgs>): DynamoDBPutItemRequest {
return {
operation: 'PutItem',
key: {
pk: util.dynamodb.toDynamoDB(`item#${util.autoId()}`),
createdAt: util.dynamodb.toDynamoDB(util.time.nowISO8601()),
},
attributeValues: {
userId: util.dynamodb.toDynamoDB((ctx.identity as AppSyncIdentityCognito).username),
name: util.dynamodb.toDynamoDB(ctx.args.input.name),
},
};
}

export function response(ctx: Context<MutationCreateItemArgs>): Item {
if (ctx.error) {
util.error(ctx.error.message, ctx.error.type, ctx.result);
}

return ctx.result;
}

2. Finally add the CDK code for the resolver function. Notice that we set the code path to be the bundled JavaScript file, Mutation.createItem.js, found in the graphql/build folder. We will configure this output directory with esbuild next.

// api-stack.ts
const createItemFunction = new AppsyncFunction(this, 'createItem', {
name: 'createItem',
api: api,
dataSource: dynamoDbDataSource,
code: Code.fromAsset(path.join(__dirname, '../graphql/build/Mutation.createItem.js')),
runtime: FunctionRuntime.JS_1_0_0,
});

Transpile and Bundle TypeScript Resolvers

  1. Create build-appsync.ts that runs esbuild for each TypeScript file found in the folder where we created our AppSync TypeScript resolvers (graphql/*.ts). The output of the command will be the graphql/build folder that we setup when defining the CDK code for the resolver function.
// helpers/build-appsync.ts
import { execSync } from 'child_process';
const glob = require('glob');

const runCommand = (command: string, message: string = '') => {
return execSync(command, { stdio: [process.stdin, process.stdout, process.stderr] });
};

const build = () => {
glob('../graphql/*.ts', function (err: Error, files: string[]) {
if (err) {
console.error('Error while expanding glob:', err);
return;
}

files.map((f) => {
runCommand(
`esbuild ${f} --bundle --sourcemap=inline --sources-content=false --platform=node --target=esnext --format=esm --external:@aws-appsync/utils --outdir=graphql/build`
);
});
});
};

build();

2. Next we will update the scripts in package.json to include a script to run this esbuild script.

"scripts": {
"build-appsync": "ts-node ./helpers/build-appsync.ts",
}

3. Finally, we will set the cdk.json file to include a reference to the script we just created to ensure we transpile and bundle our TypeScript resolvers on every CDK deploy.

"app": "npm run build-appsync && npx ts-node --prefer-ts-exts bin/cdk.ts",

4. To deploy our CDK stack we simply need to run cdk deploy

Summary

Using TypeScript to write AppSync resolvers offers developers the advantages of static typing and IntelliSense, eliminating the need for Lambda functions. By leveraging codegen, developers can automatically generate the specific types from the AppSync schema, enabling seamless integration with built-in AppSync types.

Additionally, with the assistance of esbuild, the code can be efficiently transpiled and bundled into a valid APPSYNC_JS runtime. This combination empowers developers to build type-safe, efficient, and well-integrated resolvers for their AppSync applications.

Eric Bach is a Senior Software Developer @Alberta Motor Association who enjoys learning, reading, and writing about leadership principles, event-driven microservices, and all things AWS.

--

--

Eric Bach
AMA Technology Blog

Senior Software Developer @ amaabca | AWS Certified x 2 | Domain Driven Design | Event Driven Architecture | CQRS | Microservices