AWS SDK for JavaScript v3 x Next.js で DynamoDB に Query してみた
こんにちは!DA(データアナリティクス)事業本部 サービスソリューション部の大高です。
最近、Next.jsでAWS SDK for JavaScript v3を利用しているのですが、DynamoDB に対して Query する機会があったので、対応をまとめておきたいと思います。
前提条件
今回試した環境は以下のとおりです。
- next
- ^11.1.4
- aws-amplify
- ^4.3.22
- @aws-sdk/client-dynamodb
- ^3.121.0"
また、DynamoDB上のテーブルは、以下のようなテーブルを前提とします。
キー | 名前 | 備考 |
---|---|---|
パーティションキー | id (String) | |
ソートキー | type (String) | |
ローカルセカンダリインデックス(LSI) | IdTypeIndex | パーティションキー id (String), ソートキー type (String) |
加えて、今回試す環境ではAmplifyのAuthenticatorを利用して認証していることを前提としています。詳細については以下のエントリもご参照ください。
API Routes で DynamoDB に Query する
まずは、API Routes を利用して DynamoDB に Query するコードです。
import type { Credentials } from '@aws-sdk/types' import type { NextApiRequest, NextApiResponse } from 'next' import { DynamoDBClient, QueryCommand, QueryCommandInput, QueryCommandOutput } from '@aws-sdk/client-dynamodb' import { withSSRContext } from 'aws-amplify' export default async function handler(req: NextApiRequest, res: NextApiResponse<QueryCommandOutput | Error>) { try { const body = JSON.parse(req.body) const SSR = withSSRContext({ req: req }) const credentials: Credentials = await SSR.API.Auth.currentCredentials() const input: QueryCommandInput = body.input const client = new DynamoDBClient({ credentials: credentials, region: 'ap-northeast-1', }) const command: QueryCommand = new QueryCommand(input) const response: QueryCommandOutput = await client.send(command) res.json(response) } catch (e) { console.log(e) res.status(500).json({ name: e.name, message: e.message }) } }
このAPIは、リクエストにQueryCommandInput
の情報を渡すことで、DynamoDBにQueryコマンドを発行して結果を返すだけのAPIになっています。
このあたりはシンプルですね。
API の呼び出し元
次に、APIを呼び出す側のコードです。
import { AttributeValue, QueryCommandInput, QueryCommandOutput } from '@aws-sdk/client-dynamodb' async function sleep(msec: number): Promise<unknown> { return new Promise((resolve) => { setTimeout(resolve, msec) }) } async function callApi(input: QueryCommandInput): Promise<QueryCommandOutput> { const res = await fetch('/api/query-api', { method: 'POST', body: JSON.stringify({ input: input }), }) const data = await res.json() if (!res.ok) { throw new Error(data.message) } return data as QueryCommandOutput } export async function queryItems(input: QueryCommandInput): Promise<Record<string, AttributeValue>[]> { const items: Record<string, AttributeValue>[] = [] const query = async (input: QueryCommandInput) => { const response: QueryCommandOutput = await callApi(input) items.push(...response.Items) // 追加Scanデータがある場合には、追加Scanを実施する(Scan結果が1MBを超えるケース) // ref.) https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Scan.html if (response.LastEvaluatedKey) { // APIリクエストレートを制御する await sleep(500) input.ExclusiveStartKey = response.LastEvaluatedKey await query(input) } } await query(input) return items }
callApi
では、単純にAPIに対してPOST
リクエストをしています。なお、このリクエストのbody
に対してQueryCommandInput
をJSON化したものを渡してあげることで、API側でもQueryCommandInput
を利用できるようにしています。
queryItems
では、このcallApi
の呼び出しを行い、取得された結果をitems
に溜めて返却しています。
ポイントとしては、LastEvaluatedKey
がある場合には、再帰的にQueryをしているところになります。
これはドキュメントに記載のあるとおり、Scan結果が1MBを超える場合にはレスポンスに全件含まれないためです。次回のリクエストのExclusiveStartKey
に対してLastEvaluatedKey
を設定することで、取得しきれなかったレコードを取得しています。
また、APIを再呼び出しする際には念の為少しsleepしてから呼び出すようにしてみました。
QueryCommandInput について
最後に、呼び出しに必要なQueryCommandInput
についてです。
const input: QueryCommandInput = { TableName: 'SampleTable', IndexName: 'IdTypeIndex', KeyConditionExpression: 'id = :id AND type = :type', ExpressionAttributeValues: { ':id': { S: '51f66320-7233-48e9-a718-733735da4249' }, ':type': { S: 'White Mage' }, }, }
TableName
はDynamoDBのテーブル名ですね。IndexName
には今回利用したいローカルセカンダリインデックス(LSI)の名前IdTypeIndex
を指定しています。
KeyConditionExpression
はキーでの検索条件です。ここでは実際のキー名id
と置換名(コロン付きのもの):id
をどちらも同じにしていますが、:myid
のようにしてもOKです。
最後にExpressionAttributeValues
で、利用する実際の値を指定しています。先程の置換名(コロン付きのもの):id
と:type
に対して、検索したい値をDynamoDB形式で指定しています。
なお、DynamoDB形式での値の指定は少しクセがあるのですが、もうちょっと楽に記述したい場合には@aws-sdk/util-dynamodb
のmarshall
を利用すると簡単になるかと思います。私は以下のエントリを参考にさせていただき、実装が楽になりました。
まとめ
以上、AWS SDK for JavaScript v3 x Next.js で DynamoDB に Query してみました。
DynamoDB はよく利用するので、今後はPutItemやDeleteItemについても書いていきたいと思います。
どなたかのお役に立てば幸いです。それでは!