How to store JSON in Amazon DynamoDB using AWS AppSync
Tags: graphql, appsync, dynamodb, aws
Published: 2020-08-05 05:06:03

With [Amazon DynamoDB](https://aws.amazon.com/dynamodb/) you can also store entire JSON-formatted documents as single DynamoDB items. In this blog post I show you how this works in combination with [AWS AppSync](https://aws.amazon.com/appsync/).

## DynamoDB

In the following example I store multilingual translations in the database.

| id | name | description |
| -- | ---- | ----------- |
| street-view | Street View | `{ "de" : { "S" : "Beschreibung" }, "en" : { "S" : "Description" } }` |

The item in the table has an `id`, which is the primary key. `name` and `description` are attributes. The `description` attribute is a nested attribute.

This example is very simplified. You can also make nested attributes more complex. But please note the limit for nested attributes (📖 see the section `Limits`).

## GraphQL Schema

You can use the `AWSJSON` scalar type, but this unnecessarily limits the capabilities of GraphQL. Instead, map the JSON within the schema.

For reasons of clarity, I describe only the mutation.

```graphql
input MultilingualDescriptionInput {
  en: String!
  de: String!
}

input PutAppInput {
  name: String!
  description: MultilingualDescriptionInput!
}

type Mutation {
  createApp(input: PutAppInput!): App
}
```

## AppSync Mapping Template

In the request mapping template I use the [utility helpers](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-util-reference.html) of AWS AppSync.

Take a look at the `toMapValuesJson` helper:

`$util.dynamodb.toMapValuesJson(Map)` returns the DynamoDB attribute value as a JSON encoded string.

```vtl
{
  "version" : "2018-05-29",
  "operation" : "PutItem",
  "key" : {
    "id" : $util.dynamodb.toDynamoDBJson($ctx.args.input.name.replace(" ", "-").toLowerCase()))
  },
  "attributeValues" : $util.dynamodb.toMapValuesJson($ctx.args.input)
}
```

Let's look at this in detail:

`$ctx.args.input.description` is a JSON object and looks like

```json
{
  "de" : "Beschreibung",
  "en" : "Description"
}
```

The `$util.dynamodb.toMapValuesJson` helper maps the JSON object to

```json
{
  "de" : {
    "S" : "Beschreibung"
  },
  "en" : {
    "S" : "Description"
  }
}
```

The response template returns the result as JSON.

```vtl
$util.toJson($ctx.result)
```

## Client-Side Mutation

The `CREATE_APP` mutation expects two parameters. The `name` of the type `String` and the `description` of the user-defined scalar type `MultilingualDescriptionInput` (📖 see the section `GraphQL Schema`).

```grahql
mutation CREATE_APP($name: String!, $description: MultilingualDescriptionInput!) {
  createApp(input: {
    name: $name,
    description: $description
  }) {
    id
    name
    description {
      de
      en
    }
  }
}
```

You can now access the individual fields in the return value. Of course this also works for your GraphQL queries. 

## Application

For completeness still the application code. 🤓

```js
import React from 'react'
import { useForm } from 'react-hook-form'
import { useMutation } from '@apollo/react-hooks'
import { CREATE_APP } from './graphql/mutations.gql'

const App = () => {
  const [createApp, { data }] = useMutation(CREATE_APP)
  const { register, handleSubmit } = useForm({ nativeValidation: true })

  const onSubmit = (variables) => createApp({ variables })

  return (
    <form
      style={{ display: 'flex', flexDirection: 'column', alignItems: 'start' }}
      onSubmit={handleSubmit(onSubmit)}
    >
      <label>
        Name:
        <input
          type='text'
          name='name'
          ref={register({ required: 'Please enter the name' })}
        />
      </label>
      <label>
        Description DE:
        <input
          type='text'
          name='description.de'
          ref={register({ required: 'Please enter the description' })}
        />
      </label>
      <label>
        Description EN:
        <input
          type='text'
          name='description.en'
          ref={register({ required: 'Please enter the description' })}
        />
      </label>
      <input type='submit' value='Save' />
    </form>
  )
}

export default App
```

## ⚠️ Limits

* The maximum item size in DynamoDB is 400 KB.
* DynamoDB supports nested attributes up to 32 levels deep.

—

If you have any kind of feedback, suggestions or ideas - feel free to comment this post!