DynamoDB Cannot insert anything but Key - amazon-web-services

I am experimenting with DynamoDB to insert some data in a table test-table.
With postman and Lambda, I am trying to insert some values.
Currently, my table has a Key called node, and if I try to insert a new record doing the following in the body works:
{
"TableName": "test-table",
"Key": {"note":"test node"}
}
However, I want to add some new data to my record, for example:
{
"TableName": "test-table",
"Key": {"note":"test node"},
"Title": "This is a test"
}
But doing this does not save the second property Title, only the key get saved.
What am I missing here?
Here is my Lambda code that receives data with API Gateway:
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 {
switch (event.httpMethod) {
case 'DELETE':
body = await dynamo.delete(JSON.parse(event.body)).promise();
break;
case 'GET':
body = await dynamo.scan({ TableName: event.queryStringParameters.TableName }).promise();
break;
case 'POST':
body = await dynamo.put(JSON.parse(event.body)).promise();
break;
case 'PUT':
body = await dynamo.update(JSON.parse(event.body)).promise();
break;
default:
throw new Error(`Unsupported method "${event.httpMethod}"`);
}
} catch (err) {
statusCode = '400';
body = err.message;
} finally {
body = JSON.stringify(body);
}
return {
statusCode,
body,
headers,
};
};

When using the DynamoDB Document Client, your code should ultimately look like this:
const params = { "TableName": "test-table", "Item": { "note": "test", "title": "fred" }};
const rc = await dynamo.put(params).promise();
Note specifically that the top-level attributes in the params that you pass to the put() method are TableName and Item, rather than TableName and Key. So, be sure that your parsed HTTP body matches that.
Also, because your client (the web page) is actually supplying the table name to your Lambda function, be sure to secure this application from the Confused Deputy Problem where someone hacks the HTTP body to reference a different table, such as employees or students.

Related

lambda function returning null for deleting item in DynamoDB

hi Ive been trying to get my lambda function to delete an item in dynamo db but the function is simply returning null and i have no idea how to even start debugging it, hoping someone here has the knowledge to help
my table has guid as its primary partition key and username as its sort key
heres my code in .js
const AWS = require("aws-sdk");
// Initialising the DynamoDB SDK
const documentClient = new AWS.DynamoDB.DocumentClient();
exports.handler = async (event) => {
const { guid, username } = event
const params = {
TableName: "Items", // The name of your DynamoDB table
Key:{
"guid": {"S" : guid},
"username": {"S" : username}
}
};
try {
// Utilising the scan method to get all items in the table
documentClient.delete(params, function(err, data) {
if (err) {
return("Unable to delete item. Error JSON:", JSON.stringify(err, null, 2));
} else {
return("DeleteItem succeeded:", JSON.stringify(data, null, 2));
}
});
}
catch (e) {
return {
statusCode: 500,
body: e
};
}
};
this is the payload for the test event im using in lambda
{
"guid": "34",
"username": "newusername"
}
You are using async function handler. So your function probably just finishes before your code actually has a chance to execute.
You can overcome this issue by wrapping your code around new Promise as shown in the docs

Sending CORS headers from AWS Lambda to Gateway prevents my Lambda function from executing correctly

