AWS API Gateway: Request Data

Intro

Now that we have learned how to create and deploy an API as well as connect it’s routes to Lambda, let us cover how to include meaningful data in our requests and responses.

Resources

πŸ“˜ AWS Docs: Lambda function handler in Node.js

πŸ“˜ AWS Docs: Setting up data transformations for REST APIs

πŸ“˜ Wikipedia: Apache Velocity

πŸ“˜ velocity.apache.org : User Guide

πŸ“˜ AWS Docs: $input Variables

Viewing Request Data

Currently when we make a request to our API our Lambda function is returning a simple string.

index.js
exports.handler = (event, context, callback) => {
  // callback(err, response)
  callback(null, { message: "Lambda speaking..." })
}

If you are confused by this handler function go back and review the AWS Lambda Intro or read the official documentation on Lambda Node.js handler functions here:

πŸ“˜ AWS Docs: Lambda function handler in Node.js

In a nutshell the event argument is the data we receive in the request, context is contextual information about the request, and callback is what our Lambda function sends back when our function loop is completed. Lastly callback itself takes two arguments. The first is error second is response. We are ignoring errors for now so we have just placed null in the place of that argument.

The callback is currently just sending back a static text message. Let us now update this to simply return the event right back to us so that we can see what Lambda is receiving from our request. Like a boomerang!

index.js
exports.handler = (event, context, callback) => {
  // callback(err, response)
  callback(null, event)
}

And then if we go into Postman and make a post request to our endpoint, and include in the body of the request a JSON object, we can see that we receive that object right back.

request sent back

Modify Response

The reason that we received the request back in the exact same format that we sent it is because we do not have any code in our API that modifies the request before sending it back. There are two potential places where we would modify this response.

If we wanted to modify the data before it reached Lambda we would do that in Integration Request and if we wanted to modify after it left Lambda we would do that in Integration Response.

modification locations

We are going to cover the process of modifying the data in both locations.

Integration Request

Lambda Proxy Integration

To modify the incoming request data you would navigate to Integration Request > Body Mapping Templates, where you will be able to select a mapping template, and in addition select a behavior for requests that don’t match a saved template.

However before we go there let us briefly discuss Lambda Proxy Integration. This is the option that we would select if we wanted to forward the entire request to Lambda. By entire request we mean both the headers as well as the body.

lambda proxy integration

And if we select that option we can see that we no longer have the ability to modify the response using Integration Response

integration response inactive

And we will notice something else happening here. If we test this endpoint again in postman we will get the following.

internal error

We get an internal error. So what is the cause of this error? Because we are now returning the entire request as a response, including the headers, we have an issue. We are returning a request as a response. This causes an error with API Gateway because it does not fit the schema of a response, and therefore gets blocked.

In addition the Integration Response portion of our resource was in charge of handling our CORS headers.

Ncoughlin: Cross Origin Resource Sharing (CORS)

Therefore to get a response back using Lambda Proxy Integration we need to add a CORS header to our response manually.

index.js
exports.handler = (event, context, callback) => {
  // callback(err, response)
  callback(null, { headers: { "Control-Access-Allow-Origin": "*" } })
}

Which then allows our request to give a response, as we are no longer

  • Sending a request as a response
  • Missing CORS headers

CORS header in response

There is however an argument to be made that doing all of this in Lambda defeats the purpose of using API Gateway. We want to have a separation of concerns where API Gateway is handling all of the API concerns. So let us return now to letting API Gateway handle this.

Body Mapping Templates

Now we have disabled Lambda Proxy Integration and we can again use Integration Request + Integration Response. Let us also update the body of our test HTTP POST request that we are sending to the following:

POST_Request
{
    "personData": {
        "Name": "Nick Coughlin",
        "Age": "33"
    }
}

and if we update our Lambda function where the request is being sent to the following:

index.js
exports.handler = (event, context, callback) => {
  console.log(event)
  const age = event.personData.Age  // callback(err, response)
  callback(null, age * 2)
}

We can see that we have created a variable where we extract the age from the request, and then return the age doubled. We can then test the request.

66 response

And we have successfully received back 66 as the response.

Our goal here however is to map out the data from the body before we get to Lambda. That is where our body mapping templates come in. There is a whole mini language involved here. The docs on this are available here.

πŸ“˜ AWS Docs: Setting up data transformations for REST APIs

Let us start with some examples however. If we go into Integration Request > Mapping Templates

mapping template

And we start with a very simple mapping template where we define age

mapping_template
{
"age" : $input.json('$.personData.Age')
}

and then we can update our Lambda function to the following:

index.js
exports.handler = (event, context, callback) => {
  console.log(event)
  const age = event.age  // callback(err, response)
  callback(null, age / 2)}

We can see that we no longer have to reference personData because we have already mapped the value of age before the data even got to Lambda. We can also check our console log that we requested to verify the data that was contained in event.

We send a request with age = 100

age 100 request

and our cloud watch log shows that the only data in the event was that age

cloudwatch log shows only age

Mapping Template Syntax

This all seems clear except for one part. The syntax of the Body Mapping Templates. That’s because this is an entirely new language called Apache Velocity.

πŸ“˜ Wikipedia: Apache Velocity

πŸ“˜ velocity.apache.org : User Guide

There is a lot of ground to cover with velocity, but let us break down the basics here. Here again is our example from above.

mapping_template
{
"age" : $input.json('$.personData.Age')
}

$input refers to the request data. It is a variable reserved by AWS which gives you access to the input payload (request, body, params, headers) of your request.

πŸ“˜ AWS Docs: $input Variables

.json() method extracts a piece of data from the request data.

$ refers to the request body. To everything in the request body.

And from there we drill down into the object key that we wanted to select.

Another Mapping Example

So to end this section let us look at one final example. If we have the following Post Request:

POST_Request
{
    "personData": {
        "Name": "Nick Coughlin",
        "Age": 100
    }
}

The following Mapping Template

mapping_template
{
  "name" : $input.json('$.personData.Name'),
  "age" : $input.json('$.personData.Age')
}

And the following Lambda Function

index.js
exports.handler = (event, context, callback) => {
    console.log(event);
    
    const myAge = (event.age)/2;
    const myName = event.name;
    
    const response = [myName,myAge]
    
    // callback(err, response)
    callback(null, response);
};

Some things to note here are that we sent the age as a number instead of a string. We also used a helper function in Lambda to convert our two items into an array, and we returned that array.

[
    "Nick Coughlin",
    50
]

Integration Response

Now that we understand all of the above, the Integration Response is quite simple. We create a mapping template the exact same way that we do for the data coming in, except now we are creating a mapping template to shape the data coming out of Lambda (or whatever action you have).

We will still use $input in the mapping template, however now it refers to the input coming back from the action (Lambda).

Next we will discuss how to use these models to validate requests.