DEV Community

Cover image for Mocking API endpoints with Amazon API Gateway Mock Integration
Wojciech Matuszewski
Wojciech Matuszewski

Posted on

Mocking API endpoints with Amazon API Gateway Mock Integration

Amazon API Gateway is a fully managed service for creating REST or WebSocket APIs that scale. The service integrates with many different AWS offerings enabling developers to build backends for their applications.

This blog post will look at one such integration, mainly the Mock Integration. The Mock Integration is excellent for prototyping APIs in a fast and efficient manner. It could also be used to unblock teams that depend on the API before the development is complete.

Code examples in this blog post are written in Typescript, and as my IaC of choice, I used AWS CDK.

Let us dive in.

Hypothetical scenario

The team you are working on is tasked with building a pets API. The business needs are well defined - the user can create a pet resource and list the pet resources he had created.

Your colleagues working on the application's backend need some time to agree on development practices and processes they will stick to during the development. But the frontend team had a head start. They are ready to start interacting with the API.

Since there are still many decisions to be made regarding the backend, you have decided to hack the "create a pet resource" endpoint using the API Gateway Mock Integration unblocking the frontend team in the process.

Anatomy of the Mock Integration

The beauty of the Mock Integration is that it does not interact with other AWS services like DynamoDB or AWS Lambda. This means that there are no IAM policies or table schemas to define.

The not-so-fun part of the integration are the exposed configuration options. They might seem vague or unnecessarily complicated at first sight. There is a lot of things you could do but most likely do not need to do.

Here is our starting point in terms of the code.

import * as cdk from "aws-cdk-lib";

const mockPetsAPI = new cdk.aws_apigateway.RestApi(this, "mockPetsAPI", {
  defaultCorsPreflightOptions: {
    allowOrigins: cdk.aws_apigateway.Cors.ALL_ORIGINS,
    allowMethods: cdk.aws_apigateway.Cors.ALL_METHODS
  }
});

mockPetsAPI.root.addMethod(
  "POST",
  new cdk.aws_apigateway.MockIntegration({
    passthroughBehavior: cdk.aws_apigateway.PassthroughBehavior.NEVER,
    requestTemplates: {
      /* ... */
    },
    integrationResponses: [
      /* ... */
    ]
  })
);
Enter fullscreen mode Exit fullscreen mode

The passthroughBehavior

This parameter is related to the requestTemplates parameter. It controls whether API Gateway will apply the transformations defined within the requestTemplates to the request and under what circumstances.

For our use case, I've specified NEVER as the passthroughBehavior. This means that the API Gateway will try to apply the transformations defined in the aforementioned requestTemplates section, no matter the request encoding. If we did not specify a request template for a given encoding, API Gateway would return 415 to the caller.

For an in-depth deep dive into different passthrough behaviors, please refer to the official AWS documentation.

The requestTemplates

This parameter defines key-value pairs where the key is the request encoding, like application/json, and the value is a mapping template used to transform the request.

Think of plucking only the necessary fields from the request body that will be passed to an AWS Lambda function so that you do not have to do any transformations within the Lambda code itself.

Since we are using a Mock Integration, the mapping template will be very concise.

requestTemplates: {
    "application/json": `{
        "statusCode": 201
    }`
}
Enter fullscreen mode Exit fullscreen mode

We are telling API Gateway that all requests sent to this endpoint should be handled with an integrationResponse defined for a statusCode of 201.
I assumed that all requests that the frontend team will make would be encoded using application/json scheme.

Let us talk about the integrationResponses next.

The integrationResponses

This parameter defines an array of responses that our Mock Integration can return. As I eluded earlier, the responses are tied to a statusCode defined in a given request mapping template.

In our case, we need to define only one integration response - for the 201 statusCode.

integrationResponses: [
  {
    statusCode: "201",
    responseTemplates: {
      "application/json": `{
            "id": "$context.requestId",
            "createdAt": $context.requestTimeEpoch,
            "updatedAt": $context.requestTimeEpoch
        }`
    }
  }
];
Enter fullscreen mode Exit fullscreen mode

Please reference the API Gateway mapping template documentation for more information regarding the $context variable.

With integrationResponses specified, our method configuration looks as follows.

mockAPI.root.addMethod(
  "POST",
  new cdk.aws_apigateway.MockIntegration({
    passthroughBehavior: cdk.aws_apigateway.PassthroughBehavior.NEVER,
    requestTemplates: {
      "application/json": `{
              "statusCode": 201
            }
          `
    },
    integrationResponses: [
      {
        statusCode: "201",
        responseTemplates: {
          "application/json": `{
                "id": "$context.requestId",
                "createdAt": $context.requestTimeEpoch,
                "updatedAt": $context.requestTimeEpoch
            }`
        }
      }
    ]
  })
);
Enter fullscreen mode Exit fullscreen mode

We still have some issues that we have to tackle. One of them being CORS.

CORS

Since the endpoint we are building is handling POST requests, the Mock Integration should return appropriate CORS headers. Otherwise, consumers of the endpoint will be able to make requests to it from the browser environment.

Adding CORS to the Mock Integration is a two-step process.

First, a method response needs to be configured for a given statusCode. Within the method response, we specify what kind of request parameters the integration response can populate (in our case, the parameters will correspond to CORS headers).

