Menu
Amazon API Gateway
Developer Guide

Build an API Gateway API with Custom Lambda Integration

Note

The Lambda custom integration, formerly known as the Lambda integration, is a legacy technology. We recommend that you use the Lambda proxy integration for any new API. For more information, see Build an API Gateway API with Lambda Proxy Integration Using the API Gateway Console

In this walkthrough, we use the API Gateway console to build an API that enables a client to call Lambda functions through the Lambda custom integration. For more information about AWS Lambda and Lambda functions, see the AWS Lambda Developer Guide.

To facilitate learning, we chose a simple Lambda function with minimal API setup to walk you through the steps of building an API Gateway API with the Lambda custom integration. When necessary, we describe some of the logic. For a more detailed example of the Lambda custom integration, see Create an API for Lambda Functions.

Before creating the API, set up the Lambda backend by creating a Lambda function in AWS Lambda, described next.

Create a Lambda Function for the Lambda Custom Integration

Note

Creating Lambda functions may result in charges to your AWS account.

In this step, you create a "Hello, World!"-like Lambda function for the Lambda custom integration. Throughout this walkthrough, the function is called GetStartedLambdaIntegration. It is similar to GetStartedLambdaProxyIntegration, which is the function we created for the Lambda proxy integration.

The Node.js implementation of this GetStartedLambdaIntegration Lambda function is as follows:

Copy
'use strict'; var days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; var times = ['morning', 'afternoon', 'evening', 'night', 'day']; console.log('Loading function'); exports.handler = function(event, context, callback) { // Parse the input for the name, city, time and day property values let name = event.name === undefined ? 'you' : event.name; let city = event.city === undefined ? 'World' : event.city; let time = times.indexOf(event.time)<0 ? 'day' : event.time; let day = days.indexOf(event.day)<0 ? null : event.day; // Generate a greeting let greeting = 'Good ' + time + ', ' + name + ' of ' + city + '. '; if (day) greeting += 'Happy ' + day + '!'; // Log the greeting to CloudWatch console.log('Hello: ', greeting); // Return a greeting to the caller callback(null, { "greeting": greeting }); };

For the Lambda custom integration, API Gateway passes the input to the Lambda function from the client as the integration request body. The event object of the Lambda function handler is the input.

Our Lambda function is simple. It parses the input event object for the name, city, time, and day properties. It then returns a greeting, as a JSON object of {"message":greeting}, to the caller. The message is in the "Good [morning|afternoon|day], [name|you] in [city|World]. Happy day!" pattern. It is assumed that the input to the Lambda function is of the following JSON object:

Copy
{ "city": "...", "time": "...", "day": "...", "name" : "..." }

For more information, see the AWS Lambda Developer Guide.

In addition, the function logs its execution to Amazon CloudWatch by calling console.log(...). This is helpful for tracing calls when debugging the function. To allow the GetStartedLambdaIntegration function to log the call, set an IAM role with appropriate policies for the Lambda function to create the CloudWatch streams and add log entries to the streams. The Lambda console guides you through to create the required IAM roles and policies.

If you set up the API without using the API Gateway console, such as when importing an API from Swagger, you must explicitly create, if necessary, and set up an invocation role and policy for API Gateway to invoke the Lambda functions. For more information on how to set up Lambda invocation and execution roles for an API Gateway API, see Control Access to API Gateway with IAM Permissions.

Compared to GetStartedLambdaProxyIntegation, the Lambda function for the Lambda proxy integration, the GetStartedLambdaIntegration Lambda function for the Lambda custom integration only takes input from the API Gateway API integration request body. The function can return an output of any JSON object, a string, a number, a Boolean, or even a binary blob. The Lambda function for the Lambda proxy integration, in contrast, can take the input from any request data, but must return an output of a particular JSON object. The GetStartedLambdaIntegration function for the Lambda custom integration can have the API request parameters as input, provided that API Gateway maps the required API request parameters to the integration request body before forwarding the client request to the backend. For this to happen, the API developer must create a mapping template and configure it on the API method when creating the API.

Now, create the GetStartedLambdaIntegration Lambda function.

To create the GetStartedLambdaIntegration Lambda function for Lambda custom integration

  1. Open the AWS Lambda console at https://console.aws.amazon.com/lambda/.

  2. Do one of the following:

    • If the welcome page appears, choose Get Started Now and then choose Create a function.

    • If the Lambda > Functions list page appears, choose Create a function.

  3. From Select blueprint, choose Author from scratch.

  4. In the Configure triggers pane, choose Next.

  5. In the Configure function pane, do the following:

    1. Under Basic information:

      • For Name, type GetStartedLambdaIntegration as the Lambda function name.

      • For Description, type Backend for the Getting Started walkthrough with Lambda custom integration. This is optional and you can leave it blank.

      • For Runtime, choose Node.js 6.10.

    2. Under Lambda function code:

      • Choose Edit code inline, if it is not already shown, under Content entry type.

      • Copy the Lambda function code listed in the beginning of this section and paste it in the inline code editor.

      • Leave the default choices for all other fields in this section.

    3. Under Lambda function handler and role:

      • Leave the default of index.handler for Handler.

      • For Role, choose Create new role from template(s).

      • For Role name, type a name for your role (for example, GetStartedLambdaIntegrationRole).

      • For Policy templates, choose Simple Microservice permissions.

        Tip

        To use an existing IAM role, choose Choose an existing role for Role and then select an entry from the drop-down list of existing roles. Alternatively, to create a custom role, choose Create a Custom Role and follow the instructions.

    4. For Tags, leave them blank.

    5. For Advanced settings leave the defaults.

    6. Choose Next.

    7. Choose Create function. Note of the AWS Region where you created this function. You need it later.

  6. To test the newly created function, as a best practice, choose Actions and select Configure test event.

    1. For Input test event, replace any default code statements with the following, and then choose Save and test.

      Copy
      { "name": "Jonny", "city": "Seattle", "time": "morning", "day": "Wednesday" }
    2. Choose Test to invoke the function. The Execution result: succeeded section is shown. Expand Detail and you see the following output.

      Copy
      { "greeting": "Good morning, Jonny of Seattle. Happy Wednesday!" }

      The output is also written to CloudWatch Logs.

As a side exercise, you can use the IAM console to view the IAM role (GetStartedLambdaIntegrationRole) that was created as part of the Lambda function creation. Attached to this IAM role are two inline policies. One stipulates the most basic permissions for Lambda execution. It permits calling the CloudWatch CreateLogGroup for any CloudWatch resources of your account in the region where the Lambda function is created. This policy also allows creating the CloudWatch streams and logging events for the HelloWorldForLambdaIntegration Lambda function.

Copy
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "logs:CreateLogGroup", "Resource": "arn:aws:logs:region:account-id:*" }, { "Effect": "Allow", "Action": [ "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": [ "arn:aws:logs:region:account-id:log-group:/aws/lambda/GetStartedLambdaIntegration:*" ] } ] }

