Serverless FrameworkでCRUDを試す

Serverless Frameworkを用いてCRUDできるようにしたいと思います。
最終的なゴールはブラウザからCRUDできると良いのですが、今回はcurlコマンドを使ってできるようにします。

こちらのチュートリアルを参考にやっていきます
aws-lambda Create Simple CRUD Operation

Lambdaはnodeを利用しています。

必要なもの

node, awscliが必要です。

nodeはanyenvを利用してインストールしました。

バージョン

node15系を使っていたのですが、serverlessをデプロイしようとした時にエラーになってしまい、GitHubでもissueで解決されていないようでしたので、14系でやっています。

slsとはServerless Frameworkを入れた後に使えるコマンドです。slsが短縮系なので特に違いはないらしいです。今は気にしなくても良いです。

$ node -v 
v14.15.4

$ aws --version
aws-cli/2.1.24 Python/3.7.4 Darwin/20.2.0 exe/x86_64 prompt/off

$ sls --version
Framework Core: 2.22.0
Plugin: 4.4.2
SDK: 2.3.2
Components: 3.6.1

Serverless Frameworkのインストール

nodeが使えれば利用できるはずです

$ npm install serverless -g

AWSの認証情報を入力

aws configureコマンドを使って自分の端末に認証情報を登録しましょう

以下のように入力して登録します。
リージョンは東京、データの型はjsonにしています

$ aws configure --profile user1
AWS Access Key ID [None]: {アクセスキー(各自)}
AWS Secret Access Key [None]: {シークレットアクセスキー(各自)}
Default region name [None]: ap-northeast-1
Default output format [None]: json

アプリケーション作成

下記のコマンドを入力して作成します。

serverless create --template aws-nodejs --path myService

コマンドを入力するとmyServiceという名称のディレクトリが作られ、中にはserverless.yml, handler.jsがあるはずです。
他にもevent.jsonが必要なのですが最新版だと作成されないので自分で作成します

vim event.json

{
    "event": {
        "key3": "value3",
        "key2": "value2",
        "key1": "value1"
    }
}

ファイル編集

serverless.ymlを以下のように書き変えます
チュートリアルとは違い東京リージョンで行います

service: serverless-crud

provider:
  name: aws
  runtime: nodejs12.x
  region: ap-northeast-1
  stage: dev
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:DescribeTable
        - dynamodb:Query
        - dynamodb:Scan
        - dynamodb:GetItem
        - dynamodb:PutItem
        - dynamodb:UpdateItem
        - dynamodb:DeleteItem
      Resource: "arn:aws:dynamodb:ap-northeast-1:*:*"

functions:
  create:
    handler: handler.create
    events:
      - http:
          path: todos
          method: post
          cors: true
  readAll:
    handler: handler.readAll
    events:
      - http:
          path: todos
          method: get
          cors: true
  readOne:
    handler: handler.readOne
    events:
      - http:
          path: todos/{id}
          method: get
          cors: true
  update:
    handler: handler.update
    events:
      - http:
          path: todos/{id}
          method: put
          cors: true
  delete:
    handler: handler.delete
    events:
      - http:
          path: todos/{id}
          method: delete
          cors: true

resources:
  Resources:
    TodosDynamoDbTable:
      Type: 'AWS::DynamoDB::Table'
      DeletionPolicy: Retain
      Properties:
        AttributeDefinitions:
          -
            AttributeName: id
            AttributeType: S
        KeySchema:
          -
            AttributeName: id
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1
        TableName: 'todos' 

DynamoDBのためのIAMとDynamoDBのテーブル、Lambdaを作成します

次にhandler.jsを書き換えます

'use strict';

const todosCreate = require('./lambda/todos-create.js');
const todosReadAll = require('./lambda/todos-read-all.js');
const todosReadOne = require('./lambda/todos-read-one.js');
const todosUpdate = require('./lambda/todos-update.js');
const todosDelete = require('./lambda/todos-delete.js');

