Starting Synchronous Express Workflows with API Gateway and CDK
Tags: cdk, aws, serverless, typescript
Published: 2020-12-19 14:59:18

---
title: Starting Synchronous Express Workflows with API Gateway and CDK
published: true
description: 
tags: cdk, aws, serverless, typescript
cover_image: https://i.imgur.com/0YggN2j.jpeg
---
Many things were announced before and during this year's AWS re:Invent, but one thing really caught my attention. Mainly the ability to invoke _Synchronous Express Workflows_ directly by using _API Gateway HTTP APIs_.

This is very exciting as it unlocks orchestration capabilities for synchronous invocations. As a developer, you no longer have to write logic to pool the results of your _Step Functions_ invocations.

While there are [many examples] (https://aws.amazon.com/blogs/compute/new-synchronous-express-workflows-for-aws-step-functions/) of implementing this workflow with [_AWS SAM_] (https://aws.amazon.com/serverless/sam/), I could not find any for [_AWS CDK_](https://aws.amazon.com/cdk/).

Since I'm more of an _AWS CDK_ guy nowadays, I decided to take on the challenge and write a simple implementation myself.

During the implementation, I encountered a few gotchas that caused me some trouble, so I decided to share the process I went through here in hopes of saving you a few minutes out of your day.

> This post assumes knowledge of _AWS CDK_. I will not be explaining the basics concepts around it.

## The state machine

Since this is an example, we will keep it as simple as possible. Our state machine will have one _Pass state_, which returns a static response.

```ts
const machineDefinition = new sfn.Pass(this, "passState", {
  result: { value: "Hi there!" }
});

const machine = new sfn.StateMachine(this, "machine", {
  definition: machineDefinition,
  stateMachineType: sfn.StateMachineType.EXPRESS
});
```

## The HTTP API

We need 2 things here—first, a very basic definition of an _AWS API Gateway HTTP APIs_ API.

```ts
const api = new apigwv2.HttpApi(this, "api");
```

Next, something specific to the architecture we are building – an _IAM role_ that will be assumed by the API. The role will grant the API we just created the ability to start synchronous execution of the state machine.

```ts
new iam.Role(this, "sfnRole", {
  assumedBy: new iam.ServicePrincipal("apigateway.amazonaws.com"),
  inlinePolicies: {
    AllowSFNExec: new iam.PolicyDocument({
      statements: [
        new iam.PolicyStatement({
          actions: ["states:StartSyncExecution"],
          effect: iam.Effect.ALLOW,
          resources: [machine.stateMachineArn]
        })
      ]
    })
  }
});
```

As a side-note, we create an _IAM role_ because the _AWS Step Functions_ service [does not support _Resource-based policies_](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-services-that-work-with-iam.html).

## The OpenAPI definition

As I eluded earlier, the integration does not require us to write _vtl_ code. What we need to do instead is to provide an OpenAPI definition containing _AWS API Gateway_ integration that wires everything together.

```ts
const definition = {
  openapi: "3.0.1",
  info: {
    title: "openapi-definition",
    version: "2020-12-19 12:06:00UTC"
  },
  paths: {
    "/": {
      post: {
        responses: {
          default: {
            description: "Default response for POST /"
          }
        },
        "x-amazon-apigateway-integration": {
          integrationSubtype: "StepFunctions-StartSyncExecution",
          credentials: apiRole.roleArn,
          requestParameters: {
            StateMachineArn: machine.stateMachineArn
          },
          payloadFormatVersion: "1.0",
          type: "aws_proxy",
          connectionType: "INTERNET",
          timeoutInMillis: 30000
        }
      }
    }
  },
  "x-amazon-apigateway-cors": {
    allowMethods: ["*"],
    maxAge: 0,
    allowCredentials: false,
    allowOrigins: ["*"]
  },
  "x-amazon-apigateway-importexport-version": "1.0"
};
```

Be very careful here. If you make a spelling mistake for any of the properties, the integration will not be created.

## The overrides

This is the part where I spent most of my time. As of writing this, the `HttpApi` construct does not support the `Body` parameter.
Since the `Body` is where our definition should be specified, we have to override that parameter manually.

```ts
const cfnApi = api.node.defaultChild as apigwv2.CfnApi;
cfnApi.addPropertyOverride("Body", definition);
```

If you try to deploy the stack now, your deployment will fail, with errors telling you that some properties of the `AWS::ApiGatewayV2::Api` resource are redundant. The `Name` and the `ProtocolType` are populated by the `HttpApi` construct, but should not be specified if the `Body` contains OpenAPI definition. Let us remove those.

```ts
cfnApi.addPropertyDeletionOverride("Name");
cfnApi.addPropertyDeletionOverride("ProtocolType");
```

## Summary

And that's it. This is all you need to deploy a very simple _API Gateway HTTP APIs_ API with a route that synchronously invokes an express workflow.

I hope you found it useful. Have a great day!