The other policy document applies to invoking another AWS service that is not used in this example. You can skip it for now.

Associated with the IAM role is a trusted entity, which is lambda.amazonaws.com. Here is the trust relationship:

Copy
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "lambda.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }

The combination of this trust relationship and the inline policy makes it possible for the Lambda function to invoke a console.log() function to log events to CloudWatch Logs.

If you did not use the AWS Management Console to create the Lambda function, you need to follow these examples to create the required IAM role and policies and then manually attach the role to your function.

Create an API with the Lambda Custom Integration

With the Lambda function (GetStartedLambdaIntegration) created and tested, you are ready to expose the function through an API Gateway API. For illustration purposes, we expose the Lambda function with a generic HTTP method. We use the request body, a URL path variable, a query string, and a header to receive required input data from the client. We turn on the API Gateway request validator for the API to ensure that all of the required data is properly defined and specified. We configure a mapping template for API Gateway to transform the client-supplied request data into the valid format as required by the backend Lambda function.

The API is named GetStartedLambdaIntegrationAPI.

To create an API with Lambda custom integration with a Lambda function

  1. Launch the API Gateway console.

  2. Choose Create new API.

    1. Type GetStartedLambdaIntegrationAPI for API name.

    2. Type a description of the API for Description or leave it blank.

    3. Choose Create API.

  3. Choose the root resource (/) under Resources. From the Actions menu, choose Create Resource.

    1. Type city for Resource Name.

    2. Replace Resource Path with {city}. This is an example of the templated path variable used to take input from the client. Later, we show how to map this path variable into the LAM function input using a mapping template.

    3. Select the Enable API Gateway Cors option.

    4. Choose Create Resource.

  4. With the newly created /{city} resource highlighted, choose Create Method from Actions.

    1. Choose ANY from the HTTP method drop-down menu. The ANY HTTP verb is a placeholder for a valid HTTP method that a client submits at run time. This example shows that ANY method can be used for Lambda custom integration as well as for Lambda proxy integration.

    2. To save the setting, choose the check mark.

  5. In Method Execution, for the ANY /{city} method, do the following:

    1. Choose Lambda Function for Integration type.

    2. Leave the Use Lambda Proxy integration box clear to use custom Lambda custom integration.

    3. Choose the region where you created the Lambda function; for example, us-west-2.

    4. Type the name of your Lambda function in Lambda Function; for example, GetStartedLambdaIntegration.

    5. Choose Save.

    6. Choose OK in Add Permission to Lambda Function to have API Gateway set up the required access permissions for the API to invoke the integrated Lambda function.

  6. In Method Execution, choose Method Request and configure as follows:

    • A query string parameter (time)

    • To set up a header parameter (day)

    • To define a payload property (callerName)

    At run time, the client can use these request parameters and the request body to provide time of the day, the day of the week, and the name of the caller. You already configured the /{city} path variable.

    1. Under Settings choose the pencil icon to choose Validate body, query string parameters, and headers from the Request Validator drop-down menu. This lets API Gateway perform basic request validation before forwarding the request to the Lambda function.

    2. Expand the URL Query String Parameters section. Choose Add query string. Type time for Name. Select the Required option and choose the check-mark icon to save the setting. Leave Caching cleared to avoid an unnecessary charge for this exercise.

    3. Expand the HTTP Request Headers section. Choose Add header. Type day for Name. Select the Required option and choose the check-mark icon to save the setting. Leave Caching cleared to avoid an unnecessary charge for this exercise.

    4. To define the method request payload, do the following:

      1. To define a model, choose Models under the API from the API Gateway primary navigation pane, and then choose Create.

      2. Type GetStartedLambdaIntegrationUserInput for Model name.

      3. Type application/json for Content type.

      4. Type a description for Model description or leave it blank.

      5. Copy the following schema definition to the Model schema editor:

        Copy
        { "$schema": "http://json-schema.org/draft-04/schema#", "title": "GetStartedLambdaIntegrationInputModel", "type": "object", "properties": { "callerName": { "type": "string" } } }
      6. Choose Save to finish defining the input model.

      7. Go back to Method Request and expand Request body. Choose Add model. Type application/json for Content type. Choose GetStartedLambdaIntegrationInput for Model name. Choose the check-mark icon to save the setting.

  7. In Method Execution for the ANY /{city} method, choose Integration Request to set up a body-mapping template. This maps the previously configured method request parameter of nameQuery or nameHeader to the JSON payload, as required by the backend Lambda function.

    1. Expand the Body Mapping Templates section. Choose Add mapping template. Type application/json for Content-Type. Choose the check-mark icon to save the setting.

    2. Type the following mapping template in the VTL script editor. Choose Save to finish the setup.

  8. Choose Integration Request to set up a mapping template to transform the client-supplied request data to the input format of the integrated Lambda function:

    1. Expand the Body mapping templates section.

    2. Check the recommended When there are no templates defined for Request body passthrough.

    3. Choose Add mapping template.

    4. Type application/json for Content-type.

    5. Choose the check-mark icon to save the setting.

    6. Choose GetStartedLambaIntegrationUserInput from Generate template to generate an initial mapping template. This option is available because you defined a model schema, without which you would need to write the mapping template from scratch.

    7. Modify the mapping script in the mapping template editor as follows:

      Copy
      #set($inputRoot = $input.path('$')) { "city": "$input.params('city')", "time": "$input.params('time')", "day": "$input.params('day')", "name": "$inputRoot.callerName" }