module.exports.create = (event, context, callback) => {
  todosCreate(event, (error, result) => {
    const response = {
      statusCode: 200,
      headers: {
        "Access-Control-Allow-Origin": "*"
      },
      body: JSON.stringify(result),
    };

    context.succeed(response);
  });
};

module.exports.readAll = (event, context, callback) => {
  todosReadAll(event, (error, result) => {
    const response = {
      statusCode: 200,
      headers: {
        "Access-Control-Allow-Origin": "*"
      },
      body: JSON.stringify(result),
    };

    context.succeed(response);
  });
};

module.exports.readOne = (event, context, callback) => {
  todosReadOne(event, (error, result) => {
    const response = {
      statusCode: 200,
      headers: {
        "Access-Control-Allow-Origin": "*"
      },
      body: JSON.stringify(result),
    };

    context.succeed(response);
  });
};

module.exports.update = (event, context, callback) => {
  todosUpdate(event, (error, result) => {
    const response = {
      statusCode: 200,
      headers: {
        "Access-Control-Allow-Origin": "*"
      },
      body: JSON.stringify(result),
    };

    context.succeed(response);
  });
};

module.exports.delete = (event, context, callback) => {
  todosDelete(event, (error, result) => {
    const response = {
      statusCode: 200,
      headers: {
        "Access-Control-Allow-Origin": "*"
      },
      body: JSON.stringify(result),
    };



    context.succeed(response);
  });
};

次にCRUDの各Lambda関数を作成します

todos-create.js

'use strict';

const AWS = require('aws-sdk');
const dynamoDb = new AWS.DynamoDB.DocumentClient();
const uuid = require('uuid');

module.exports = (event, callback) => {
  const data = JSON.parse(event.body);

  data.id = uuid.v1();
  data.updatedAt = new Date().getTime();

  const params = {
    TableName: 'todos',
    Item: data
  };

  return dynamoDb.put(params, (error, data) => {
    if (error) {
      callback(error);
    }
    callback(error, params.Item);
  });
};

todos-read-all.js


'use strict'; const AWS = require('aws-sdk'); const dynamoDb = new AWS.DynamoDB.DocumentClient(); module.exports = (event, callback) => { const params = { TableName: 'todos', }; return dynamoDb.scan(params, (error, data) => { if (error) { callback(error); } callback(error, data.Items); }); };

todos-read-one.js

'use strict';

const AWS = require('aws-sdk');
const dynamoDb = new AWS.DynamoDB.DocumentClient();

module.exports = (event, callback) => {
  const params = {
    TableName: 'todos',
    Key: {
      id: event.pathParameters.id
    }
  };

  return dynamoDb.get(params, (error, data) => {
    if (error) {
      callback(error);
    }
    callback(error, data.Item);
  });
};

todos-update.js


'use strict'; const AWS = require('aws-sdk'); const dynamoDb = new AWS.DynamoDB.DocumentClient(); module.exports = (event, callback) => { const data = JSON.parse(event.body); data.id = event.pathParameters.id; data.updatedAt = new Date().getTime(); const params = { TableName: 'todos', Item: data }; return dynamoDb.put(params, (error, data) => { if (error) { callback(error); } callback(error, params.Item); }); };

todos-delete.js

'use strict';

const AWS = require('aws-sdk');
const dynamoDb = new AWS.DynamoDB.DocumentClient();

module.exports = (event, callback) => {
  const params = {
    TableName: 'todos',
    Key: {
      id: event.pathParameters.id
    }
  };

  return dynamoDb.delete(params, (error, data) => {
    if (error) {
      callback(error);
    }
    callback(error, params.Key);
  });
};

必要なモジュールを揃える

npmで必要なモジュールを揃えます

$ npm init
$ npm install aws-sdk --save
$ npm install uuid --save

デプロイ

