59

In this question How to pass a querystring or route parameter to AWS Lambda from Amazon API Gateway

shows how to map query string params to AWS lambda using API gateway. I would like to do the same but mapping POST values instead of query-string. I tried:

{
    "values": "$input.params()"
}

but did not work, I don't see the actual form data. BTW I am posting using:

application/x-www-form-urlencoded

I get my response from my lambda function, so I know it is invoking lambda fine, but my problem is that I don't see the POST params anywhere. I can;t figure out how to map them. I dump all I get on Lambda side and here it is:

 {"values":"{path={}, querystring={}, header={Accept=*/*, Accept-Encoding=gzip, deflate, Accept-Language=en-US,en;q=0.8, Cache-Control=no-cache, CloudFront-Forwarded-Proto=https, CloudFront-Is-Desktop-Viewer=true, CloudFront-Is-Mobile-Viewer=false, CloudFront-Is-SmartTV-Viewer=false, CloudFront-Is-Tablet-Viewer=false, CloudFront-Viewer-Country=US, Content-Type=application/x-www-form-urlencoded, Origin=chrome-extension://fhbjgbiflinjbdggehcddcbncdddomop, Postman-Token=7ba28698-8753-fcb1-1f48-66750ce12ade, Via=1.1 6ba5553fa41dafcdc0e74d152f3a7a75.cloudfront.net (CloudFront), X-Amz-Cf-Id=sc8C7dLcW0BHYopztNYrnddC0hXyFdBzHv0O9aWU1gKhd1D_J2HF3w==, X-Forwarded-For=50.196.93.57, 54.239.140.62, X-Forwarded-Port=443, X-Forwarded-Proto=https}}"}
Community
  • 1
  • 1
ecorvo
  • 3,559
  • 4
  • 24
  • 35
  • Take a look at this tutorial, I believe it will help - https://snowulf.com/2015/08/05/tutorial-aws-api-gateway-to-lambda-to-dynamodb/ – Stefan Aug 17 '15 at 18:29

13 Answers13

87

Good answer by r7kamura. Additionally Here's an example of an understandable and robust mapping template for application/x-www-form-urlencoded that works for all cases (assuming POST):

{
    "data": {
        #foreach( $token in $input.path('$').split('&') )
            #set( $keyVal = $token.split('=') )
            #set( $keyValSize = $keyVal.size() )
            #if( $keyValSize >= 1 )
                #set( $key = $util.urlDecode($keyVal[0]) )
                #if( $keyValSize >= 2 )
                    #set( $val = $util.urlDecode($keyVal[1]) )
                #else
                    #set( $val = '' )
                #end
                "$key": "$val"#if($foreach.hasNext),#end
            #end
        #end
    }
}

It would transform an input of

name=Marcus&email=email%40example.com&message=

into

{
    "data": {
                "name": "Marcus",
                "email": "email@example.com",
                "message": ""
    }
}

A Lambda handler could use it like this (this one returns all input data):

module.exports.handler = function(event, context, cb) {
  return cb(null, {
    data: event.data
  });
};
Marcus Whybrow
  • 19,578
  • 9
  • 70
  • 90
  • 4
    Man, not even a single upvote, this works beautifully for me, thanks! – ObjectiveTruth Jul 23 '16 at 07:23
  • 1
    worked perfectly. Pulled my hair in last couple of days, trying to load facebook canvas app using aws. Nice job @Marcus – Rukshan Dangalla Sep 26 '16 at 16:27
  • Hello! I'm trying to use this solution for and c# Dot.Net Core Lambda integretion so far the mapping lets the request pass through to the lambda. But my Body is always null. I've tried building a model that fits with the parameter of the form but to no avail. If anyone succeeded using this solution with c# any help would be apreciated. – Lorien Jun 18 '17 at 16:50
  • Why do we need `#if($foreach.hasNext),#end`? – Vitaly Zdanevich May 06 '18 at 14:19
  • @Marcus Whybrow, How to integrate this to SAM? – Sabreena Apr 11 '19 at 10:25
  • @VitalyZdanevich that's to determine if a comma should be added or not – jgreen Jul 25 '19 at 20:34
  • A major disadvantage of this template is that we can't have multiple values per key. For example ```key1=first&key1=second&key2=third``` gives ```{ "data": { "key1": "second", "key2": "third" } }```. Better let Lambda handle parsing if an array of multiple values is needed. **querystring** module in NodeJs does this in a neat manner ```var qs = require('querystring') exports.handler = async (event) => { return { statusCode: 200, body: JSON.stringify({ "body": qs.parse(event.body) }) }; };``` – secretshardul Jan 31 '20 at 11:00
  • 1
    bless you. was stuck with this for 2 days. reached here via github link somehow. Thanks lot – Ankush Feb 10 '21 at 17:40
  • 1
    Sir, you save my life. – whck6 May 18 '23 at 02:06