Test Invoking the API Method

The API Gateway console provides a testing facility for you to test invoking the API before it is deployed. You use the Test feature of the console to test the API by submitting the following request:

Copy
POST /Seattle?time=morning day:Wednesday { "callerName": "John" }

In this test request, you set ANY to POST, set {city} to Seattle, assign Wednesday as the day header value, and assign "John" as the callerName value.

To test invoking the ANY /{city} method

  1. In Method Execution, choose Test.

  2. Choose POST from the Method drop-down list.

  3. Type Seattle for the {city} path variable.

  4. Type morning for the day query string parameter.

  5. Type { "callerName":"John" } for Request Body.

  6. Choose Test.

  7. Verify that the returned response payload is as follows:

    Copy
    { "greeting": "Good morning, John of Seattle. Happy Wednesday!" }
  8. You can also view the logs to examine how API Gateway processes the request and response.

    Copy
    Execution log for request test-request Thu Aug 31 01:07:25 UTC 2017 : Starting execution for request: test-invoke-request Thu Aug 31 01:07:25 UTC 2017 : HTTP Method: POST, Resource Path: /Seattle Thu Aug 31 01:07:25 UTC 2017 : Method request path: {city=Seattle} Thu Aug 31 01:07:25 UTC 2017 : Method request query string: {time=morning} Thu Aug 31 01:07:25 UTC 2017 : Method request headers: {day=Wednesday} Thu Aug 31 01:07:25 UTC 2017 : Method request body before transformations: { "callerName": "John" } Thu Aug 31 01:07:25 UTC 2017 : Request validation succeeded for content type application/json Thu Aug 31 01:07:25 UTC 2017 : Endpoint request URI: https://lambda.us-west-2.amazonaws.com/2015-03-31/functions/arn:aws:lambda:us-west-2:123456789012:function:GetStartedLambdaIntegration/invocations Thu Aug 31 01:07:25 UTC 2017 : Endpoint request headers: {x-amzn-lambda-integration-tag=test-request, Authorization=****************************************************************************************************************************************************************************************************************************************************************************************************************************************338c72, X-Amz-Date=20170831T010725Z, x-amzn-apigateway-api-id=beags1mnid, X-Amz-Source-Arn=arn:aws:execute-api:us-west-2:123456789012:beags1mnid/null/POST/{city}, Accept=application/json, User-Agent=AmazonAPIGateway_beags1mnid, X-Amz-Security-Token=FQoDYXdzELL//////////wEaDMHGzEdEOT/VvGhabiK3AzgKrJw+3zLqJZG4PhOq12K6W21+QotY2rrZyOzqhLoiuRg3CAYNQ2eqgL5D54+63ey9bIdtwHGoyBdq8ecWxJK/YUnT2Rau0L9HCG5p7FC05h3IvwlFfvcidQNXeYvsKJTLXI05/yEnY3ttIAnpNYLOezD9Es8rBfyruHfJfOqextKlsC8DymCcqlGkig8qLKcZ0hWJWVwiPJiFgL7laabXs++ZhCa4hdZo4iqlG729DE4gaV1mJVdoAagIUwLMo+y4NxFDu0r7I0/EO5nYcCrppGVVBYiGk7H4T6sXuhTkbNNqVmXtV3ch5bOlh7 [TRUNCATED] Thu Aug 31 01:07:25 UTC 2017 : Endpoint request body after transformations: { "city": "Seattle", "time": "morning", "day": "Wednesday", "name" : "John" } Thu Aug 31 01:07:25 UTC 2017 : Sending request to https://lambda.us-west-2.amazonaws.com/2015-03-31/functions/arn:aws:lambda:us-west-2:123456789012:function:GetStartedLambdaIntegration/invocations Thu Aug 31 01:07:25 UTC 2017 : Received response. Integration latency: 328 ms Thu Aug 31 01:07:25 UTC 2017 : Endpoint response body before transformations: {"greeting":"Good morning, John of Seattle. Happy Wednesday!"} Thu Aug 31 01:07:25 UTC 2017 : Endpoint response headers: {x-amzn-Remapped-Content-Length=0, x-amzn-RequestId=c0475a28-8de8-11e7-8d3f-4183da788f0f, Connection=keep-alive, Content-Length=62, Date=Thu, 31 Aug 2017 01:07:25 GMT, X-Amzn-Trace-Id=root=1-59a7614d-373151b01b0713127e646635;sampled=0, Content-Type=application/json} Thu Aug 31 01:07:25 UTC 2017 : Method response body after transformations: {"greeting":"Good morning, John of Seattle. Happy Wednesday!"} Thu Aug 31 01:07:25 UTC 2017 : Method response headers: {X-Amzn-Trace-Id=sampled=0;root=1-59a7614d-373151b01b0713127e646635, Content-Type=application/json} Thu Aug 31 01:07:25 UTC 2017 : Successfully completed execution Thu Aug 31 01:07:25 UTC 2017 : Method completed with status: 200

    The logs show the incoming request before the mapping and the integration request after the mapping. When a test fails, the logs are useful for evaluating whether the original input is correct or the mapping template works correctly.