mockPetsAPI.root.addMethod(
  "POST",
  new cdk.aws_apigateway.MockIntegration({
    /* Previously defined parameters */
  }),
  {
    methodResponses: [
      {
        statusCode: "200",
        responseParameters: {
          "method.response.header.Access-Control-Allow-Origin": true,
          "method.response.header.Access-Control-Allow-Methods": true,
          "method.response.header.Access-Control-Allow-Headers": true
        }
      }
    ]
  }
);
Enter fullscreen mode Exit fullscreen mode

Similar to integrationResponses, the methodResponses is an array of method responses. The association between a given integration response and a method response is carried through the statusCode parameter.

Secondly, we need to amend our integration response to include the values for the CORS headers.

mockPetsAPI.root.addMethod(
  "POST",
  new cdk.aws_apigateway.MockIntegration({
    /* Previously defined parameters */
    integrationResponses: [
      {
        statusCode: "201",
        responseTemplates: {
          /* Previously defined template */
        },
        // Populating the CORS headers with specific values.
        responseParameters: {
          "method.response.header.Access-Control-Allow-Methods":
            "'OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD'",
          "method.response.header.Access-Control-Allow-Headers":
            "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'",
          "method.response.header.Access-Control-Allow-Origin": "'*'"
        }
      }
    ]
  }),
  {
    methodResponses: [
      {
        statusCode: "200",
        responseParameters: {
          "method.response.header.Access-Control-Allow-Origin": true,
          "method.response.header.Access-Control-Allow-Methods": true,
          "method.response.header.Access-Control-Allow-Headers": true
        }
      }
    ]
  }
);
Enter fullscreen mode Exit fullscreen mode

As you can see, I'm generous in terms of how open the CORS settings are. I would advise fine-tuning them according to your needs.

Working with the request body

So far, our API definition looks as follows.

Click to expand
import * as cdk from "aws-cdk-lib";

const mockPetsAPI = new cdk.aws_apigateway.RestApi(this, "mockPetsAPI", {
  defaultCorsPreflightOptions: {
    allowOrigins: cdk.aws_apigateway.Cors.ALL_ORIGINS,
    allowMethods: cdk.aws_apigateway.Cors.ALL_METHODS
  }
});

mockPetsAPI.root.addMethod(
  "POST",
  new cdk.aws_apigateway.MockIntegration({
    passthroughBehavior: cdk.aws_apigateway.PassthroughBehavior.NEVER,
    requestTemplates: {
      // For this particular request (in our case every request that is made to this endpoint)
      // respond with integration response defined for statusCode 201.
      "application/json": `{
            "statusCode": 201
        }`
    },
    integrationResponses: [
      {
        statusCode: "201",
        responseTemplates: {
          "application/json": `{
                "id": "$context.requestId",
                "createdAt": $context.requestTimeEpoch,
                "updatedAt": $context.requestTimeEpoch
            }`
        },
        // These definitions would not be possible without the definition within the `responseParameters` section.
        responseParameters: {
          "method.response.header.Access-Control-Allow-Methods":
            "'OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD'",
          "method.response.header.Access-Control-Allow-Headers":
            "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'",
          "method.response.header.Access-Control-Allow-Origin": "'*'"
        }
      }
    ]
  }),
  {
    methodResponses: [
      {
        // Everything defined in this section is applicable to the integration response with `statusCode` of 201.
        statusCode: "201",
        responseParameters: {
          // What kind of response parameters are allowed to be defined within the `responseParameters` section.
          "method.response.header.Access-Control-Allow-Origin": true,
          "method.response.header.Access-Control-Allow-Methods": true,
          "method.response.header.Access-Control-Allow-Headers": true
        }
      }
    ]
  }
);
Enter fullscreen mode Exit fullscreen mode

Notice that we are not using the request body in any way.

I could not find any information in the official documentation regarding how to return parts of the request body in the context of Mock Integration. Thankfully, a very helpful person presented a way how it could be done. Please refer to this post on StackOverflow.

The solution feels hacky because it is. Sadly the Mock Integration is limited in terms of manipulating and referencing the request body.

For full implementation, including returning parts of the body as a response to the POST request, you could refer to an example I have on GitHub.

GitHub logo WojciechMatuszewski / apigw-mockintegration-cognito

Example of creating an APIGW Mock Integration with Cognito authorizer.

APIGW Mock Integration with Cognito authorizer

Deployment

  1. cd backend
  2. npm run bootstrap
  3. npm run deploy
  4. Populate the frontend/index.tsx with the output values
  5. cd frontend
  6. npm start

Learnings

Cognito

  • The autoVerifiedAttributes really means automatically start verification for these attributes This means that whenever user signs up, the "here is your code" email will be send to his email inbox.

    Verification requires users to retrieve a code from their email or phone to confirm ownership. Verification of a phone or email is necessary to automatically confirm users and enable recovery from forgotten passwords. Learn more about email and phone verification.

    So to make sure the Cognito will NOT send an email to the user with the verification code, you should set the autoVerifiedAttributes to an empty array.

  • By default, the authorizer you can create (at least for the REST API) via cdk will honour the IdToken This is because the …

Summary

The API Gateway is very powerful. I've written this blog post because I struggled to understand how its pieces together. Learning about different transformations and integrations that are available to me definitely made me a better developer.

I hope that this guide will be helpful to you as writing it was helpful for me.

Questions? Want to reach out? Maybe something I wrote is not necessarily correct? (I'm happy to be corrected).

You can find me on Twitter - @wm_matuszewski

Thank you for your time.


Thumbnail by Sam Moqadam from unsplash

Discussion (0)