23

If you enable Lambda Proxy Integration enter image description here

The POST body will be available from:

event['body']['param']

GET parameters and headers will also be available via

event['pathParameters']['param1']
event["queryStringParameters"]['queryparam1']
event['requestContext']['identity']['userAgent']
event['requestContext']['identity']['sourceIP']
Jonathan
  • 10,792
  • 5
  • 65
  • 85
  • 2
    `event.body` returns `id="some"`, but not able to get `id` by `event.body.id` or they way you've written. – Nobody Oct 29 '19 at 10:49
  • 1
    event['body'] (and others) throws KeyError, @Jonathan could you please elaborate a bit how this works? I'm trying to pass a variable from the URL to the lambda handler, which would then use that variable to fill in an URL that I want to make a requests.get to. – KMFR Dec 18 '19 at 09:30
22

You can convert any request body data into valid JSON format by configuring the mapping templates in the integration settings so that AWS Lambda can receive it.

Currently it seems Amazon API Gateway does not support application/x-www-form-urlencoded officially yet, but avilewin posted a solution to do that on the AWS forums. In the mapping templates you can use Velocity Template Language (VTL), so what you need to do is to configure mapping templates that convert application/x-www-form-urlencoded format into valid JSON format. Of course this is a dirty solution, but I think it's the only way to do that for now.

Steffen Opel
  • 63,899
  • 11
  • 192
  • 211
r7kamura
  • 416
  • 2
  • 5
11

You can convert the params into JSON with a API gateway template: https://forums.aws.amazon.com/thread.jspa?messageID=673012&tstart=0#673012

Or you may do this in the lambda function itself using QueryString parser pacakge: https://www.npmjs.com/package/qs

var qs = require('qs');
var obj = qs.parse('a=c');  // { a: 'c' } 

If Amazon adds built-in support for such feature, I will use that but until then I personally prefer the second way because it's cleaner and easier to debug if something goes wrong.

Update July 2017:

You may use proxy integration which supports it by default: http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-lambda.html

advncd
  • 3,787
  • 1
  • 25
  • 31
8

Extending @markus-whybrow answer:

{
        #foreach( $token in $input.path('$').split('&') )
            #set( $keyVal = $token.split('=') )
            #set( $keyValSize = $keyVal.size() )
            #if( $keyValSize >= 1 )
                #set( $key = $util.urlDecode($keyVal[0]) )
                #if( $keyValSize >= 2 )
                    #set( $val = $util.urlDecode($keyVal[1]) )
                #else
                    #set( $val = '' )
                #end
                "$key": "$util.escapeJavaScript($val)"#if($foreach.hasNext),#end
            #end
        #end
    }

This gets rid of "data" and also fixes a case if you have a double-quote in one of your values.

