🙌

serverless-expressからDynamoDBを呼び出す

2020/12/30に公開
2

serverless-expressを利用したLambdaからDyanamoDBを呼び出す方法についてです。

前回のserverless-expressでAPI GatewayからLambdaを実行するの記事の続きになります。

コード作成

まずはコードです。
DynamoDBに接続するにはaws-sdkを利用します。

$ npm install aws-sdk

続いてコードを書いていきます。
今回は前回の記事で作成したrouter.jsにそのまま書いてしまいます。

routes/router.js
const express  = require('express');
const router = express.Router();

const AWS = require("aws-sdk");

// ローカルで実行する際の設定
const dynamoOptions = process.env.NODE_ENV === "development" ? {
    region: "localhost",
    endpoint: "http://localhost:8000", 
  } : {};
const documentClient = new AWS.DynamoDB.DocumentClient(dynamoOptions);

router.get('/', (req, res) => {
    res.json({message : 'Hello World!'});
});

/**
 * GET: /users ユーザー一覧取得
 */
router.get('/users', (req, res) => {
    documentClient.scan({
        TableName: 'Users'
    }).promise()
    .then((result) => res.json(result))
    .catch((e) => res.status(422).json({ errors: e }));
});

/**
 * GET: /user/:userId ユーザー取得
 * @param userId 取得対象ユーザーのIDを指定
 */
router.get('/user/:userId', (req, res) => {
    // 本来はuserIdのvalidationチェックが必要
    documentClient.get({
        TableName: 'Users',
        Key: {
            id: parseInt(req.params.userId)
        }
    }).promise()
    .then((result) => res.json(result))
    .catch((e) => res.status(422).json({ errors: e }));
});

/**
 * POST: /user ユーザー作成API
 * @param {req.body} { id: id, name: 名前, age: 年齢 }
 */
router.post('/user', (req, res) => {
    documentClient.put({
        TableName: 'Users',
        Item: req.body
    }).promise()
    .then((result) => res.json(result))
    .catch((e) => res.status(422).json({ errors: e }));
});

module.exports = router;

app.jsにも設定を追加します。
(ようかんさん @inoue2002 から記事の内容の漏れを教えていただきました。ありがとうございました。)

app.js
const app = express();

const bodyParser = require('body-parser')
const router = require('./routes/router');
const cors = require('cors')

app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))

if (process.env.NODE_ENV === 'local') {
  app.use(cors());
}

// システムエラー検知時の共通レスポンス
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({message: 'システムエラーが発生しました。'});
});

app.use('/', router);

module.exports = app;

これでコードはOKです。
続いてローカルで動作確認していきましょう。

ローカルにDynamoDBの環境構築

今回はローカル環境でDynamoDBを立ち上げるためにDockerを利用しました。

$ docker pull amazon/dynamodb-local
$ docker run -d --name dynamodb -p 8000:8000 amazon/dynamodb-local

これでlocalhost:8000にDynamoDBが立ち上がりました。

このままだとテーブルが作成されていない状態なので、テーブルを作成していきます。
今回はNode.jsでテーブル作成するコードを用意しました。

script/create-table.js
// ローカル環境のDynamoDBテーブル作成
const AWS = require("aws-sdk");

AWS.config.update({
    region: "localhost",
    endpoint: "http://localhost:8000", 
});

const dynamoDB = new AWS.DynamoDB();

const params = {
    TableName : "Users",
    KeySchema: [
        { AttributeName: "id",   KeyType: "HASH"}
    ],
    AttributeDefinitions: [
        { AttributeName: "id",   AttributeType: "N" }
    ],
    ProvisionedThroughput: {
        ReadCapacityUnits: 1, 
        WriteCapacityUnits: 1
    }
}

dynamoDB.createTable(params, (err, data) => {
    if (err) {
        console.error("Unable to create table. Error JSON:", JSON.stringify(err, null, 2))
    } else {
        console.log("Created table. Table description JSON:", JSON.stringify(data, null, 2))
    }
})

では実行します。

$ node script/create-table.js

Created table. Table description JSON: {
-- 省略

これでローカル環境で動作確認ができるようになりました。

$ npm start

$ curl -X POST localhost:3000/user/ -H "Content-Type: application/json" -d '{"id":1, "name":"taro", "age":21}'
{}

$ curl localhost:3000/users
{"Items":[{"name":"taro","age":21,"id":1}],"Count":1,"ScannedCount":1}

o$ curl localhost:3000/user/1
{"Item":{"name":"taro","age":21,"id":1}}

DynamoDB作成

では実際にDynamoDBを作成していきます。

前回作成したserverless.ymlにDynamoDBを作成するためのresourceを記載します。

serverless.yml
service: serverless-express-test

provider:
  name: aws
  runtime: nodejs12.x
  region: ap-northeast-1
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:Query
        - dynamodb:Scan
        - dynamodb:GetItem
        - dynamodb:PutItem
        - dynamodb:UpdateItem
        - dynamodb:DeleteItem
      Resource: 'arn:aws:dynamodb:${self:provider.region}:*:table/Users'

package:
  exclude:
    - .git/**
    - test/**
    - README.md
    - script/**

functions:
  serverlessTest:
    handler: lambda.handler
    events:
      - http: ANY /
      - http: 'ANY {proxy+}'

resources:
  Resources:
    users:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: Users
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: N
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1

ポイントは2つです。

  • providerにLambdaからDynamoDBを操作するためのIAM設定を追加
  • resourcesにDynamoDBのテーブル作成処理を追加

これであとはデプロイするのみです。

$ sls deploy

--省略
endpoints:
  ANY - https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev
  ANY - https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/{proxy+}
--省略

動作確認します。

$ curl -X POST https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/user -H "Content-Type: application/json" -d '{"id":2, "name":"Hanako", "age":22}'
{}

$ curl https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/users
{"Items":[{"id":2,"name":"Hanako","age":22}],"Count":1,"ScannedCount":1}

$ curl https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/user/2
{"Item":{"id":2,"name":"Hanako","age":22}}

これでOKです。
コードはこちらに配置してあります。

参考情報

AWS-SDK - DynamoDB
Serverless.yml - Reference
DockerHub - dynamodb-local

Discussion

ようかんようかん

非常に参考になる記事をありがとうございます。
こちらの環境で試してみた際に、ローカルでuserをDBに保存しようとpostした際にreq.bodyがundefindeになってしまいました。こちら(https://qiita.com/Saw-000/items/58d917857c40f2e11a3b)の記事を参考に、body-parser の設定を行うことで解決できましたので、参考までに共有させていただきます!

YutaSaitoYutaSaito

ようかんさん (@inoue2002)
ありがとうございます!
内容修正しました。