Deploy the API

The test invocation is a simulation and has limitations. For example, it bypasses any authorization mechanism enacted on the API. To test the API execution in real time, you must deploy the API first. To deploy an API, you create a stage to create a snapshot of the API at that time. The stage name also defines the base path after the API's default host name. The API's root resource is appended after the stage name. When you modify the API, you must redeploy it to a new or existing stage before the changes take effect.

To deploy the API to a stage

  1. Choose the API from the APIs pane or choose a resource or method from the Resources pane. Choose Deploy API from the Actions drop-down menu.

  2. For Deployment stage, choose New Stage.

  3. For Stage name, type a name; for example, test.

    Note

    The input must be UTF-8 encoded (i.e., unlocalized) text.

  4. For Stage description, type a description or leave it blank.

  5. For Deployment description, type a description or leave it blank.

  6. Choose Deploy. After the API is successfully deployed, you see the API's base URL (the default host name plus the stage name) displayed as Invoke URL at the top of the Stage Editor. The general pattern of this base URL is https://api-id.region.amazonaws.com/stageName. For example, the base URL of the API (beags1mnid) created in the us-west-2 region and deployed to the test stage is https://beags1mnid.execute-api.us-west-2.amazonaws.com/test.

Test the API in a Deployment Stage

There are several ways you can test a deployed API. For GET requests using only URL path variables or query string parameters, you can type the API resource URL in a browser. For other methods, you must use more advanced REST API testing utilities, such as POSTMAN or cURL.

