Tutorial: Build an API Gateway REST API with Lambda non-proxy integration
In this walkthrough, we use the API Gateway console to build an API that enables a client to call Lambda functions through the Lambda non-proxy integration (also known as 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 Tutorial: Create a Calc REST API with two AWS service integrations and one Lambda non-proxy integration.
Before creating the API, set up the Lambda backend by creating a Lambda function in AWS Lambda, described next.
Topics
Create a Lambda function for Lambda non-proxy 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
.
The Node.js implementation of this GetStartedLambdaIntegration
Lambda
function is as follows:
'use strict'; var days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; var times = ['morning', 'afternoon', 'evening', 'night', 'day']; console.log('Loading function'); export const 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], [
pattern. It is assumed that the input to the
Lambda function is of the following JSON object: name
|you] in
[city
|World]. Happy
day
!"
{ "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 an OpenAPI file
Compared to GetStartedLambdaProxyIntegration
, 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
Open the AWS Lambda console at https://console.aws.amazon.com/lambda/
. -
Do one of the following:
-
If the welcome page appears, choose Get Started Now and then choose Create function.
-
If the Lambda > Functions list page appears, choose Create function.
-
-
Choose Author from scratch.
-
In the Author from scratch pane, do the following:
-
For Name, enter
GetStartedLambdaIntegration
as the Lambda function name. -
For Execution role, choose Create a new role from AWS policy templates.
-
For Role name, enter a name for your role (for example,
GetStartedLambdaIntegrationRole
). -
For Policy templates, choose Simple microservice permissions.
-
Choose Create function.
-
-
In the Configure function pane, under Function code do the following:
-
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.
-
Choose Deploy.
-
-
To test the newly created function, choose Configure test events from Select a test event....
-
For Create new event, replace any default code statements with the following, enter
HelloWorldTest
for the event name, and choose Create.{ "name": "Jonny", "city": "Seattle", "time": "morning", "day": "Wednesday" }
-
Choose Test to invoke the function. The Execution result: succeeded section is shown. Expand Detail and you see the following output.
{ "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.
{ "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:
{ "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 Lambda non-proxy 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.
To create an API with Lambda custom integration with a Lambda function
-
Sign in to the API Gateway console at https://console.aws.amazon.com/apigateway
. -
If this is your first time using API Gateway, you see a page that introduces you to the features of the service. Under REST API, choose Build. When the Create Example API popup appears, choose OK.
If this is not your first time using API Gateway, choose Create API. Under REST API, choose Build.
Choose New API.
Enter a name in API Name.
Optionally, add a brief description in Description.
Choose Create API.
-
Choose the root resource (
/
) under Resources. From the Actions menu, choose Create Resource.-
Type
city
for Resource Name. -
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 Lambda function input using a mapping template. -
Select the Enable API Gateway Cors option.
-
Choose Create Resource.
-
-
With the newly created
/{city}
resource highlighted, choose Create Method from Actions.-
Choose
ANY
from the HTTP method drop-down menu. TheANY
HTTP verb is a placeholder for a valid HTTP method that a client submits at run time. This example shows thatANY
method can be used for Lambda custom integration as well as for Lambda proxy integration. -
To save the setting, choose the check mark.
-
-
In Method Execution, for the
/{city} ANY
method, do the following:-
Choose Lambda Function for
Integration type
. -
Leave the Use Lambda Proxy integration box unchecked.
-
Choose the region where you created the Lambda function; for example,
us-west-2
. -
Type the name of your Lambda function in Lambda Function; for example,
GetStartedLambdaIntegration
. -
Leave the Use Default timeout box checked.
-
Choose Save.
-
Choose OK in the Add Permission to Lambda Function popup to have API Gateway set up the required access permissions for the API to invoke the integrated Lambda function.
-
-
In this step you'll configure the following:
-
A query string parameter (
time
) -
A header parameter (
day
) -
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.
-
In the Method Execution pane, choose Method Request.
-
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. You can ignore the warning message to have a request validator. Leave Caching cleared to avoid an unnecessary charge for this exercise. -
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. You can ignore the warning message to have a request validator. Leave Caching cleared to avoid an unnecessary charge for this exercise. -
To define the method request payload, do the following:
-
To define a model, choose Models under the API from the API Gateway primary navigation pane, and then choose Create.
-
Type
GetStartedLambdaIntegrationUserInput
for Model name. -
Type
application/json
for Content type. -
Leave the Model description blank.
-
Copy the following schema definition into the Model schema editor:
{ "$schema": "http://json-schema.org/draft-04/schema#", "title": "GetStartedLambdaIntegrationUserInput", "type": "object", "properties": { "callerName": { "type": "string" } } }
-
Choose Create model to finish defining the input model.
-
Choose Resources, choose the
/{city} ANY
method, choose Method Request, and expand Request body. Choose Add model. Typeapplication/json
for Content type. ChooseGetStartedLambdaIntegrationUserInput
for Model name. Choose the check-mark icon to save the setting.
-
-
-
Choose the
/{city} ANY
method and choose Integration Request to set up a body-mapping template. In this step you'll map the previously configured method request parameter ofnameQuery
ornameHeader
to the JSON payload, as required by the backend Lambda function:-
Expand the Mapping Templates section. Choose Add mapping template. Type
application/json
for Content-Type. Choose the check-mark icon to save the setting. -
In the pop-up that appears, choose Yes, secure this integration.
-
Check the recommended
When there are no templates defined
for Request body passthrough. -
Choose
GetStartedLambdaIntegrationUserInput
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. -
Replace the generated mapping script in the mapping template editor with the following:
#set($inputRoot = $input.path('$')) { "city": "$input.params('city')", "time": "$input.params('time')", "day": "$input.params('day')", "name": "$inputRoot.callerName" }
-
Choose Save.
-
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:
POST /Seattle?time=morning day:Wednesday { "callerName": "John" }
In this test request, you'll set ANY
to POST
, set
{city}
to Seattle
, assign Wednesday
as the
day
header value, and assign "John"
as the
callerName
value.
To test-invoke the ANY /{city}
method
-
In Method Execution, choose Test.
-
Choose
POST
from the Method drop-down list. -
In Path, type
Seattle
. -
In Query Strings, type
time=morning
. -
In Headers, type
day:Wednesday
. -
In Request Body, type
{ "callerName":"John" }
. -
Choose Test.
-
Verify that the returned response payload is as follows:
{ "greeting": "Good morning, John of Seattle. Happy Wednesday!" }
-
You can also view the logs to examine how API Gateway processes the request and response.
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
-
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.
-
For Deployment stage, choose New Stage.
-
For Stage name, type a name; for example,
test
.Note
The input must be UTF-8 encoded (i.e., unlocalized) text.
-
For Stage description, type a description or leave it blank.
-
For Deployment description, type a description or leave it blank.
-
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://
. For example, the base URL of the API (api-id
.region
.amazonaws.com/stageName
beags1mnid
) created in theus-west-2
region and deployed to thetest
stage ishttps://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
To test the API using cURL
-
Open a terminal window on your local computer connected to the internet.
-
To test
POST /Seattle?time=evening
:Copy the following cURL command and paste it into the terminal window.
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:
{"greeting":"Good evening, John of Seattle. Happy Thursday!"}
If you change
POST
toPUT
in this method request, you get the same response.
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
Sign in to the AWS Management Console and open the AWS Lambda console at https://console.aws.amazon.com/lambda/
. -
From the list of functions, choose GetHelloWorld, choose Actions, and then choose Delete function. When prompted, choose Delete again.
-
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
Open the IAM console at https://console.aws.amazon.com/iam/
. -
From Details, choose Roles.
-
From the list of roles, choose APIGatewayLambdaExecRole, choose Role Actions, and then choose Delete Role. When prompted, choose Yes, Delete.
-
From Details, choose Policies.
-
From the list of policies, choose APIGatewayLambdaExecPolicy, choose Policy Actions, and then choose Delete. When prompted, choose Delete.