Having difficulty with DynamoDB query - javascript

I'm trying to wrap my head around AWS DynamoDB and having trouble getting records.
If my db item looks like this:
{
"id": string (primary)
"identifier": string
"project": string
}
I'm trying to get a record using both identifier and project:
Right now I'm doing that like this:
const params = {
TableName: 'MY_TABLE',
ExpressionAttributeValues: {
':identifier': { S : 'Dave' },
':project': { S : 'red_squad' },
},
KeyConditionExpression: 'identifier = :identifier and project = :project'
}
docClient
.query(
params,
(err, data) => console.log(err || data)
)
However it's telling me that it needs the primary key, however I that's not suitable for my use case.

Use a scan, not a query
https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#scan-property
const params = {
TableName: 'MY_TABLE',
ExpressionAttributeValues: {
':identifier': { S : 'Dave' },
':project': { S : 'red_squad' },
},
FilterExpression: 'identifier = :identifier and project = :project'
}
docClient
.scan(
params,
(err, data) => console.log(err || data)
)

Related

How to Update a specific Item in an array nested in a document

Hi I am building an RESTful API in Node using mongoose to manage data on a practice food delivery site I am building.
I want to setup a patch route that will remove an order Item from my items array nested in my Orders document based on a request from the user identifying the specific item with a name or ID.
I have a patch route which pushes a new order item into the Items Array nested in the Orders document, I want this patch route to also be able to remove a specific Item from the array based on a prop such as name or ID
I have tried using the Update and UpdateOne methods and I think I'm just getting the syntax wrong or something as I keep getting errors.
Server.js:
require("dotenv").config()
const express = require("express");
const mongoose = require("mongoose");
const app = express();
mongoose.connect(process.env.DATABASE_URL)
const db = mongoose.connection
db.on("error", () => console.error(error))
db.once("open", () => console.log("connected to database"))
app.use(express.json())
const subscribersRouter = require("./routes/subscribers")
const suscribersLoginRouter = require ("./routes/login")
const restaurantsRouter = require("./routes/restaurants")
const ordersRouter = require("./routes/orders")
app.use("/subscribers", subscribersRouter)
app.use("/login", suscribersLoginRouter)
app.use("/restaurants", restaurantsRouter)
app.use("/orders", ordersRouter)
app.listen(3000, () => {
console.log("Server has started on port 3000")
});
Order Model:
const mongoose = require("mongoose")
const orderSchema = new mongoose.Schema({
userID: {
type: String,
required: true
},
total: {
type: Number,
required: true
},
items: {
type: Array,
required: true
}
})
module.exports = mongoose.model("order", orderSchema)
Orders Route (you will see here that I have a patch route which pushes a new order item into the Items Array nested in the Orders document, I want this patch route to also be able to remove a specific Item from the array based on a prop such as name or ID, the issue I am have is 1. How to create an if statement that gets the update of the item to be triggered and the the code id use in that if statement to actually update that Item)
const express = require("express")
const router = express.Router()
const Order = require("../models/order")
// Getting All
router.get("/", async (req, res) => {
try {
const orders = await Order.find()
res.json(orders)
} catch (err) {
res.status(500).json({
message: err.message
})
}
})
// Getting One
router.get("/:id", getOrder, (req, res) => {
res.json(res.order)
})
// Creating One
router.post("/", async (req, res) => {
const order = new Order({
userID: req.body.userID,
total: req.body.total,
items: req.body.items
})
try {
console.log(order)
const newOrder = await order.save()
res.status(201).json(newOrder)
} catch (err) {
res.status(400).json({
message: err.message
})
}
})
// Updating One
router.patch("/:id", getOrder, async (req, res) => {
if (req.body.userID != null) {
res.order.userID = req.body.userID
}
if (req.body.total != null) {
res.order.total = req.body.total
}
if (req.body.items != null) {
const currentItems = res.order.items
const newItem = req.body.items
currentItems.push(newItem)
}
try {
const updatedItems = await res.order.save()
res.json(updatedItems)
} catch (err) {
res.status(400).json({
message: err.message
})
}
})
// Deleting One
router.delete("/:id", getOrder, async (req, res) => {
try {
await res.order.remove()
res.json({
message: "Deleted Order"
})
} catch (err) {
res.status(500).json({
message: err.message
})
}
})
async function getOrder(req, res, next) {
let order
try {
order = await Order.findById(req.params.id)
if (order === null) {
return res.status(404).json({
message: "Cannot Find Order"
})
}
} catch (err) {
return res.status(500).json({
message: err.message
})
}
res.order = order
next()
}
module.exports = router
TEST Requests:
# ORDERS
# Get All
GET http://localhost:3000/orders
###
#Get One
GET http://localhost:3000/orders/627fe8e575a8229d0ae81e73
###
#Create One
POST http://localhost:3000/orders
Content-Type: application/json
{
"userID": "627f8b476fa64425928750c9",
"total":50,
"items": [
{
"name": "Burder",
"price": "R20",
"description": "A good Fuggen Waffel"
},
{
"name": "Hotdog",
"price": "R20",
"description": "A good Fuggen Waffel"
},
{
"name": "Bunny Chow",
"price": "R20",
"description": "A good Fuggen Waffel"
},
{
"name": "Pizza",
"price": "R20",
"description": "A good Fuggen Waffel"
}
]
}
###
#Delete One or all
DELETE http://localhost:3000/orders/628202c3b208aebc7f7f8f98
###
# Update on (add Order Item)
PATCH http://localhost:3000/orders/628202c3b208aebc7f7f8f98
Content-Type: application/json
{
"items": {
"name": "gravy",
"price": "R20",
"description": "A good Fuggen Waffel"
}
}
###
I'm not sure I understood you correctly. I understood that you need the PATCH route to also delete an item from the items array by name.
So here is my solution to it:
Because you already fetched the order and you just want to delete a specific item from the items property, you can use filter to do so before saving the order document.
res.order.items = res.order.items.filter(({ name }) => name !== itemNameToRemove);
Like this:
// Updating One
router.patch("/:id", getOrder, async(req, res) => {
const {
userID,
total,
items,
itemNameToRemove
} = req.body;
if (userID != null) {
res.order.userID = userID;
}
if (total != null) {
res.order.total = total;
}
if (items != null) {
const newItem = items;
res.order.items.push(newItem);
if (itemNameToRemove) {
res.order.items = res.order.items.filter(({
name
}) => name !== itemNameToRemove);
}
}
try {
const updatedItems = await res.order.save()
res.json(updatedItems)
} catch (err) {
res.status(400).json({
message: err.message
})
}
})
you can use $pull for this.
Order.update(
{ userID : "userID123" },
{$pull : {"items" : {"name":"gravy"}}}
)
This will delete the object with name as gravy belong to the userID : userID123