I'm slowly inching my way towards a rudimentary ability to use or understand AWS. I have a Gateway API set up to Post a string to a (Node 10.x)Lambda function that then gets sent to a Dynamo table. I've been having issues with CORS when trying to make API calls from webpage javascript, and found out it had something to do with the CORS in the handler in the Lambda function. Here is what I have right now:
const AWS = require('aws-sdk');
const db = new AWS.DynamoDB.DocumentClient({region: 'us-east-1'});
exports.handler = async function(event, context) {
var responseCode = 200;
var response = {
statusCode: responseCode,
headers: {
"Access-Control-Allow-Origin": "*"
},
body: JSON.stringify(event)
};
context.succeed(response);
console.log("Made it past context succeed");
var characterData = JSON.parse(event.body);
return await db.put(characterData).promise();
};
I run tests from the AWS API page just sending it strings to Post like
{
"TableName" : "characterTable",
"Item" : {
"userID" : "123",
"characterName" : "Alan",
"race" : "human"
}
}
When I comment out
context.succeed(response);
my function adds the data to the Dynamo table, but does not show the CORS headers in the Gateway log, meaning they weren't received, and if I try it on a webpage, the webpage will error telling me I'm missing the Access-Control-Allow-Origin header. If I leave that snippet in, the Gateway log shows the proper CORS header, and the webpage console moves past the Access-Control-Allow-Origin error onto a different error (missing token ‘content-type’ in CORS header but that's a problem for me in the future) but the data does not get passed onto the Dynamo table, even though the console.log statement right above it triggers properly.
I'm not sure how or why this is happening, so I would appreciate any insight into what might be wrong!
EDIT: Here is my webpage JS
//Default AWS sdk object
var lambda = new AWS.Lambda();
//api sdk stuff
var apigClient = apigClientFactory.newClient({
apiKey: 'iHadMyAPIKeyHere ' //placeholder for my actual API Key
});
function makeJSON(){
var userID = "";
var name = document.forms["characterForm"]["characterName"].value;
var race = document.forms["characterForm"]["race"].value;
var playerClass = document.forms["characterForm"]["class"].value;
var strength = document.forms["characterForm"]["strength"].value;
var dexterity = document.forms["characterForm"]["dexterity"].value;
var constitution = document.forms["characterForm"]["constitution"].value;
var intelligence = document.forms["characterForm"]["intelligence"].value;
var wisdom = document.forms["characterForm"]["wisdom"].value;
var charisma = document.forms["characterForm"]["charisma"].value;
characterSheetObj = {"userID": userID, "name": name, "race": race, "class": playerClass, "strength": strength, "dexterity": dexterity, "constitution": constitution, "intelligence": intelligence, "wisdom": wisdom, "charisma": charisma}
characterSheetJSON = JSON.stringify(characterSheetObj);
alert(characterSheetJSON);
var params = {
}
var body = {
"TableName" : "characterTable",
"Item" : {
"userID" : userID,
"name" : name,
"race" : race
}
}
var additionalParams = {
}
apigClient.myresourcePost(null, body);
}
Welcome to StackOverflow so you're problem is you need to use callback like so...
callback is called as the eventual result of the function, you want to return callback(....) to ensure your tests work properly later down line.
const AWS = require('aws-sdk');
const db = new AWS.DynamoDB.DocumentClient({region: 'us-east-1'});
exports.handler = async function(event, context, callback) {
var responseCode = 200;
var response = {
statusCode: responseCode,
headers: {
"Access-Control-Allow-Origin": "*"
},
body: JSON.stringify(event)
};
console.log("Made it past context succeed");
var characterData = JSON.parse(event.body);
await db.put(characterData).promise();
return callback(null, response)
};
NOTE Your dynamo DB call is in the wrong format and will generate an exception.
Context succeed is the old way, please see this answer for more info on returning proper http responses.

AWS lambda - how to use conditionals depending on if query parameters exist?

I want my function to return a list of everything in a table if there are no query parameters, and a single row if the parameter id exists
var mysql = require('mysql');
var config = require('./config.json');
var pool = mysql.createPool({
host : config.host,
user : config.user,
password : config.password,
database : config.database
});
exports.handler = (event, context, callback) => {
var whereClause
if(event.queryStringParameters.id !== null){
let id = event.queryStringParameters.id
whereClause = ' where id='+id
}
context.callbackWaitsForEmptyEventLoop = false;
pool.getConnection(function(err, connection) {
// Use the connection
connection.query('SELECT * from users'+whereClause, function (error, results, fields) {
// And done with the connection.
connection.release();
// Handle error after the release.
if (err) callback(err);
else {
var response = {
"statusCode": 200,
"headers": {
"my_header": "my_value"
},
"body": JSON.stringify(results),
"isBase64Encoded": false
};
callback(null, response);
}
});
});
};
the function fails when no query parameter is present with the error
"Cannot read property 'id' of null"
why is that?
You didn't supply any line number information or a stack trace, so I'm guessing this if statement fails because event.queryStringParameters is null:
if(event.queryStringParameters.id !== null)
let id = event.queryStringParameters.id
whereClause = ' where id='+id
}
And you should instead write:
if (event.queryStringParameters && event.queryStringParameters.id !== null) {
let id = event.queryStringParameters.id;
whereClause = ' where id=' + id;
}
Having said that, you should not inject user-supplied values (such as id) into SQL queries using string concatenation. This opens you up to a SQL Injection attack. Here are ideas for how to write this code more safely: How to prevent SQL Injection in Node.js
Do you use AWS Lambda with Amazon API Gateway?
AWS Lambda with Amazon API Gateway
In this case:
Make sure that you create a body mapping template in API gateway (Integration Request->Body Mapping Templates). As an example here's a body mapping template that would pass along the query parameter email to your lambda function: { "id": "$input.params('id')" }
AWS Developer Forum

