Amplify-js: How to update/query/scan in DynamoDB using Amplify?

Created on 4 Jan 2019  路  17Comments  路  Source: aws-amplify/amplify-js

I would like to update a field in my DynamoDB table called 'AppTable' using the AWS-Amplify methods that were generated when I created the corresponding CRUD cloud-api (e.g. PUT, GET, POST, DELETE).

These few API methods reside in awsmobilejs/backend/cloud-api/AppTable/app.js, but they are not comprehensive enough!

I can simply use API.put to add to this table. e.g:

apiResponse = API.put('AppTableCRUD', path, body)

body: {
    "uploaderBool": true,
    "userId": 'user1',
    "itemId": '10005',
    "Username": "first_app_user",
    "Email": "[email protected]",
    "NumberList": 
       [
        "7.9",
        "5.7",
        "3.4",
        "4.9"
       ],
    "AverageNumber":[
        "5.5"
       ],
     }
    }

What i want to do, is update the NumberList field with a function in my react-native app, count how many numbers in the list on DynamoDB, then calculate/write an updated number called AverageNumber.

If i use a current API.put method i have to put in this ENTIRE body everytime (this list could be hundreds of thousand of entries so Get, then Put with a single update is absurd).

How do you use the .scan(), .query() and .update() method that DynamoDB has with Amplify?

API feature-request

Most helpful comment

Hi @chai86, I created graphql api using aws-amplify-cli with this app code (react web)

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import Amplify, { API, graphqlOperation } from 'aws-amplify';
import { withAuthenticator } from "aws-amplify-react";
import config from './aws-exports';
import { createVideo } from "./graphql/mutations";

Amplify.configure(config);

class App extends Component {

  CreateVideo = async () => {

    const newVideoInfo = {
      videoIdInt: 12345,
      videoName: "sheeran-moon",
      videoUploader: "ed",
      videoUploadedAt: "tonight",
      videoLength: 85,
      numberList: [7, 8, 9],
      shareLink: "www.voda.com"
    }

    try {
      await API.graphql(graphqlOperation(createVideo, { input: newVideoInfo }))
      console.log('User successfully created!!')
    }
    catch (err) {
      console.log('create user error: ', err)
    }

  }

  render() {
    return (
      <div >
        <button
          onClick={this.CreateVideo}
        >Add user</button>
      </div>

    );
  }


}

export default withAuthenticator(App, true);

it seems that input attribute is missing so it should be like this
await API.graphql(graphqlOperation(createVideo, { input: newVideoInfo }))

All 17 comments

@chai86

I tried this on a similar project with a code like this.

I modified my app.js file that contains the aws serverlesss express code that runs on my lambda function by adding support for PATCH method. This has a path /items/addNumber/:hashKey/:sortKey where :hashKey and :sortKey are the object that is going to be updated. Inside that function I get the data from DynamoDB (including the array of numbers), then adding the new number to the array and calculating the average. newNumber is on the body of the request.

I added this code on app.js (lambda function code)

