DEV Community

Tony Goodchild
Tony Goodchild

Posted on Originally published at latticegrid.dev

Build a CRUD app with a JavaScript grid, Node and DynamoDB. Part 2: add and delete

This is the dev.to edition of a tutorial that lives, with a live copy of the finished grid you can try, at latticegrid.dev/tutorials/crud-dynamodb-part-2. Part 1 built the table, the Lambda function and a page that reads from it.

Part 1 ended with a page that reads products from your own DynamoDB table through a Lambda function. This part adds the two verbs that make it an application: adding a product through the grid's built-in form, and deleting one from the right-click menu. Both land in DynamoDB before the page moves on.

Everything from part 1 carries over. You will change the function's code, widen its permissions and its URL settings, and swap the page.

Source: the part-2 folder of toclocoinc/lattice-tutorial-crud-dynamodb (Download ZIP). This part needs Lattice Grid 1.76.0 or later, which is the version the page loads.

Step 1: teach the function two more verbs

Open Lambda, then products-read, then the Code tab. Replace the contents of index.mjs with lambda/index.mjs from the download and choose Deploy.

The function now looks at the request's method:

  • GET returns every product, as before.
  • POST takes a product as JSON, gives it a sku if the page did not, stamps updated with today's date, and writes it with a condition that refuses to overwrite an existing sku. It answers with the product as stored.
  • DELETE takes ?sku= and removes that product.

The condition on the write is the one line worth pausing on. Without it, two people adding a product with the same sku would silently overwrite each other. With it, the second one gets a clear "already exists" answer that the page can show.

Step 2: allow the new verbs through the URL

The Function URL's CORS setting from part 1 allows every origin but only the GET method, so the browser would refuse to send a POST or a DELETE.

  1. On the function's Configuration tab, choose Function URL, then Edit.
  2. Under Configure cross-origin resource sharing (CORS), set Allow methods to GET, POST and DELETE, and Allow headers to content-type. The page sends that header with the JSON body of a POST.
  3. Save.

Step 3: give the function the permission it needs, and no more

Part 1 attached AmazonDynamoDBReadOnlyAccess, which is enough to read and nothing else. The function now writes, so it needs PutItem and DeleteItem, and the tidy way to grant them is a policy that names this one table.

  1. On the Configuration tab, choose Permissions, then click the role name under Execution role. IAM opens.
  2. Under Permissions policies, tick AmazonDynamoDBReadOnlyAccess and choose Remove. Confirm.
  3. Choose Add permissions. This is the same menu part 1 used to attach the read-only policy; this time choose Create inline policy instead of Attach policies. Switch the editor to JSON and paste iam/products-policy.json from the download. Replace REGION with your region (eu-west-2 for London) and ACCOUNT_ID with the twelve-digit account id shown at the top right of the console.
  4. Choose Next, name it products-table, and Create policy.

The IAM role's Permissions policies tab, the Add permissions menu open over Attach policies and Create inline policy

The policy allows scan, get, put, update and delete on the Products table and nothing on any other table. Update is not used yet; part 3 needs it, and it saves a return trip to IAM.

Step 4: the page

Open web/index.html from the download and paste your Function URL into the same line as in part 1. Open the file in a browser.

Three things are new.

Add product opens the grid's built-in form for a new row. Fill in a name, category, quantity and price, and choose Save. The form validates the fields the way the grid's own editors do, then calls the create hook you gave it:

rowForm: {
  fields: [
    { field: 'name', label: 'Product' },
    { field: 'category', label: 'Category' },
    { field: 'quantity', label: 'In stock' },
    { field: 'price', label: 'Price' },
  ],
  create: (values) => api('POST', '', values),
},
Enter fullscreen mode Exit fullscreen mode

The grid's built-in New row form open, with Product, Category, In stock and Price filled in

create posts the values to the function and returns what comes back: the product as DynamoDB stored it, with the sku the server assigned. The grid adds that row, scrolls to it and selects it. If the function refuses, the form stays open with your values and a retry, and the status line shows the server's reason.

Right-click a row and the menu has the grid's own items, then a Delete for that product:

contextMenu: (p, defaults) => [
  ...defaults,
  { separator: true },
  {
    name: 'Delete ' + p.data.name,
    action: async () => {
      if (!confirm('Delete ' + p.data.name + '?')) return;
      await api('DELETE', '?sku=' + encodeURIComponent(p.key));
      grid.rows.apply({ remove: [p.key] });
    },
  },
],
Enter fullscreen mode Exit fullscreen mode

The row's right-click menu, ending in a Delete item named after the product on that row

p describes the cell that was clicked: p.key is the row's key, the sku, and p.data is your product object. The action deletes on the server first and only then removes the row from the grid, so a failed delete leaves the row where it was.

The api helper is eight lines that wrap fetch, set the JSON header when there is a body, and turn a non-2xx answer into an error carrying the function's message. Every call in the page goes through it.

Step 5: check the table

Add a product, then open DynamoDB, Tables, Products, Explore table items. The new product is there with the sku the function chose. Delete it from the page and refresh the console; it is gone.

What you have

A page that reads, adds and deletes against your own table, with every write happening on the server before the page reflects it. The grid supplied the form and the menu; you supplied two verbs and a policy.

Next

Part 3: edit in place. Cells become editable and each committed change is patched to DynamoDB while the grid shows it, with the old value put back if the server says no. The page also goes full screen on a button, and the form moves from a drawer into a panel of your own.

Cleaning up

As in part 1: delete the function, the table and the role. The inline policy goes with the role.

Read the full source, including lambda/index.mjs and web/index.html, on the public repository, or grab it as a ZIP download and skip the cloning.

Lattice Grid is a JavaScript data grid with charts, dashboards and server-side pushdown, free to develop with on localhost. The row form and the context menu used here are part of the core grid: latticegrid.dev.

Top comments (0)