デプロイしていきます。
先ほど作成したAWSのprofileを利用していきます

$ sls deploy -v --aws-profile user1

成功するといろいろ情報が出てきます

Service Information
service: serverless-crud
stage: dev
region: ap-northeast-1
stack: serverless-crud-dev
resources: 37
api keys:
  None
endpoints:
  POST - https://XXXXXXX.execute-api.ap-northeast-1.amazonaws.com/dev/todos
  GET - https://XXXXXXX.execute-api.ap-northeast-1.amazonaws.com/dev/todos
  GET - https://XXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/dev/todos/{id}
  PUT - https://XXXXXXX.execute-api.ap-northeast-1.amazonaws.com/dev/todos/{id}
  DELETE - https://XXXXXXX.execute-api.ap-northeast-1.amazonaws.com/dev/todos/{id}

CRUDしてみる

ターミナルでcurlコマンドを利用してCRUDしてみます。

CREATE – curl -X POST https://XXXX.execute-api.region.amazonaws.com/dev/todos --data '{ "body" : "curlでPOST" }'
READ – curl https://XXXX.execute-api.region.amazonaws.com/dev/todos
READ(ID指定) – curl https://XXXX.execute-api.region.amazonaws.com/dev/todos/<id>
UPDATE – curl -X PUT https://XXXX.execute-api.region.amazonaws.com/dev/todos/<id> --data '{ "body" : "UPDATEしたよ" }'
DELETE – curl -X DELETE https://XXXX.execute-api.region.amazonaws.com/dev/todos/<id>


//CREATE
$ curl -X POST https://XXXXXXX.execute-api.ap-northeast-1.amazonaws.com/dev/todos --data '{"body":"curlでPOST"}'
{"body":"curlでPOST","id":"0dfaf2f0-6b39-11eb-9f3a-8d5346ce6816","updatedAt":1612917835679}

$ curl -X POST https://XXXXXXX.execute-api.ap-northeast-1.amazonaws.com/dev/todos --data '{"body":"curlでPOST2"}'
{"body":"curlでPOST2","id":"7bbf61e0-6b39-11eb-9f3a-8d5346ce6816","updatedAt":1612918019838}
//READ
$ curl https://XXXXXXX.execute-api.ap-northeast-1.amazonaws.com/dev/todos
[{"id":"0dfaf2f0-6b39-11eb-9f3a-8d5346ce6816","updatedAt":1612917835679,"body":"curlでPOST"},{"id":"7bbf61e0-6b39-11eb-9f3a-8d5346ce6816","updatedAt":1612918019838,"body":"curlでPOST2"}]
//READ(ID指定)
$ curl https://XXXXXXX.execute-api.ap-northeast-1.amazonaws.com/dev/todos/0dfaf2f0-6b39-11eb-9f3a-8d5346ce6816
{"id":"0dfaf2f0-6b39-11eb-9f3a-8d5346ce6816","updatedAt":1612917835679,"body":"curlでPOST"}
//UPDATE
$ curl -X PUT https://XXXXXXX.execute-api.ap-northeast-1.amazonaws.com/dev/todos/0dfaf2f0-6b39-11eb-9f3a-8d5346ce6816 --data '{"body":"UPDATEしたよ"}'
{"body":"UPDATEしたよ","id":"0dfaf2f0-6b39-11eb-9f3a-8d5346ce6816","updatedAt":1612918180045}
//DELETE
$ curl -X DELETE https://XXXXXXX.execute-api.ap-northeast-1.amazonaws.com/dev/todos/7bbf61e0-6b39-11eb-9f3a-8d5346ce6816
{"id":"7bbf61e0-6b39-11eb-9f3a-8d5346ce6816"}

すべてできましたね!
IDがハッシュ値になっているので長くなっていますが、正常にCURDできました!!

リソースを削除するときはsls removeで削除できます。
ただしDynamoDBのテーブルは削除できないので、手動削除が必要です。