To test the API using cURL

  1. Open a terminal window on your local computer connected to the internet.

  2. To test POST /Seattle?time=evening:

    Copy the following cURL command and paste it into the terminal window.

    Copy
    curl -v -X POST \ 'https://beags1mnid.execute-api.us-west-2.amazonaws.com/test/Seattle?time=evening' \ -H 'content-type: application/json' \ -H 'day: Thursday' \ -H 'x-amz-docs-region: us-west-2' \ -d '{ "callerName": "John" }'

    You should get a successful response with the following payload:

    Copy
    {"greeting":"Good evening, John of Seattle. Happy Thursday!"}

    If you change POST to PUT in this method request, you get the same response.

  3. To test GET /Boston?time=morning:

    Copy the following cURL command and paste it into the terminal window.

    Copy
    curl -X GET \ 'https://beags1mnid.execute-api.us-west-2.amazonaws.com/test/Boston?time=morning' \ -H 'content-type: application/json' \ -H 'day: Thursday' \ -H 'x-amz-docs-region: us-west-2' \ -d '{ "callerName": "John" }'

    You get a 400 Bad Request response with the following error message:

    Copy
    {"message": "Invalid request body"}

    This is because the GET request that you submitted cannot take a payload and fails the request validation.

Clean Up

If you no longer need the Lambda functions you created for this walkthrough, you can delete them now. You can also delete the accompanying IAM resources.

Warning

If you plan to complete the other walkthroughs in this series, do not delete the Lambda execution role or the Lambda invocation role. If you delete a Lambda function that your APIs rely on, those APIs will no longer work. Deleting a Lambda function cannot be undone. If you want to use the Lambda function again, you must re-create the function.

If you delete an IAM resource that a Lambda function relies on, that Lambda function will no longer work, and any APIs that rely on that function will no longer work. Deleting an IAM resource cannot be undone. If you want to use the IAM resource again, you must re-create the resource.

To delete the Lambda functions

  1. Sign in to the AWS Management Console and open the AWS Lambda console at https://console.aws.amazon.com/lambda/.

  2. From the list of functions, choose GetHelloWorld, choose Actions, and then choose Delete function. When prompted, choose Delete again.

  3. From the list of functions, choose GetHelloWithName, choose Actions, and then choose Delete function. When prompted, choose Delete again.

To delete the associated IAM resources

  1. Open the IAM console at https://console.aws.amazon.com/iam/.

  2. From Details, choose Roles.

  3. From the list of roles, choose APIGatewayLambdaExecRole, choose Role Actions, and then choose Delete Role. When prompted, choose Yes, Delete.

  4. From Details, choose Policies.

  5. From the list of policies, choose APIGatewayLambdaExecPolicy, choose Policy Actions, and then choose Delete. When prompted, choose Delete.

You have now reached the end of this walkthrough.