AmplifyでAWS Serverless Expressを使用してCRUD処理を行う

[ACCESS Advent Calendar 2022](https://qiita.com/advent-calendar/2022/access) の6日目になります。

この記事は、幅広いAWSサービスを活用できるプラットフォームのAWS Amplifyで、LambdaをExpressで構築できる`AWS Serverless Express` を使用して、DynamoDBと接続、その上でCRUD処理を行う書き方についてとなります。
参考となった記事は「[Create a REST API integrated with Amazon DynamoDB using AWS Amplify and Vue](https://gerard-sans.medium.com/create-a-rest-api-integrated-with-amazon-dynamodb-using-aws-amplify-and-vue-5be746e43c22)」となります。ほぼその記事通りの実装となります。

## 環境構築

LambdaでCRUD処理を書く前に環境を構築します。

### 各packageとAmplifyのversion

``` terminal
"aws-sdk": "2.1253.0",
"aws-serverless-express": "3.4.0",
"express": "4.18.2"
"Amplify CLI": "10.5.1"
``` 

### ポリシー設定

CRUD処理を行う前提として、LambdaからDynamoDBにアクセスするための権限が必要となります。
そこでポリシーを設定します。
`$ amplify add api` を実行した後、`amplify/backend/function/{functionName}`に `custom-policies.json` が自動で作成されます。
そのファイルに記述することで、Lambda関数でDynamoDBにデータが反映されます。

```json: custom-policies.json
[
    {
        "Action": [
            "dynamodb:PutItem", // POST
            "dynamodb:Scan", // GET ALL
            "dynamodb:GetItem", // GET ONE
            "dynamodb:UpdateItem", // UPDATE
            "dynamodb:DeleteItem" // DELETE
        ],
        "Resource": ["arn:aws:dynamodb:*:*:table/{tableName}"],
        "Effect": "Allow"
    }
]
```

### AWS Serverless Expressを使用

LambdaをExpressで構築することができます。
それは `$ amplify add api` で、設定についての質問の

```terminal
Choose the function template that you want to use
```
で、`Serverless ExpressJS function (Integration with API Gateway)` を選択するとExpressで構築されたfunctionのSampleファイルが自動で作成されるので、それを見本に構築します。

### DynamoDBに接続

次にDynamoDBをAmplifyで使用するには `storage` を追加した上で、さらに`function` をupdateします。

``` terminal
$ amplify add storage
$ amplify update function
```

その上でLamda関数を定義するファイルで `DocumentClient` を読み込むと、DynamoDBと接続されます。

``` javascript
import AWS from 'aws-sdk';
const dynamodb = new AWS.DynamoDB.DocumentClient();
```

その `DocumentClient` を読み込んだ変数 `dynamodb` に各CRUD処理のメソッドを定義します。
例えばPOSTの場合は

``` javascript: POST例
dynamodb.put(params, (error, result) => {
    if (error) {
        // errorのresponseを書く
    } else {
        // 正常のresponseを書く
    }
});
```
となります。
その他のCRUD処理のメソッドは下記になります。
``` javascript
dynamodb.scan // GET ALL
dynamodb.get // GET ONE
dynamodb.update // UPDATE
dynamodb.delete // DELETE
```

環境構築のおおよその設定をイメージできたのではないでしょうか。
次にCRUD処理の書き方について説明します。

## CRUD処理の書き方

### paramsについて

前述の `POST例` でput関数の引数に `params` を設定しました。
その `params` は、公式ドキュメント「[Calling DynamoDB from Lambda in Node.js](https://docs.amplify.aws/guides/functions/dynamodb-from-js-lambda/q/platform/js/)」に記載されてるとおり、いくつかの `key`と`value` を指定することができます。

``` javascript
const params = {
    TableName : 'your-table-name',
    IndexName: 'some-index',
    KeyConditionExpression: '#name = :value',
    ExpressionAttributeValues: { ':value': 'shoes' },
    ExpressionAttributeNames: { '#name': 'name' }
    Item: {
        id: '12345',
        price: 100.00
    }
    Key: {
        id: '12345'
    }
    ReturnValues: NONE | ALL_OLD | UPDATED_OLD | ALL_NEW | UPDATED_NEW
}
```

`Item` はDynamoDBに登録するデータになります。
さらに `Key` は既に登録済みのデータを抽出する際に指定します。とても直感的で分かりやすいですね。
例えばDynamoDBに1件新規で登録する場合は

``` javascript:POST例
const postUser = (request, response) => {
    const timestamp = new Date().toISOString();
    const params = {
        TableName: tableName,
        Item: {
            ...request.body,
            id: uuidv4(),
            createdAt: timestamp,
            updatedAt: timestamp,
        },
    };
    
    dynamodb.put(params, (error, result) => {
        if (error) {
            // errorのresponseを書く
        } else {
            // 正常のresponseを書く
        }
    });
};
```
となります。
次にDynamoDBに登録されているデータを1件抽出する場合は

```  javascript:GET ONE例
const getOneUser = (request, response) => {
    const params = {
        TableName: tableName,
        Key: {
            id: request.params.userId,
        },
    };
  
    dynamodb.get(params, (error, result) => {
        if (error) {
            // errorのresponseを書く
        } else {
            // 正常のresponseを書く
        }
    });
};
```

となります。
さらにDynamoDBに登録されているデータを1件削除する場合は

``` javascript:DELETE例
const deleteUser = async (request, responss) => {
    const params = {
        TableName: tableName,
        Key: {
            id: request.params.userId,
        },
    };

    dynamodb.delete(params, (error, result) => {
        if (error) {
            // errorのresponseを書く
        } else {
            // 正常のresponseを書く
        }
    });
};
```
となります。

### UPDATE処理

UPDATE処理は前述の処理と比較すると少し複雑です。
UPDATE処理には前述した `params` で
 - `UpdateExpression`
 - `ExpressionAttributeValues`
 - `ExpressionAttributeNames` 
を使用します。
その中の`UpdateExpression` について、公式サイト「[Update expressions](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html)」で確認すると

```
SET — modifying or adding item attributes
REMOVE — deleting attributes from an item
ADD — updating numbers and sets
DELETE — removing elements from a set
```
を指定することができます。
そこで `SET` を利用して
``` javascript
UpdateExpression: 'SET #attributeName = :attributeValue'
```
と設定します。
この書き方については、「[DynamoDBでデータを更新する際に使うUpdateExpressionについて一通りまとめてみた](https://dev.classmethod.jp/articles/dynamodb-update-expression-actions/)」の記事に記載されいてるとおり、

> UpdateExpressionの中では #xxxなど#で始まる変数でAttributeNameを表現できる。 また、:xxxなど:で始める変数で値を表現ができる。

となります。
つまり、paramsで`ExpressionAttributeValues`、`ExpressionAttributeNames`　の `ExpressionAttributeNames` はDynamoDBに登録する項目の属性名、`ExpressionAttributeValues` はその属性名に登録する値になります。

``` javascript
UpdateExpression: 'SET #attributeName = :attributeValue'
ExpressionAttributeNames: {
    '#attributeName': {属性名},
},
ExpressionAttributeValues: {
    ':attributeValue': {値},
｝
```
と指定します。
具体的に例を書くと
``` javascript: UPDATE例
const updateUser = (request, response) => {
    const timestamp = new Date().toISOString();
    const params = {
        TableName: tableName,
        Key: {
            id: request.params.userId,
        },
        UpdateExpression: 'SET #title = :title,  #updatedAt = :updatedAt',
        ExpressionAttributeNames: {
            '#title': 'title',
            '#updatedAt': 'updatedAt',
        },
        ExpressionAttributeValues: {
            ':title': request.body.title,
            ':updatedAt': timestamp,
        },
    };
    
    dynamodb.update(params, (error, result) => {
        if (error) {
            // errorのresponseを書く
        } else {
            // 正常のresponseを書く
        }
    }
};
```
になります。
こう書くことで、DynamoDBに既に登録されているid `request.params.userId` に対して `title` と `updateAt` の値のみ変更になります。

以上となります。
いかがでしたでしょうか。
AmplifyでCRUD処理を行う際の簡単な説明とSampleコードについて書きました。
そこまで行き着くまでに、AmplifyやAWSのドキュメント、さらには参考記事を読み解く必要があるので、この記事で1つにまとめました。
一助となれば幸いです。


## 参考サイト

- [Create a REST API integrated with Amazon DynamoDB using AWS Amplify and Vue](https://gerard-sans.medium.com/create-a-rest-api-integrated-with-amazon-dynamodb-using-aws-amplify-and-vue-5be746e43c22)
- [Update expressions](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html)
- [DynamoDBでデータを更新する際に使うUpdateExpressionについて一通りまとめてみた](https://dev.classmethod.jp/articles/dynamodb-update-expression-actions/)