Mark Sergienko
  • 181
  • 1
  • 4
  • 1
    what if you have array params? This will end up in the lambda function being a string key like: { key1: 'value1', key2: value2', 'array_key[][something]': 'some array value' }. We have to do some additional parsing in this case right? – Dianna Jan 07 '19 at 13:26
  • @Dianna did you find the solution to this problem? – secretshardul Jan 31 '20 at 11:21
  • 1
    @real_shardul i guess I did, but I wouldn't be able to tell you what was the solution because: 1. I don't remember it; 2. I don't have access to that lambda function and aws account anymore. A lot happened in 1 year :) – Dianna Jan 31 '20 at 12:26
5

I found a really simple solution I think it is worth to share, since it took me a few time to find this minimal working code.

If you have a common form, that send data with content type application/x-www-form-urlencoded, just flag "Lambda proxy integration", then you will find encoded form data in event.body which can be parsed with Node.js querystring native module.

const querystring = require('querystring')

function handler (event, context, callback) {
  // Read form data.
  const { user, pass } = querystring.parse(event.body)

  // Follows your code to handle request.
}
Gianluca Casati
  • 3,303
  • 1
  • 32
  • 20
5

June 2021

Mapping template for application/x-www-form-urlencoded for POST: (Python)

from base64 import b64decode
from urllib.parse import parse_qs

def lambda_handler(event, context):
    params = parse_qs(b64decode(event.get('body')).decode('utf-8'))
    print(params.get('name')[0])
    print(params.get('email')[0])

Similarly, do whatever operations you want to perform using params dictionary and whatever is the parameter name.

  • This is the correct answer. Nowadays the API Gateway delivers the `application/x-www-form-urlencoded` POST body as a base64 encoded string. No need for mapping. – gtourkas Jul 31 '23 at 06:48
4

I implemented an API with POST requests where Content-Type was application/x-www-form-urlencoded. If you're just interested in getting a long querystring of values similar to GET requests, then use this mapping syntax.

{
    "body": "$input.body"
}

Note, you can add other mappings too... I left that out to address the original problem.

Here's a blog tutorial that I used when originally implementing my API. My lambda function then parses the querystring passes the data onto other processes.

openwonk
  • 14,023
  • 7
  • 43
  • 39
3

This works with lambda integration. Assuming your POST request body is e.g.

{
   "name" : "Hello",
   "address" : "Cool place" 
}

You can access it like so :

if (event.body !== null && event.body !== undefined) {
   let body = JSON.parse(event.body)
   let name = body.name;
   let address = body.address;
}

More info here : http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-lambda.html

Dodi
  • 2,201
  • 4
  • 30
  • 39
2

If you want to send to your Lambda function all the body from POST, write this at Integration Request:

{
    "values": $input.json('$')
}

If you want to build your own structure from the body, do this:

{
    "values": {
        "body-param1": $input.json('body-param1'),
        "others": {
            "body-param2": "$input.json('body-param2')",
        }
    }
}

Where body-param1 is a number and body-param2 is a string.

If your want to send the headers, do this:

{
    "headers": {
        #foreach($param in $input.params().header.keySet())
            "$param": "$util.escapeJavaScript($input.params().header.get($param))" #if($foreach.hasNext), #end
        #end
    }
}
Camilo Ortegón
  • 3,414
  • 3
  • 26
  • 34
1

Simple function that returns ordered dictionary of posted values:

import urllib
from collections import OrderedDict
postdata = ''

def GetPostData(body):
    #postdata = OrderedDict()
    postdata = {}
    for items in body.split('&'):
        vals = items.split('=')
        postdata[vals[0]] = urllib.parse.unquote(vals[1])
    return postdata

#Testing it out:
#Assume you get body from event['body'] or body = event.get['body']
body = 'text=This%20works%20really%20well%21%245123%21%403%2146t5%40%2341&anotherkey=isawesome23412%201%21%403%21%40312'

postdata = GetPostData(body)
print(postdata['text'])
#returns 'This works really well!$5123!@3!46t5@#41'
print(postdata['anotherkey'])
#returns 'isawesome23412 1!@3!@312'

To avoid a key error when a posted items is missing, you should use value = postdata.get('') instead, as value will be None if the key is not present.

Vinnie Amir
  • 557
  • 5
  • 10
1

Updated (2022) NodeJS full example:

invoking:

curl -H "Content-Type:application/x-www-form-urlencoded" http://lambda_baseurl?aaa=1&bbb=2&ccc=3

Lambda code:

const {URLSearchParams} = require('node:url')

exports.handler = async (event, context) => {
    const jsonBody = Object.fromEntries(new URLSearchParams(Buffer.from(event.body, 'base64').toString()));
    console.log(JSON.stringify(jsonBody)) // prints: {aaa: 1, bbb: 2, ccc: 3}
}
Mercury
  • 7,430
  • 3
  • 42
  • 54
0

This answer is late to the thread, but I found a solution for this that works. In the API Gateway, add a POST method to a resource. In the Integration Request, choose Lambda Function and Lambda Proxy.

Your JSON from the Request Body section of the test will be passed directly to the function, which you can read and return back like so:

const AWS = require('aws-sdk');

const dynamo = new AWS.DynamoDB.DocumentClient();

exports.handler = async (event, context) => {
    console.log('Received event:', JSON.stringify(event, null, 2));

    let body;
    let statusCode = '200';
    const headers = {
        'Content-Type': 'application/json',
    };

    try {
        body = JSON.parse(event.body);
    } catch (err) {
        statusCode = '400';
        body = err.message;
    } finally {
        body = JSON.stringify(body);
    }

    return {
        statusCode,
        body,
        headers,
    };
};
w7a
  • 61
  • 6