Connect AWS mobile backend to DynamoDB

I am trying to use AWS mobile backend (using lambda function) to insert into dynamoDB (also configured at the mobile backend) but with no success so far.
The relevant code:
'use strict';
console.log("Loading function");
const AWS = require('aws-sdk');
const docClient = new AWS.DynamoDB.DocumentClient({region:process.env.MOBILE_HUB_PROJECT_REGION});
exports.handler = function(event, context, callback) {
var responseCode = 200;
var requestBody, pathParams, queryStringParams, headerParams, stage,
stageVariables, cognitoIdentityId, httpMethod, sourceIp, userAgent,
requestId, resourcePath;
console.log("request: " + JSON.stringify(event));
// Request Body
requestBody = event.body;
if (requestBody !== undefined && requestBody !== null) {
// Set 'test-status' field in the request to test sending a specific response status code (e.g., 503)
responseCode = JSON.parse(requestBody)['test-status'];
}
// Path Parameters
pathParams = event.path;
// Query String Parameters
queryStringParams = event.queryStringParameters;
// Header Parameters
headerParams = event.headers;
if (event.requestContext !== null && event.requestContext !== undefined) {
var requestContext = event.requestContext;
// API Gateway Stage
stage = requestContext.stage;
// Unique Request ID
requestId = requestContext.requestId;
// Resource Path
resourcePath = requestContext.resourcePath;
var identity = requestContext.identity;
// Amazon Cognito User Identity
cognitoIdentityId = identity.cognitoIdentityId;
// Source IP
sourceIp = identity.sourceIp;
// User-Agent
userAgent = identity.userAgent;
}
// API Gateway Stage Variables
stageVariables = event.stageVariables;
// HTTP Method (e.g., POST, GET, HEAD)
httpMethod = event.httpMethod;
// TODO: Put your application logic here...
let params = {
Item:{
"prop1":0,
"prop2":"text"
},
TableName:"testTable"
};
docClient.put(params, function(data, err){
if(err)
responseCode = 500;
else
{
responseCode = 200;
context.succeed(data);
}
});
// For demonstration purposes, we'll just echo these values back to the client
var responseBody = {
requestBody : requestBody,
pathParams : pathParams,
queryStringParams : queryStringParams,
headerParams : headerParams,
stage : stage,
stageVariables : stageVariables,
cognitoIdentityId : cognitoIdentityId,
httpMethod : httpMethod,
sourceIp : sourceIp,
userAgent : userAgent,
requestId : requestId,
resourcePath : resourcePath
};
var response = {
statusCode: responseCode,
headers: {
"x-custom-header" : "custom header value"
},
body: JSON.stringify(responseBody)
};
console.log("response: " + JSON.stringify(response))
context.succeed(response);
};
this doesn't put the item to the table for some reason.
I gave the necessary permissions using the roles part, anything I am missing?
**responseCode is only for testing purposes.
Edit:
tried AWS node.js lambda request dynamodb but no response (no err, no return data) and doesn't work either.
Edit2:
Added the full handler code. (it the default generated code when creating first AWS lambda).
I have refactored some bits of your code to look much simpler and use async/await (make sure to select Node 8.10 as the running environment for your function) instead of callbacks. I also got rid of the context and callback parameters, as they were used for older versions of NodeJS. Once you're using Node 8+, async/await should be the default option.
Also, it is possible to chain a .promise() on docClient.putItem, so you can easily await on it, making your code way simpler. I have left only the DynamoDB part (which is what is relevant to your question)
'use strict';
console.log("Loading function");
const AWS = require('aws-sdk');
const docClient = new AWS.DynamoDB.DocumentClient({region:process.env.MOBILE_HUB_PROJECT_REGION});
exports.handler = async (event) => {
let params = {
Item:{
"prop0":1,
"prop2":"text"
},
TableName:"testTable"
};
try {
await docClient.put(params).promise();
} catch (e) {
console.log(e)
return {
messsage: e.message
}
}
return { message: 'Data inserted successfully' };
};
Things to keep in mind if still it does not work:
Make sure your Lambda function has the right permissions to insert items on DynamoDB (AmazonDynamoDBFullAccess will do it)
You ALWAYS have to provide the partition key when inserting items to DynamoDB. On your example, the JSON only has two properties: prop1 and prop2. If none of them are the partition key, your code will certainly fail.
Make sure you table also exists
If you code fails, just check CloudWatch logs as any exception is now captured and printed out on the console.
The reason why no data is written in the table is because the call to DynamoDB put is asynchronous and will return by calling your callback. But during that time, the rest of the code continues to execute and your function eventually finish before the call to DynamoDB has a chance to complete.
You can use the await / async keywords to make your code sychronous :
async function writeToDynamoDB(params) {
return new Promise((resolve,reject) => {
docClient.put(params, function(data, err){
if(err)
reject(500);
else
resolve(data);
});
});
}
let params = ...
var data = await writeToDynamoDB(params)
You can find sample code I wrote (in Typescript) at https://github.com/sebsto/maxi80-alexa/blob/master/lambda/src/DDBController.ts