app.patch(path + "/addNumber" + hashKeyPath + sortKeyPath, function (req, res) {
  let params = {}
  try {
    params[partitionKeyName] = convertUrlType(req.params[partitionKeyName], partitionKeyType);
  } catch (err) {
    res.json({ error: 'Wrong column type ' + err });
  }

  if (hasSortKey) {
    try {
      params[sortKeyName] = convertUrlType(req.params[sortKeyName], sortKeyType);
    } catch (err) {
      res.json({ error: 'Wrong column type ' + err });
    }
  }

  let getItemParams = {
    TableName: tableName,
    Key: params
  }

  var newNumber = req.body.newNumber;

  if (newNumber == null || isNaN(newNumber)) {
    res.json({ error: 'Wrong number ' + newNumber });
  }

  dynamodb.get(getItemParams, (err, data) => {
    if (err) {
      res.json({ error: 'Could not load items: ' + err.message });
    } else {
      if (data.Item) {
        data = data.Item;
      }
      data.numbers = [].concat(data.numbers || []).concat(newNumber);

      data.average = data.numbers.reduce(function(acc, currentValue){ return acc + currentValue; }) / data.numbers.length;

      let putItemParams = {
        TableName: tableName,
        Item: data
      }
      dynamodb.put(putItemParams, (err, data) => {

        if (err) {
          res.json({ error: err, url: req.url, body: req.body });
        } else {
          res.json({ success: 'post call succeed!', url: req.url, data: data })
        }
      });
    }
  });

After updating the code I did a push to update the lambda code that runs on the server.

Now my client code to invoke the api is like this.

API.patch('myapi', '/items/addNumber/id-1/item-1', {
      body: {
        "newNumber": 3
      }
    }).then(result => console.log({result}));

id-1 is a value from my hashKey and item-1 the sortKey. The body of the request contains the newNumber.

I hope this helps

@elorzafe

Thanks so much for the information.

As mentioned before, i think the DynamoDB function update() is useful as it would be more versatile for virtually any addition to the table as exemplified in the documentation here: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GettingStarted.Js.03.html#GettingStarted.Js.03.03

var docClient = new AWS.DynamoDB.DocumentClient();

function updateItem() {
    var table = "Movies";
    var year = 2015;
    var title = "The Big New Movie";

    var params = {
        TableName:table,
        Key:{
            "year": year,
            "title": title
        },
        UpdateExpression: "set info.rating = :r, info.plot=:p, info.actors=:a",
        ExpressionAttributeValues:{
            ":r":5.5,
            ":p":"Everything happens all at once.",
            ":a":["Larry", "Moe", "Curly"]
        },
        ReturnValues:"UPDATED_NEW"
    };

    docClient.UPDATE(params, function(err, data) {
        if (err) {
            document.getElementById('textarea').innerHTML = "Unable to update item: " + "\n" + JSON.stringify(err, undefined, 2);
        } else {
            document.getElementById('textarea').innerHTML = "UpdateItem succeeded: " + "\n" + JSON.stringify(data, undefined, 2);
        }
    });
}

I've also tried to 'hijack' the app.post() call, looked similar to put() which i don't use in my backend/cloud-api/AppTable/app.js. I'll show you below what i've attempted:

In the backend/cloud-api/AppTable/app.js:

app.post(path, function(req, res) {

  if (userIdPresent) {
    req.body['userId'] = req.apiGateway.event.requestContext.identity.cognitoIdentityId || UNAUTH;
  }

  let params = {
    TableName: tableName,  //AppTable
    Item: req.body . //I'll show the body object in my main App.js file
  }

  dynamodb.UPDATE(params, (err, data) => {
    if(err) {
      res.json({error: err, url: req.url, body: req.body});
    } else{
      res.json({success: 'update succeed!', url: req.url, data: data})
    }
  });
});

Calling it in my App.js file, and based on the request body in the documentation:

async testUpdateItems(){

        const path = '/AppTable';

        var updateTableWith = {
              TableName: 'AppTable',
              Key:{
                'userId': 'user1',
                'itemId': '10005'
              },
              UpdateExpression: 'set NumberList= :r', AverageNumber= :f',
              ExpressionAttributeValues: { 
                ":r": ["7.3", "0.7", "9.9"],
                ':f': "10.0"
            },
            ReturnValues: "UPDATED_NEW"        
        }

        //Use the API to 'Update' this to the DynamoDB database (remember we hijacked API.Post)
        try{
            const apiResponse = await API.post('AppTableCRUD', path, updateTableWith)      
            console.log("response from update is: " + apiResponse);
        }
        catch (e) {
            console.log(e);
        }
    }

However it doesn't seem to work or update my table at all. Can your app.patch() be leveraged instead with dynamodb.update()? If so how?

As a feature request there really should be support for DynamoDB API's in Amplify dynamodb.query(), dynamodb.scan() and dynamodb.update() in Amplify, else so much functionality is simply lost.

@chai86 I think your code doesnt work because in that case let params = req.body are the params for the dynamo update instead of

let params = {
    TableName: tableName,  //AppTable
    Item: req.body . //I'll show the body object in my main App.js file
  }

However I think is a security risk doing that. Someone can remove/change data from every table and not necessary doing what you expect to do.

Have you try AWS AppSync? (is a graphql server with real-time and offline capabilities) You can use DynamoDB/RDS/Lambda/others as Data Sources. Using amplify-cli you can bootstrap your api and data sources very easily. Take a look here

@elorzafe
Ah ok. I'm still unsure why if the req.body i put in as below, it doesn't work.

```
var updateTableWith = {
TableName: 'AppTable',
Key:{
'userId': 'user1',
'itemId': '10005'
},
UpdateExpression: 'set NumberList= :r', AverageNumber= :f',
ExpressionAttributeValues: {
":r": ["7.3", "0.7", "9.9"],
':f': "10.0"
},
ReturnValues: "UPDATED_NEW"
}

In the documentation for DynamoDB the `params` i thought that similarly `updateTableWith` should still get processed when calling update() as that's what the function is expecting right?

var docClient = new AWS.DynamoDB.DocumentClient();
docClient.update(params, function(err, data)
```

AWS AppSync with mutations, subscriptions etc... seems quite counter intuitive when DynamoDB's Put, Get, Delete, Scan, Query, Update functions etc.... are pretty self explanatory and directly address my DynamoDB database. Plus AppSync is schema based.

Nevertheless, i have spent months configuring a React Native mobile app, uploading images, videos, designing a front end etc... assuming the database piece would be relatively straightforward. I weighed up the options and chose to utilise AWS Amplify and AWS MobileHub to develop my mobile app, and for Amplify to not offer some of the fundamental database functions in the backend/cloud-api/AppTable/app.js is a huge surprise at this stage. You guys have done fantastic work on the aws-mobile-react-native-starter, truly helpful and avant-garde!

Right now when I type app.xxxx() in backend/cloud-api/AppTable/app.js the functions dont appear. Will the dynamodb.query(), dynamodb.scan() and dynamodb.update() ever be made available?? @powerful23 @mlabieniec @mbahar @richardzcode ?

Any ideas on the availability guys? @powerful23 @mlabieniec @mbahar @richardzcode @elorzafe

@chai86 what do you mean by functions don't appear? You mean they are not autogenerated? Or is not possible to use dynamodb api on app.js.

@elorzafe

When i say the functions don't appear, i mean in app.js if i try to use a HTTP method, when typing app.xxx() none of the app.scan(), app.update(),app.query() automatically appear when typing. The only ones of real use are app.put(), app.post(), app.get(), app.delete() and these are already existing in my app.js file.

Within these HTTP methods, I see that dynamodb.delete(), dynamodb.get(), dynamodb.query() are called. And even dynamodb.update(), dynamodb.scan() is available to be called with:
const dynamodb = new AWS.DynamoDB.DocumentClient()

How do i call these update, scan, query functions? Why are they not available as an app.xxx() so that i can call them? Moreover, how would i even call these in my App.js react native file? Currently i use:

const apiResponse = await API.put('myappAPI', path, objectBodyToPut)

But obviously the only ones i have right now in my app.js that were generated when the API was created by awsmobile were API.put, API.post, API.get, API.delete. Thats it.

So theres 4 x API calls and 11 x DynamoDB calls (batchGet, get, put, query, update, scan, transactGet, etc...). It doesn't compute. How can i call these DynamoDB functions in my App.js in the same way i call API.put or API.delete for example?

@chai86

Correct me if I didn't understand well.

What you are saying is you would like to have something like this on your app code.

const apiResponse = await API.scan('myappAPI', path, options) and that scan will be on your lambda function code. So it will be a 1:1 parity between the app code and the backend code and all DynamoDB methods will be available?

Thanks for your feedback!

@elorzafe

Thats spot on! There's no use to trying to use AWS mobile with DynamoDB with only a put, get and delete. I've been left "paralyzed" in project terms for 2 weeks now because of the absence of the other functions.

How can I achieve this?:

const apiResponse = await API.scan('myappAPI', path, options)
const apiResponse = await API.update('myappAPI', path, options)
const apiResponse = await API.query('myappAPI', path, options)

p.s. also i guess there would need to be examples of what the 'options' paramater would contain and how it would be composed.

@chai86

Is an interesting use case what you mention (I am sorry you have been paralyzed in your project), but API category was designed to make HTTP requests to REST and Graphql endpoints, is more than the specific use case as a data proxy to DynamoDB.

We provide capabilitites to integrate with DynamoDB by generating lambda function code that supports this. I understand that for your requirements that is not enough, that's why I recommend to you to use AppSync (https://aws-amplify.github.io/docs/js/api#simple-query) in my opinion is more simple but is ok if you don't think the same. I can discuss this with the team so I will mark this issue as feature request.

@elorzafe

I did take your comments on board and i have used AppSync with my React Native app, because I really can not wait for features to appear/not appear. I've managed to generate the GraphQL API and now i call:

await API.graphql(graphqlOperation(mutation, newInfoToUpload) for example.

It is a much more difficult process (mutations, queries, subscriptions with all the corresponding resolver mapping templates) and it's quite putting off compared to the simpler HTTP calls.

I've yet to succesfully make any of the calls work and send data to DynamoDB yet from my React Native app

Any advice (or anyone in the community you think could be of help) would be much appreciated.

@chai86, do you have a annotated schema? (Assuming you used amplify-cli). I can help you if you want?

Thanks @elorzafe

My schema.graphql is really simple:

type Users @model {
  id: ID!
  userName: String!
  name: String
  email: String!
  mobileNum: String
  listOfRated: [RatedPairs]
  userDescription: String
  isBlocked: Boolean
}

type Video @model {
  id: ID!
  videoIdInt: Int
  videoName: String!
  videoUploader: String!
  videoUploadedAt: String
  videoLength: Float!
  numberList: [Float]!
  shareLink: String
}

type RatedPairs {
  videoId: Int
  correspondingRating: Float
}

In the example in the weblink i previously sent, it only attempts a write to the DynamoDB video table using my createVideo mutation (again in the weblink)

Hi @chai86, I created graphql api using aws-amplify-cli with this app code (react web)

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import Amplify, { API, graphqlOperation } from 'aws-amplify';
import { withAuthenticator } from "aws-amplify-react";
import config from './aws-exports';
import { createVideo } from "./graphql/mutations";

Amplify.configure(config);

class App extends Component {

  CreateVideo = async () => {

    const newVideoInfo = {
      videoIdInt: 12345,
      videoName: "sheeran-moon",
      videoUploader: "ed",
      videoUploadedAt: "tonight",
      videoLength: 85,
      numberList: [7, 8, 9],
      shareLink: "www.voda.com"
    }

    try {
      await API.graphql(graphqlOperation(createVideo, { input: newVideoInfo }))
      console.log('User successfully created!!')
    }
    catch (err) {
      console.log('create user error: ', err)
    }

  }

  render() {
    return (
      <div >
        <button
          onClick={this.CreateVideo}
        >Add user</button>
      </div>

    );
  }


}

export default withAuthenticator(App, true);

it seems that input attribute is missing so it should be like this
await API.graphql(graphqlOperation(createVideo, { input: newVideoInfo }))

@elorzafe

You've only gone and bloomin done it! Thanks!

Right, now to play with the other mutations and queries (create, delete, update, get, list). I'll let you know how i go 馃憤

@chai86 I am glad it works! 馃槃

Hi @elorzafe. I've successfully been able to add new entries and update information using AppSync. However i'm totally stumped when it comes to 'Get' information.

So for example if i try:
await API.graphql(graphqlOperation(getVideo, { id: ???? }))

I have no idea what my 'id' is. Obviously once the entry is created a unique id is created, but i have no way of knowing that unless going to my AWS console.

I have created my own (as it were in dynamodb, SortKey) called 'videoIntId' in creating the entry which i know (see code from Jan 19). I need to use this videoIntId to retrieve the information in my table. How do i go about using this to get my table entry information?

Was this page helpful?
0 / 5 - 0 ratings