How to push inside nested array of object that have a precise value of a key in MongoDB?

I have one question about a problem that I'm not able to fix. I try to update push a string passed via Query in my mongoose collection.
My collection are like this:
{
"_id": {
"$oid": "6199288597e42bf84d017f9e"
},
"name": "Lisa",
"surname": "Bianchi",
"ID_school": "afbH598U3",
"classes": [
{
"class": "5A",
"activities": {
"in_progress": [],
"finisched": []
},
"_id": {
"$oid": "6199288597e42bf84d017f9f"
}
},
{
"class": "1A",
"activities": {
"in_progress": [],
"finisched": []
},
"_id": {
"$oid": "6199288597e42bf84d017fa0"
}
}
],
"email": "insegnante#a.com",
"__v": 0
}
and I try to push a string in in_progress array that match, for example, with class:"5A" using this way:
import db from "../models/index.js";
const Teacher = db.teacher
const updateActivity = (req, res) => {
const query = { _id: req.query.id};
const update = {$push:{'classes.$[group].activities.in_progress': req.query.data } };
const options = {arrayFilters: { 'group.class': req.query.class }};
Teacher.findOneAndUpdate(query, update, options).exec((err, data) => {
if (err) {
res.status(400).send({ message: err });
return;
} else {
res.status(200).send(data);
}
})
}
const API = {
updateActivity
}
export default API
The query works fine, but nothing was pushed. I tested whit Insomnia passing in the Query field
id = 6199288597e42bf84d017f9e;
class:'5A';
data:"pushed"
Any suggestion? Thanks!
try this way by passing classes.class in query and also change push to $push:{'classes.$.activities.in_progress': req.query.data }
const updateActivity = (req, res) => {
const query = { _id: req.query.id ,'classes.class': req.query.class};
const update = {$push:{'classes.$.activities.in_progress': req.query.data } };
Teacher.updateOne(query,update).exec((err, data) => {
if (err) {
res.status(400).send({ message: err });
return;
} else {
res.status(200).send(data);
}
})
}
There are two ways of doing this:
Option 1: arrayFilters - more flexible Docu
The option you are using.
You have a syntax error - arrayFilters should be an array of documents.
const updateActivity = (req, res) => {
const query = { _id: req.query.id };
const update = {
$push:{ 'classes.$[group].activities.in_progress': req.query.data }
};
// This MUST be an array of filter documents!
const options = { arrayFilters: [{ 'group.class': req.query.class }] };
Teacher
.findOneAndUpdate(query, update, options)
.exec((err, data) => {
if (err) {
res.status(400).send({ message: err });
return;
} else {
res.status(200).send(data);
}
});
}
Option 2: Via Query (as answered by #Saurabh Mistry)
Repeating his answer for completeness
By specifying a query that targets a particular element in an array within result documents.
const updateActivity = (req, res) => {
const query = {
_id: req.query.id,
'classes.class': req.query.data,
};
const update = {
$push:{ 'classes.$.activities.in_progress': req.query.data }
};
Teacher
.findOneAndUpdate(query, update, options)
.exec((err, data) => {
if (err) {
res.status(400).send({ message: err });
return;
} else {
res.status(200).send(data);
}
});
}

AWS Dynamodb not fetching data synchronously by node js

I am new on node js dynamo db I wrote a node js sdk to fetch one row from a table ona dynamodb. It is fetching data correctly but not immediately for this I got error
My code is below a simple code
var AWS = require("aws-sdk");
var config = function(){
AWS.config.update({region: 'us-east-1'});
// Create the DynamoDB service object
var ddb = new AWS.DynamoDB({apiVersion: '2012-08-10'});
var params = {
TableName: 'tblConfigs',
// Key: {
// "id" : {S: "1"},
// }
ExpressionAttributeValues: {
":v1": {
S: "1"
}
},
FilterExpression: "id = :v1",
};
var v;
var json = ddb.scan(params, function(err, data) {
if (err) {
console.log("Error", err);
} else {
v = data;
// console.log(JSON.stringify(data.Item));
// return JSON.stringify(data.Item);
}
});
// if(v=="u")
// for(var i=0;)
v = v.Items[0];
// for()
var con = {
"host": v.endpoint.S,
"user": v.endpoint.username.S,
"password": v.endpoint.password.S,
"database": v.endpoint.database_name.S
};
return con;
}
And I got the below error
> config()
TypeError: Cannot read property 'Items' of undefined
at config (repl:31:7)
as v is undefined so it is giving the error but v is not undefined when I execute the code in node console it first time gave undefined next time it gave value
like below
> v
{ Items:
[ { password: [Object],
stage: [Object],
username: [Object],
id: [Object],
endpoint: [Object],
database_name: [Object] } ],
Count: 1,
ScannedCount: 1 }
how can I fetch the row immediately not after some time? IS there any good way in dynamodb I tried, get, getItem, scan, query all are giving data correctly but not immediately...Please suggest
You are missing one important thing: Javascript execution is asynchronous. As long as you are not using async/await syntax you have to "play" with callbacks like this:
var ddb = new AWS.DynamoDB({ apiVersion: '2012-08-10' });
function loadConfig(callback) {
var params = {
TableName: 'tblConfigs',
ExpressionAttributeValues: {
':v1': {
S: '1'
}
},
FilterExpression: 'id = :v1'
};
ddb.scan(params, function (error, data) {
if (error) {
callback(error);
} else {
var item = data.Items[0];
callback(null, {
'host': item.endpoint.S,
'user': item.endpoint.username.S,
'password': item.endpoint.password.S,
'database': item.endpoint.database_name.S
});
}
});
}
loadConfig(function (error, configuration) {
if (error) {
console.log(error);
} else {
// Your connection logic (JUST AN EXAMPLE!)
var connection = mysql.connect({
host: configuration.host,
user: configuration.user,
password: configuration.password,
database: configuration.database
})
}
});
Btw. storing database configurations in DynamoDB isn't a good solution, i would recommend to check AWS Systems Manager Parameter Store.
Edit
To give you a short example how the async/await syntax looks like
var ddb = new AWS.DynamoDB({ apiVersion: '2012-08-10' });
const loadConfig = async () => {
const { Items } = await ddb.scan({
TableName: 'tblConfigs',
ExpressionAttributeValues: {
':v1': {
S: '1'
}
},
FilterExpression: 'id = :v1'
}).promise();
const item = Items[0];
return {
'host': item.endpoint.S,
'user': item.endpoint.username.S,
'password': item.endpoint.password.S,
'database': item.endpoint.database_name.S
};
};

DynamoDB Validation Exception - Key element does not match the schema

I'm trying to get an item from my DynamoDB but get the following error
ValidationException: The provided key element does not match the
schema
The create item piece of the code works. But no the Get item.
Table Info:
Table Name: movieTable
Primary Partition Key: itemID
Primary Sort Key: sortKey
Here's the code for the create and update:
var fbUserId;
var params;
var keyText;
var attText;
var valText;
var dynamodb = null;
var docClient = null;
var appId = '405140756489952'; //from facebook
var roleArn = 'arn:aws:iam::042765862882:role/Verzosa'; //from AWS IAM
var resultData = null;
document.getElementById('putThis').onclick = function () {
dynamodb = new AWS.DynamoDB({ region: 'us-west-2' });
docClient = new AWS.DynamoDB.DocumentClient({ service: dynamodb });
keyText = document.getElementById("keyValue").value;
attText = document.getElementById("attributeText").value;
valText = document.getElementById("valueText").value;
console.log("Key Value: ", keyText);
console.log("Attribute: ", attText);
console.log("Value: ", valText);
params = {
TableName: 'movieTable',
Item: {
itemID: keyText,
sortKey: valText
}
};
docClient.put(params, function(err, data){
if (err) console.log(err);
else
{
resultData = data;
console.log(resultData);
}
})
};
document.getElementById('getThis').onclick = function () {
dynamodb = new AWS.DynamoDB({ region: 'us-west-2' });
docClient = new AWS.DynamoDB.DocumentClient({ service: dynamodb });
keyText = document.getElementById("keyValue").value;
attText = document.getElementById("attributeText").value;
console.log("Key Value: ", keyText);
console.log("Attribute: ", attText);
params = {
TableName: 'movieTable',
Key: {
itemID: keyText,
},
ProjectionExpression: "#a",
ExpressionAttributeNames: {
'#a': attText
}
};
docClient.get(params, function (err, data)
{
if (err)
{
console.log(err, err.stack);
}
else
{
console.log("success, logging data: ");
console.log(data);//shows keys
console.log("attribute 1 is " + data.Item.sortKey)
//var output = data.Item.attribute1;
l = document.getElementById("output");
l.innerHTML = data.Item.sortKey;
}
})
};
Any help would be appreciated.
You are getting this error because when using AWS.DynamoDB.DocumentClient.get method, you must specify both hash and sort key of an item. But you have only hash key specified (itemId), and sort key is missing.
Here is how your get params should look like:
...
params = {
TableName: 'movieTable',
Key: {
itemID: keyText,
sortKey: valText // <--- sort key added
},
ProjectionExpression: "#a",
ExpressionAttributeNames: {
'#a': attText
}
};
docClient.get(params, function (err, data) {
...
If you'd like to get a record with a hash key only, without specifying its sort key, you should use query method instead of get:
...
params = {
TableName: 'movieTable',
KeyConditionExpression: '#itemID = :itemID',
ProjectionExpression: "#a",
ExpressionAttributeNames: {
'#a': attText,
'#itemID': 'itemID'
},
ExpressionAttributeValues: {
':itemID': keyText
}
};
dynamodbDoc.query(params, function(err, data) {
...
Be aware that while get method always returns 1 or no records, query can possibly return multiple records, so you would have to revisit your current implementation of get callback (e.g. instead of accessing data.Item you should use data.Items array, see query method docs)
You need to pass both primary key and sort key in the params.
You can find these keys from the table UI.
and you should pass those as parameters when making the request
this.ProjectsModel.delete({pk1:"project#giri-test#appsc", sk1:"metadata#giri-test#appsc"}, (error) => {
if (error) {
console.error(error);
} else {
console.log("Successfully deleted item");
}
});

AWS DynamoDB on Lambda not returning inserted data

const AWS = require('aws-sdk')
const docClient = new AWS.DynamoDB.DocumentClient({ region: 'eu-central-1' })
const createDocument = (text, callback) => {
const createParams = {
Item: {
text: text
},
TableName: 'ToDoItems'
}
docClient.put(createParams, (err, data) => {
if(err) {
callback(err, null)
} else {
callback(null, data)
}
})
}
exports.handle = (event, context, callback) => {
createDocument(event.text, (err, data) => {
if(err) {
callback(err, null)
} else {
callback(null, data)
}
})
}
That's my AWS Lambda function, the issue is that when I get a callback, data object is empty, even though document is inserted into DynamoDB. What could the issue be here?
You can't. You have to separately query. On put, if you set ReturnValues: 'ALL_NEW', then you'll get "ReturnValues can only be ALL_OLD or NONE"
Note the 'possible' in AWS's documentation:
http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#put-property
ReturnValues — (String) Possible values include:
"NONE"
"ALL_OLD"
"UPDATED_OLD"
"ALL_NEW"
"UPDATED_NEW"
Also, instead of separately querying, you can also just use the params value. If it was saved, then what you have in createParams.Item is basically what's returned if you were to separately query.
There is a workaround - You can use update method of DynamoDB.DocumentClient.
TableName: "table",
Key: {
id: randomId
},
AttributeUpdates: {
authorId: {Action: "PUT", Value: event.authorId},
date: {Action: "PUT", Value: event.date},
description: {Action: "PUT", Value: event.description},
title: {Action: "PUT", Value: event.title}
},
ReturnValues: "ALL_NEW"
This method will create new item and return all what you need
You have to request the return values, like this:
const createParams = {
Item: {
text: text
},
TableName: 'ToDoItems',
ReturnValues: 'ALL_NEW'
}
This is documented here.
I did have to implementing that the return in .then() was params.Item, like this:
var params = {
TableName:table,
Item:{
"name": value,
"email": value2,
}
};
console.info("Adding a new item...");
await docClient.put(params)
.promise()
.then(data => {
return params.Item;
}).catch(error => {
console.error(error)
throw new Error(error)
})

Categories