DockerでNext.js+DynamoDBのローカル環境を作る
概要
Docker を用いて、Next.js と DynamoDB を組み合わせたローカル環境を作ります。
docker-compose を持ちいて、合計 3 つのコンテナを立ち上げて実現します。
- Next.js 用コンテナ
- DynamoDB(ローカル)用コンテナ
- AWS CLI(ローカル)用コンテナ
構成
最終的に作成する構成は以下の通りとします。
.
├ src :Next.jsのプロジェクトソースを格納する
├ bin :AWS CLI用のコンテナとのファイル受け渡し用
├ Dockerfile :Next.js用のコンテナ定義のDockerfile
├ Dockerfile-awscli :AWS CLI用のコンテナ定義のDockerfile
└ docker-compose.yml :各コンテナの起動設定
以下のファイルを作成していきます。
FROM node:17-alpine
WORKDIR /usr/app
FROM amazon/aws-cli
ENV AWS_ACCESS_KEY_ID=fake_access_key\
AWS_SECRET_ACCESS_KEY=fake_secret_access_key\
DYNAMODB_REGION=ap-northeast-1
WORKDIR /usr/app
version: '3'
services:
app:
build: ./
volumes:
- ./src:/usr/app
command: sh -c "yarn dev"
ports:
- "3000:3000"
dynamodb:
image: amazon/dynamodb-local
command: -jar DynamoDBLocal.jar -sharedDb -dbPath . -optimizeDbBeforeStartup
volumes:
- dynamodb:/home/dynamodblocal
ports:
- 8001:8000
awscli:
build:
context: .
dockerfile: Dockerfile-awscli
entrypoint: [""]
tty: true
command:
- /bin/sh
volumes:
- ./bin:/usr/app
volumes:
dynamodb:
driver: local
bin:
driver: local
※DynamoDB のデータは、毎回消えないように volumes 指定で永続化しています。(Docker の管理領域に保管されます)
※DynamoDB の command では、DynamoDB がデフォルトだとメモリ内にデータを保管しており、コンテナを停止するとデータが消えてしまうため、保持するように設定をしています。
※ローカルの bin フォルダを AWS CLI の作業ディレクトリにマウントしています。
※DynamoDB のポートは他の処理と衝突しないよう 8000 番ポート →8001 番ポートにマッピングしています。環境に合わせて設定してください。(8000 番ポートでも問題ないです)
コンテナを起動する
docker-compose up -d
AWS CLI 用コンテナ名を確認する
docker ps -a
AWS CLI 用コンテナに入る
docker exec -it [AWS CLI用コンテナ名] /bin/bash
AWS CLI コマンドを実行する
aws dynamodb list-tables --region ap-northeast-1 --endpoint-url http://dynamodb:8000
※AWS CLI 仕様上、--region
と、--endpoint-url
の指定は毎回必要になります。
※Docker ネットワーク内では、ポート番号 8000 番で通信します。
実行結果
次のような結果が返ってくれば OK です。
{
"TableNames": []
}
テーブル作成
aws dynamodb \
--region ap-northeast-1 \
--endpoint-url http://dynamodb:8000 \
create-table \
--table-name User \
--attribute-definitions \
AttributeName=UserId,AttributeType=S \
AttributeName=SortKey,AttributeType=S \
--key-schema \
AttributeName=UserId,KeyType=HASH AttributeName=SortKey,KeyType=RANGE \
--billing-mode PAY_PER_REQUEST
※結果が表示されたら下までスクロールしてq
で抜けます。
あとは必要に応じて、コマンドを記載したファイルをbin
フォルダに格納しておき、aws dynamodb コマンドを実行しつつ作業を進めていきます。
DynamoDB Client を Next.js プロジェクトに導入する
次のコマンドを実行し、DynamoDB Client を Next.js のプロジェクトにインストール(追加)します。
yarn add @aws-sdk/client-dynamodb
DynamoDB Client をプログラム内から呼び出す
API 処理などで、次のような記載を行い、DynamoDBClient を呼び出せるようにします。
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
DynamoDB Client に接続情報を渡し初期化する
次のような記述を行うことで、ローカルの DynamoDB に接続できる Client を作成できます。
accessKey や secretAccessKey は、ローカル接続の場合はダミー値で構いません。
const dbClient = new DynamoDBClient({
credentials: {
accessKeyId: "dummy",
secretAccessKey: "dummy",
},
endpoint: "http://dynamodb:8000",
region: "ap-northeast-1",
});
あとは必要な処理を実装していけば OK です。
PUT と GET を行った例を記載しておきます。(DynamoDB の PUT と GET を動かしてみる以外特に整えていないので、実際には各種ハンドリングを実装ください)
PUT 処理の例
import type { NextApiRequest, NextApiResponse } from "next";
import { DynamoDBClient, PutItemCommand } from "@aws-sdk/client-dynamodb";
const dbClient = new DynamoDBClient({
credentials: {
accessKeyId: "dummy",
secretAccessKey: "dummy",
},
endpoint: "http://dynamodb:8000",
region: "ap-northeast-1",
});
type Data = {
result: string;
};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
try {
const name = req.query.name;
if (typeof name !== "string") {
throw new Error();
}
const putCommand = new PutItemCommand({
TableName: "User",
Item: {
UserId: { S: "test1" },
SortKey: { S: "001" },
Name: { S: name },
},
});
const putResultData = await dbClient.send(putCommand);
console.log(putResultData);
res.status(200).json({
result: "SUCCESS",
});
} catch (err) {
res.status(500).json({ result: "failed to put data" });
}
}
GET 処理の例
import type { NextApiRequest, NextApiResponse } from "next";
import { DynamoDBClient, GetItemCommand } from "@aws-sdk/client-dynamodb";
const dbClient = new DynamoDBClient({
credentials: {
accessKeyId: "dummy",
secretAccessKey: "dummy",
},
endpoint: "http://dynamodb:8000",
region: "ap-northeast-1",
});
type Data = {
result: string;
};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
try {
const getCommand = new GetItemCommand({
TableName: "User",
Key: {
UserId: { S: "test1" },
SortKey: { S: "001" },
},
});
const getResultData = await dbClient.send(getCommand);
console.log(getResultData.Item);
if (!getResultData.Item?.Name.S) {
res.status(500).json({ result: "failed to get data(No Item)" });
} else {
res.status(200).json({
result: getResultData.Item.Name.S,
});
}
} catch (err) {
res.status(500).json({ result: "failed to get data(Error)" });
}
}
Discussion