この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは!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 するコードです。
pages/api/query-api.ts
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を呼び出す側のコードです。
libs/query.ts
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についても書いていきたいと思います。
どなたかのお役に立てば幸いです。それでは!