Getting an item from an Amazon DynamoDB table into a Lambda function intent node.js

I have created a table on Amazon DynamoDB called users with 2 items, userID and userName. Below is the code for my simple Lambda function. I have created an intent called userNameIntent where I want to be able to read the item userName from my table. So I want Alexa to respond with "Your username is " and then the userName from the table. Sorry for the newbie question, pretty new to coding and I can't seem to find a simple solution. I would appreciate your help. Thanks.
const Alexa = require('alexa-sdk');
var AWS = require('aws-sdk');
var DOC = require('dynamodb-doc');
var dynamodb = new AWS.DynamoDB.DocumentClient({region: 'eu-west-1'});
var params = {
TableName: 'users',
Key: {
"userID": "00001"
}
};
const handlers = {
'LaunchRequest': function () {
this.response.speak('Welcome');
this.emit(':responseReady');
},
'userNameIntent': function () {
this.response.speak('Your username is ');
this.emit(':responseReady');
}
};
exports.handler = function (event, context, callback) {
const alexa = Alexa.handler(event, context, callback);
alexa.APP_ID = APP_ID;
alexa.registerHandlers(handlers);
alexa.execute();
dynamodb.get(params, function(err, data) {
if (err){
callback ("error", null);
} else {
callback(null, data);
}
});
};
You are close but a few things:
1) the intent handler must process the response by passing the intent details (ie. slot values) to the DynamoDB query that will fetch the user info
2) the intent handler cannot return a response until the DynamoDB query is complete (i.e you must only set the response from the DynamoDB callback)
Have a look at my answer to a very similar question.