インフラ構成
WebAPIの作成に当たって今回使用するAWSの各サービスを簡単にまとめてみます。
・API Gateway
HTTPリクエストを受け付けて、他のAWSサービス(Lambda等)にルーティングする機能です。REST APIやWebSocket APIを簡単に作成できます。API Gatewayでは、リクエストのパスやクエリパラメータ、ヘッダー、ボディなどをLambda関数に渡すことができます。
https://aws.amazon.com/jp/api-gateway/
・Lambda
サーバレスコンピューティングのサービスです。コードをアップロードするだけで、イベントやトリガーに応じて自動的に実行されます。サーバの管理やスケーリングを気にする必要がありません。Lambdaは、Node.js、Python、Java、Go、Rubyなどの言語に対応しています
https://aws.amazon.com/jp/lambda/
・DynamoDB
DynamoDBは、NoSQLデータベースのサービスで、高速でスケーラブルなキーバリューストアやドキュメントストアを提供します。DynamoDBでは、テーブルを作成して、パーティションキーとオプションでソートキーを指定して、データを保存することができます。
https://aws.amazon.com/jp/dynamodb/
これらのサービスを組み合わせることで、HTTPリクエストをAPI Gatewayで受けて、そこからLambdaにリクエストデータを渡してプログラムを起動させ、LambdaからDynamoDBにデータを保存するといったサーバーレスなAPIのインフラを構成することができます。
API要件
作成するAPIの機能としては、CRUD操作を有するシンプルなタスク管理APIを実装していこうと思います。今回は主にインフラ構築が目的ですので、ソースコードは最小限に抑えて実装していきます。
HTTP Method | パス | 概要 |
---|---|---|
POST | /tasks | タスクを作成 |
GET | /tasks | タスクを取得 |
PUT | /tasks/:id | タスクを更新 |
DELETE | /tasks/:id | タスクを削除 |
環境準備
AWSのリソースを作成するにあたって今回はServerless Frameworkを使用してリソースを作成していこうと思います。
まずは、Serverless Frameworkをインストールします。
npm install -g serverless@3.24.1
インストールが完了しましたら、次にServerless Frameworkのテンプレートを作成します。
serverless create --template aws-nodejs-ecma-script
テンプレートファイル一式が作成されましたら必要なライブラリをインストールします。
npm install
最後に後の実装でプログラムからAWSのリソースを操作するためにAWSのSDKが必要になるのでインストールしておきます。
npm install aws-sdk@2
DynamoDBテーブルの作成
DynamoDBでタスクを管理するためのテーブルを作成していきます。
作成するにあたって必要な設定値をserverless.ymlファイルのresources要素の下に追記していきます。
https://www.serverless.com/framework/docs/providers/aws/guide/serverless.yml#aws-resources
+resources:
+ Resources:
+ TasksDynamoDBTable:
+ Type: AWS::DynamoDB::Table
+ Properties:
+ TableName: tasks
+ AttributeDefinitions:
+ - AttributeName: id
+ AttributeType: S
+ KeySchema:
+ - AttributeName: id
+ KeyType: HASH
+ ProvisionedThroughput:
+ ReadCapacityUnits: 5
+ WriteCapacityUnits: 5
ファイルの編集が完了しましたら、npx serverless deploy
コマンドを実行してデプロイを行います。
npx serverless deploy
IAM Roleプラグインの導入
後々API実装の中でDynamoDBとの通信でIAM設定を入れる必要になりますので
Serverless frameworkでIAM設定を行うためのプラグインを導入していきます。
npm install --save-dev serverless-iam-roles-per-function@3.2.0
コマンドの実行が完了しましたら、serverless.ymlファイルのplugins要素の下に設定を追記します。
plugins:
- serverless-webpack
+ - serverless-iam-roles-per-function
タスク作成APIの実装
API Gatewayを利用してAPIサーバーとしてLambdaを利用するので、APIとして動かす為に必要な設定値をserverless.ymlファイルに追記していきます。
また、DynamoDBにデータ作成を許可するIAM設定を追記します。
+functions:
+ taskPost:
+ handler: src/taskHandler.post
+ events:
+ - httpApi:
+ method: post
+ path: /tasks
+ iamRoleStatements:
+ - Effect: Allow
+ Action:
+ - dynamodb:PutItem
+ Resource: 'arn:aws:dynamodb:*:*:table/tasks'
taskHandler.jsファイルにリクエストボディー情報をもとにDynamoDBのテーブルにタスクを登録するAPIのプログラム処理を追記します。
+import {DynamoDB} from 'aws-sdk'
+import crypto from 'crypto'
+
+export async function post(event, context) {
+ const requestBody = JSON.parse(event.body)
+ const item = {
+ id: {
+ S: crypto.randomUUID()
+ },
+ title: {
+ S: requestBody.title
+ }
+ }
+ const dynamodb = new DynamoDB({
+ region: 'ap-south-1'
+ })
+ await dynamodb.putItem({
+ TableName: 'tasks',
+ Item: item
+ }).promise()
+ return item
+}
ファイルの編集が完了しましたら、npx serverless deploy
コマンドを実行してデプロイを行います。
ec2-user:~/environment/lambda-demo $ npx serverless deploy
(node:3722) NOTE: We are formalizing our plans to enter AWS SDK for JavaScript (v2) into maintenance mode in 2023.
Please migrate your code to use AWS SDK for JavaScript (v3).
For more information, check the migration guide at https://a.co/7PzMCcy
(Use `node --trace-warnings ...` to show where the warning was created)
Deploying lambda-demo to stage dev (ap-south-1)
✔ Service deployed to stack lambda-demo-dev (103s)
endpoint: POST - https://nbf2t81za3.execute-api.ap-south-1.amazonaws.com/tasks
functions:
taskPost: lambda-demo-dev-taskPost (2 MB)
Need a faster logging experience than CloudWatch? Try our Dev Mode in Console: run "serverless dev"
コマンドの戻り値のendpointにデプロイ先のURLが記載されいるのいるので実際にそのURLに対してリクエストを投げてみます。
想定通りのレスポンスが帰ってくるのが確認できました。
タスク取得APIの実装
serverless.ymlファイルのfunctions要素の配下にタスク取得APIの設定情報、及びDynamoDBからデータ取得を許可するIAM設定を追記します。
functions:
+ taskList:
+ handler: src/taskHandler.list
+ events:
+ - httpApi:
+ method: get
+ path: /tasks
+ iamRoleStatements:
+ - Effect: Allow
+ Action:
+ - dynamodb:Scan
+ Resource: 'arn:aws:dynamodb:*:*:table/tasks'
+
taskHandler.jsファイルにDynamoDBのテーブルに登録済みのタスク一覧を出力するAPIのプログラム処理を追記します。
+export async function list(event, context) {
+ const dynamodb = new DynamoDB({
+ region: 'ap-south-1'
+ });
+ const result = await dynamodb.scan({
+ TableName: 'tasks'
+ }).promise();
+ const tasks = result.Items.map((item) => {
+ return {
+ id: item.id.S,
+ title: item.title.S
+ };
+ });
+ return {
+ tasks
+ };
+}
ファイルの編集が完了しましたら、npx serverless deploy
コマンドを実行してデプロイを行います。
ec2-user:~/environment/lambda-demo $ npx serverless deploy(node:5254) NOTE: We are formalizing our plans to enter AWS SDK for JavaScript (v2) into maintenance mode in 2023.
Please migrate your code to use AWS SDK for JavaScript (v3).
For more information, check the migration guide at https://a.co/7PzMCcy
(Use `node --trace-warnings ...` to show where the warning was created)
Deploying lambda-demo to stage dev (ap-south-1)
✔ Service deployed to stack lambda-demo-dev (62s)
endpoints:
POST - https://nbf2t81za3.execute-api.ap-south-1.amazonaws.com/tasks
GET - https://nbf2t81za3.execute-api.ap-south-1.amazonaws.com/tasks
functions:
taskPost: lambda-demo-dev-taskPost (2 MB)
taskList: lambda-demo-dev-taskList (2 MB)
Need a faster logging experience than CloudWatch? Try our Dev Mode in Console: run "serverless dev"
コマンドの戻り値のendpointにデプロイ先のURLが記載されいるのいるので実際にそのURLに対してリクエストを投げてみます。
想定通りのレスポンスが帰ってくるのが確認できました。
タスク更新APIの実装
serverless.ymlファイルのfunctions要素の配下にタスク更新APIの設定情報、及びDynamoDBのデータの更新を許可するIAM設定を追記します。
functions:
+ taskPut:
+ handler: src/taskHandler.put
+ events:
+ - httpApi:
+ method: put
+ path: /tasks/{taskId}
+ iamRoleStatements:
+ - Effect: Allow
+ Action:
+ - dynamodb:updateItem
+ Resource: 'arn:aws:dynamodb:*:*:table/tasks'
taskHandler.jsファイルにパスパラメータで指定されたタスクをリクエストボディー情報をもとにDynamoDBのテーブルに登録されているタスクを更新するAPIのプログラム処理を追記します。
+export async function put(event, context) {
+ let taskId = event.pathParameters.taskId;
+ const requestBody = JSON.parse(event.body);
+ const dynamodb = new DynamoDB({
+ region: 'ap-south-1'
+ });
+ const params = {
+ TableName: 'tasks',
+ Key: {
+ id: {
+ S: taskId
+ },
+ },
+ UpdateExpression: 'set title = :x',
+ ExpressionAttributeValues: {
+ ':x': {
+ S: requestBody.title
+ },
+ },
+ ReturnValues: 'ALL_NEW',
+ };
+ const result = await dynamodb.updateItem(params).promise();
+ return {
+ task: result
+ };
+}
ファイルの編集が完了しましたら、npx serverless deploy
コマンドを実行してデプロイを行います。
ec2-user:~/environment/lambda-demo $ npx serverless deploy
(node:5945) NOTE: We are formalizing our plans to enter AWS SDK for JavaScript (v2) into maintenance mode in 2023.
Please migrate your code to use AWS SDK for JavaScript (v3).
For more information, check the migration guide at https://a.co/7PzMCcy
(Use `node --trace-warnings ...` to show where the warning was created)
Deploying lambda-demo to stage dev (ap-south-1)
✔ Service deployed to stack lambda-demo-dev (62s)
endpoints:
POST - https://nbf2t81za3.execute-api.ap-south-1.amazonaws.com/tasks
GET - https://nbf2t81za3.execute-api.ap-south-1.amazonaws.com/tasks
PUT - https://nbf2t81za3.execute-api.ap-south-1.amazonaws.com/tasks/{taskId}
functions:
taskPost: lambda-demo-dev-taskPost (2 MB)
taskList: lambda-demo-dev-taskList (2 MB)
taskPut: lambda-demo-dev-taskPut (2 MB)
Need a faster logging experience than CloudWatch? Try our Dev Mode in Console: run "serverless dev"
コマンドの戻り値のendpointにデプロイ先のURLが記載されいるのいるので実際にそのURLに対してリクエストを投げてみます。
想定通りのレスポンスが帰ってくるのが確認できました。
タスク削除APIの実装
serverless.ymlファイルのfunctions要素の配下にタスク削除APIの設定情報、及びDynamoDBからデータ削除を許可するIAM設定を追記します。
functions:
+ taskDelete:
+ handler: src/taskHandler.remove
+ events:
+ - httpApi:
+ method: delete
+ path: /tasks/{taskId}
+ iamRoleStatements:
+ - Effect: Allow
+ Action:
+ - dynamodb:DeleteItem
+ Resource: 'arn:aws:dynamodb:*:*:table/tasks'
taskHandler.jsファイルにパスパラメータで指定されたタスクをDynamoDBのテーブルから削除するAPIのプログラム処理を追記します。
+export async function remove(event, context) {
+ let taskId = event.pathParameters.taskId;
+ const dynamodb = new DynamoDB({
+ region: 'ap-south-1'
+ });
+ const params = {
+ TableName: 'tasks',
+ Key: {
+ id: {
+ S: taskId
+ },
+ },
+ ReturnValues: 'ALL_OLD'
+ };
+ const result = await dynamodb.deleteItem(params).promise();
+ return {
+ task: result
+ };
+}
ファイルの編集が完了しましたら、npx serverless deploy
コマンドを実行してデプロイを行います。
ec2-user:~/environment/lambda-demo $ npx serverless deploy
(node:6657) NOTE: We are formalizing our plans to enter AWS SDK for JavaScript (v2) into maintenance mode in 2023.
Please migrate your code to use AWS SDK for JavaScript (v3).
For more information, check the migration guide at https://a.co/7PzMCcy
(Use `node --trace-warnings ...` to show where the warning was created)
Deploying lambda-demo to stage dev (ap-south-1)
✔ Service deployed to stack lambda-demo-dev (62s)
endpoints:
POST - https://nbf2t81za3.execute-api.ap-south-1.amazonaws.com/tasks
GET - https://nbf2t81za3.execute-api.ap-south-1.amazonaws.com/tasks
PUT - https://nbf2t81za3.execute-api.ap-south-1.amazonaws.com/tasks/{taskId}
DELETE - https://nbf2t81za3.execute-api.ap-south-1.amazonaws.com/tasks/{taskId}
functions:
taskPost: lambda-demo-dev-taskPost (2 MB)
taskList: lambda-demo-dev-taskList (2 MB)
taskPut: lambda-demo-dev-taskPut (2 MB)
taskDelete: lambda-demo-dev-taskDelete (2 MB)
Need a faster logging experience than CloudWatch? Try our Dev Mode in Console: run "serverless dev"
コマンドの戻り値のendpointにデプロイ先のURLが記載されいるのいるので実際にそのURLに対してリクエストを投げてみます。
想定通りのレスポンスが帰ってくるのが確認できました。
リソースの削除
最後に今回作成したリソースをまとめて削除します。
npx serverless remove
完成コード
import {
DynamoDB
} from 'aws-sdk';
import crypto from 'crypto';
export async function post(event, context) {
const requestBody = JSON.parse(event.body);
const item = {
id: {
S: crypto.randomUUID()
},
title: {
S: requestBody.title
}
};
const dynamodb = new DynamoDB({
region: 'ap-south-1'
});
await dynamodb.putItem({
TableName: 'tasks',
Item: item
}).promise();
return item;
}
export async function list(event, context) {
const dynamodb = new DynamoDB({
region: 'ap-south-1'
});
const result = await dynamodb.scan({
TableName: 'tasks'
}).promise();
const tasks = result.Items.map((item) => {
return {
id: item.id.S,
title: item.title.S
};
});
return {
tasks
};
}
export async function put(event, context) {
let taskId = event.pathParameters.taskId;
const requestBody = JSON.parse(event.body);
const dynamodb = new DynamoDB({
region: 'ap-south-1'
});
const params = {
TableName: 'tasks',
Key: {
id: {
S: taskId
},
},
UpdateExpression: 'set title = :x',
ExpressionAttributeValues: {
':x': {
S: requestBody.title
},
},
ReturnValues: 'ALL_NEW',
};
const result = await dynamodb.updateItem(params).promise();
return {
task: result
};
}
export async function remove(event, context) {
let taskId = event.pathParameters.taskId;
const dynamodb = new DynamoDB({
region: 'ap-south-1'
});
const params = {
TableName: 'tasks',
Key: {
id: {
S: taskId
},
},
ReturnValues: 'ALL_OLD'
};
const result = await dynamodb.deleteItem(params).promise();
return {
task: result
};
}
service: lambda-demo
# app and org for use with dashboard.serverless.com
#app: your-app-name
#org: your-org-name
frameworkVersion: "3"
# Add the serverless-webpack plugin
plugins:
- serverless-webpack
- serverless-iam-roles-per-function
provider:
name: aws
runtime: nodejs16.x
region: ap-south-1
functions:
taskPost:
handler: src/taskHandler.post
events:
- httpApi:
method: post
path: /tasks
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:PutItem
Resource: 'arn:aws:dynamodb:*:*:table/tasks'
taskList:
handler: src/taskHandler.list
events:
- httpApi:
method: get
path: /tasks
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:Scan
Resource: 'arn:aws:dynamodb:*:*:table/tasks'
taskPut:
handler: src/taskHandler.put
events:
- httpApi:
method: put
path: /tasks/{taskId}
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:updateItem
Resource: 'arn:aws:dynamodb:*:*:table/tasks'
taskDelete:
handler: src/taskHandler.remove
events:
- httpApi:
method: delete
path: /tasks/{taskId}
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:DeleteItem
Resource: 'arn:aws:dynamodb:*:*:table/tasks'
resources:
Resources:
TasksDynamoDBTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: tasks
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 5
WriteCapacityUnits: 5