Shift from Containers to Serverless Computing using AWS Lambda - Part 3

In a Part 1 blog post we created a SNS topic, Lambda, SNS Topic Subscription and IAM Role with required policies using CloudFormation Template. In Part 2 we updated Lambda to persist SNS event into DynamoDB table. We’ll continue on the this theme and create a full blown REST API using Amazon API Gateway, Lambda and and DynamoDB for persistence. We’ll also switch to Serverless framework instead of raw CloudFormation Template to create complete stack.

Serverless framework provides consistent packaging and deployment experience across cloud providers. It provides an abstraction on top of cloud vendor settings and configurations. This makes it easier to focus on actual functions instead of cloud specific configurations, packaging and deployments. Under the hood Serverless framework uses CloudFormation template for AWS. So Let’s get started. First we need to install Serverless framework using following command -

$ npm install serverless -g

Serverless framework comes with several template projects for quickstart. Let’s use hello-world template to create HelloWorld function.

$ serverless create --template hello-world --path serverless-api-lambda-dynamodb-example

Serverless: Generating boilerplate...
Serverless: Generating boilerplate in "/Users/nd/dev/aws/temp/serverless-api-gateway-lambda-dynamodb-example"
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.27.0
 -------'

Serverless: Successfully generated boilerplate for template: "hello-world"

Above command will create handler.js and serverless.yml files under serverless-api-lambda-dynamodb-example directory. handler.js file contains actual Lambda function and serverless.yml contains Serverless configuration which will be translated to cloudformation template at the time of packaging and deployment.

Let’s take a closer look at the contents of the serveless.yml file -

 1# Welcome to serverless. Read the docs
 2# https://serverless.com/framework/docs/
 3
 4# Serverless.yml is the configuration the CLI
 5# uses to deploy your code to your provider of choice
 6
 7# The `service` block is the name of the service
 8service: serverless-api-lambda-dynamodb-example
 9
10# The `provider` block defines where your service will be deployed
11provider:
12  name: aws
13  runtime: nodejs6.10
14
15# The `functions` block defines what code to deploy
16functions:
17  helloWorld:
18    handler: handler.helloWorld
19    # The `events` block defines how to trigger the handler.helloWorld code
20    events:
21      - http:
22          path: hello-world
23          method: get
24          cors: true

Provider is aws and the runtime is nodejs6.10. Further under functions it defines helloWorld function with handler handler.helloWorld i.e. helloWorld function defined in the handler.js file. This function will be accessible via hello-world path and Get HTTP method. Actual HelloWorld function is very simple. It creates a response with Go serverless... message and input event as evident from following code.

 1'use strict';
 2
 3module.exports.helloWorld = (event, context, callback) => {
 4  const response = {
 5    statusCode: 200,
 6    headers: {
 7      'Access-Control-Allow-Origin': '*', // Required for CORS support to work
 8    },
 9    body: JSON.stringify({
10      message: 'Go Serverless v1.0! Your function executed successfully!',
11      input: event,
12    }),
13  };
14
15  callback(null, response);
16};
17 
18

Let’s fire it up using following command. It will take care of creating package, uploading it to s3 and creating CloudFormation stack.

$ sls deploy

Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (404 B)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
.................................
Serverless: Stack update finished...
Service Information
service: serverless-api-lambda-dynamodb-example
stage: dev
region: us-east-1
stack: serverless-api-lambda-dynamodb-example-dev
api keys:
  None
endpoints:
  GET - https://pd5lhv9w39.execute-api.us-east-1.amazonaws.com/dev/hello-world
functions:
  helloWorld: serverless-api-lambda-dynamodb-example-dev-helloWorld

As evident from console logs, it created the Lambda and API is accessible at endpoint https://pd5lhv9w39.execute-api.us-east-1.amazonaws.com/dev/hello-world

Let’s test the API endpoint using following curl command -

$ curl -i -X GET https://pd5lhv9w39.execute-api.us-east-1.amazonaws.com/dev/hello-world

HTTP/2 200 
content-type: application/json
content-length: 1530
date: Mon, 21 May 2018 21:22:23 GMT
x-amzn-requestid: 0d085c19-5d3d-11e8-a9d3-6773de1969f5
access-control-allow-origin: *
x-amz-apigw-id: HQXGTHotIAMFTUg=
x-amzn-trace-id: Root=1-5b03388e-b113f47b969c20d8c5cc6ab0
x-cache: Miss from cloudfront
via: 1.1 ccffff70b43b15585d7c2b7684176a5a.cloudfront.net (CloudFront)
x-amz-cf-id: 0dfTbO5YwXF8TbVVw1M6GVY4H4wHM3IrJ-rmdGs7hFJFaBlUP2Sn9w==

{"message":"Go Serverless v1.0! Your function executed successfully!","input":{"resource":"/hello-world","path":"/hello-world","httpMethod":"GET","headers":{"Accept":"*/*","CloudFront-Forwarded-Proto":"https","CloudFront-Is-Desktop-Viewer":"true","CloudFront-Is-Mobile-Viewer":"false","CloudFront-Is-SmartTV-Viewer":"false","CloudFront-Is-Tablet-Viewer":"false","CloudFront-Viewer-Country":"NL","Host":"pd5lhv9w39.execute-api.us-east-1.amazonaws.com","User-Agent":"curl/7.54.0","Via":"2.0 ccffff70b43b15585d7c2b7684176a5a.cloudfront.net (CloudFront)","X-Amz-Cf-Id":"qaOTkHriCGbVArriJ9O6HPTNe4kXbjxIw49TrXh8grxNpgOKtQ30YQ==","X-Amzn-Trace-Id":"Root=1-5b03388e-d86f428b60f5cf99cd9f688b","X-Forwarded-For":"5.132.103.162, 54.240.156.121","X-Forwarded-Port":"443","X-Forwarded-Proto":"https"},"queryStringParameters":null,"pathParameters":null,"stageVariables":null,"requestContext":{"resourceId":"v4imrm","resourcePath":"/hello-world","httpMethod":"GET","extendedRequestId":"HQXGTHotIAMFTUg=","requestTime":"21/May/2018:21:22:22 +0000","path":"/dev/hello-world","accountId":"046105881309","protocol":"HTTP/1.1","stage":"dev","requestTimeEpoch":1526937742784,"requestId":"0d085c19-5d3d-11e8-a9d3-6773de1969f5","identity":{"cognitoIdentityPoolId":null,"accountId":null,"cognitoIdentityId":null,"caller":null,"sourceIp":"5.132.103.162","accessKey":null,"cognitoAuthenticationType":null,"cognitoAuthenticationProvider":null,"userArn":null,"userAgent":"curl/7.54.0","user":null},"apiId":"pd5lhv9w39"},"body":null,"isBase64Encoded":false}}

As you might have noticed the default runtime config in serverless.yml is node.js6.10. As AWS started supporting Node.js 8.10 we’ll switch configuration to 8.10 and update HelloWorld lambda code accordingly. We’ll also rename handles.js to hello-controller.js

 1exports.helloWorld = async(event) => {
 2    try {
 3
 4        const response = {
 5            statusCode: 200,
 6            body: JSON.stringify({
 7                message: 'Hello World',
 8                input: event,
 9            }),
10        };
11
12        return response;
13    } catch (err) {
14        console.log(err);
15        throw err;
16    }
17};
18 
19

Now we need to test it again. However frequent redeployment to AWS during development is not handy. That’s were Serverless framework’s excellent serverless-offline plugin comes to rescue. It allows us to test Lambda locally. Let’s install and configure this plugin. While we are at it we’ll also configure serverless-dynamodb-local plugin which will be needed for further testing. Before configuring this plugins we need to add missing package.json file so that we can add dependencies.

$ npm install --save-dev serverless-dynamodb-local

$ npm install --save-dev serverless-offline
 

We need to add entries for these plugins in serverless.yml. Following is Updated and cleaned up serverless.yml

 1service: serverless-api-lambda-dynamodb-example
 2
 3provider:
 4  name: aws
 5  runtime: nodejs8.10
 6  dynamodb:
 7      start:
 8        migrate: true
 9
10functions:
11  helloWorld:
12    handler: handler.helloWorld
13    # The `events` block defines how to trigger the handler.helloWorld code
14    events:
15      - http:
16          path: hello-world
17          method: get
18          cors: true
19
20plugins:
21  - serverless-dynamodb-local
22  - serverless-offline

We need to also install local DynamoDB using following command -

$ sls dynamodb install
 

Now we can fire it up locally using following command -

$ sls offline start
                            
Dynamodb Local Started, Visit: http://localhost:8000/shell
Serverless: Starting Offline: dev/us-east-1.

Serverless: Routes for helloWorld:
Serverless: GET /hello-world

Serverless: Offline listening on http://localhost:3000

We can test hello-world endpoint using the curl command that we had used earlier. Just swap endpoint URL with local one. As you might have noticed from console log, Serverless framework spins up local DynamoDB as well. It is accessible via Web Shell at http://localhost:8000/shell

While this hello-world static endpoint is good enough for demo purpose it’s hardly of any use in real world scenarios. So let’s build a more practical REST API which will create, read, update and delete accounts. It will use DynamoDB table for persistence. We’ll organize this API as controller, service and repository. We’ll also move client creation code in dynamodb-client file. So let’s create respective files.

account-controller.js -

  1const AccountService = require('./account-service').AccountService;
  2const accountService = new AccountService();
  3
  4exports.createAccount = async(event) => {
  5    try {
  6        // extract request body
  7        const accountRequest = JSON.parse(event.body)
  8
  9        // validation
 10        if (!accountRequest.name || !accountRequest.iban) {
 11            const response = {
 12                statusCode: 400,
 13                headers: { 'Content-Type': 'application/json' },
 14                body: JSON.stringify({'code':'invalid_data', 'message': 'name and iban are mandatory'})
 15            };
 16            return response;
 17        }
 18
 19        // create new account
 20        const account = await accountService.createAccount(accountRequest);
 21
 22        // prepare response
 23        const response = {
 24            statusCode: 201,
 25            headers: { 'Content-Type': 'application/json' },
 26            body: JSON.stringify(account)
 27        };
 28
 29        return response;
 30    } catch (err) {
 31        console.log(err);
 32        throw err;
 33    }
 34};
 35
 36exports.getAccount = async(event) => {
 37    try {
 38        // validation
 39        if (!event.pathParameters.id) {
 40            const response = {
 41                statusCode: 400,
 42                headers: { 'Content-Type': 'application/json' },
 43                body: JSON.stringify({'code':'invalid_data', 'message': 'account id is mandatory'})
 44            };
 45            return response;
 46        }
 47
 48        // get account
 49        const account = await accountService.getAccount(event.pathParameters.id);
 50
 51        // prepare response
 52        const response = {
 53            statusCode: 200,
 54            headers: { 'Content-Type': 'application/json' },
 55            body: JSON.stringify(account)
 56        };
 57
 58        return response;
 59    } catch (err) {
 60        console.log(err);
 61        throw err;
 62    }
 63};
 64
 65exports.getAccounts = async(event) => {
 66    try {
 67        // get accounts
 68        const accounts = await accountService.getAccounts();
 69
 70        // prepare response
 71        const response = {
 72            statusCode: 200,
 73            headers: { 'Content-Type': 'application/json' },
 74            body: JSON.stringify(accounts)
 75        };
 76
 77        return response;
 78    } catch (err) {
 79        console.log(err);
 80        throw err;
 81    }
 82};
 83
 84exports.updateAccount = async(event) => {
 85    try {
 86        // extract request body
 87        const accountUpdateRequest = JSON.parse(event.body)
 88
 89        // validation
 90        if (!event.pathParameters.id || !accountUpdateRequest.name || !accountUpdateRequest.iban) {
 91            const response = {
 92                statusCode: 400,
 93                headers: { 'Content-Type': 'application/json' },
 94                body: JSON.stringify({'code':'invalid_data', 'message': 'id, name and iban are mandatory'})
 95            };
 96            return response;
 97        }
 98
 99        // update account
100        const account = await accountService.updateAccount(event.pathParameters.id, accountUpdateRequest);
101
102        // prepare response
103        const response = {
104            statusCode: 200,
105            headers: { 'Content-Type': 'application/json' },
106            body: JSON.stringify(account)
107        };
108
109        return response;
110    } catch (err) {
111        console.log(err);
112        throw err;
113    }
114};
115
116exports.deleteAccount = async(event) => {
117    try {
118        // validation
119        if (!event.pathParameters.id) {
120            const response = {
121                statusCode: 400,
122                headers: { 'Content-Type': 'application/json' },
123                body: JSON.stringify({'code':'invalid_data', 'message': 'account id is mandatory'})
124            };
125            return response;
126        }
127
128        // delete account
129        const account = await accountService.deleteAccount(event.pathParameters.id);
130
131        // prepare response
132        const response = {
133            statusCode: 200,
134            headers: { 'Content-Type': 'application/json' },
135            body: JSON.stringify(account)
136        };
137
138        return response;
139    } catch (err) {
140        console.log(err);
141        throw err;
142    }
143};
144

account-service.js

 1const AccountRepository = require('./account-repository').AccountRepository;
 2const accountRepository = new AccountRepository();
 3
 4class AccountService {
 5
 6    async createAccount(accountRequest) {
 7        try {
 8            return accountRepository.createAccount(accountRequest);
 9        } catch (err) {
10            console.log(err);
11            throw err;
12        }
13    }
14
15    async getAccount(id) {
16        try {
17            return accountRepository.getAccount(id);
18        } catch (err) {
19            console.log(err);
20            throw err;
21        }
22    }
23
24    async getAccounts() {
25        try {
26            return accountRepository.getAccounts();
27        } catch (err) {
28            console.log(err);
29            throw err;
30        }
31    }
32
33    async updateAccount(id, accountUpdateRequest) {
34        try {
35            return accountRepository.updateAccount(id, accountUpdateRequest);
36        } catch (err) {
37            console.log(err);
38            throw err;
39        }
40    }
41
42    async deleteAccount(id) {
43        try {
44            return accountRepository.deleteAccount(id);
45        } catch (err) {
46            console.log(err);
47            throw err;
48        }
49    }
50
51}
52
53exports.AccountService = AccountService;
54

account-repository.js

  1const uuid = require("uuid");
  2const dynamoDbClient = require('./dynamodb-client');
  3
  4// Get account table name from env variable configured through serverless.yml
  5const ACCOUNT_TABLE = process.env.ACCOUNT_TABLE;
  6
  7class AccountRepository {
  8
  9    async createAccount(accountRequest) {
 10        try {
 11            // Prepare account item
 12            const timeStamp = new Date().getTime();
 13            const accountItem = {
 14                TableName: ACCOUNT_TABLE,
 15                Item: {
 16                    id: uuid.v1(),
 17                    name: accountRequest.name,
 18                    iban: accountRequest.iban,
 19                    createdAt: timeStamp,
 20                    updatedAt: timeStamp
 21                }
 22            };
 23
 24            const client = await dynamoDbClient.dynamoDbClient();
 25
 26            console.log("Registering new account");
 27
 28            // insert account in DynamoDB
 29            await client.put(accountItem).promise();
 30
 31            console.log("Registered new account with id " + accountItem.Item.id);
 32
 33            return accountItem.Item;
 34        } catch (err) {
 35            console.log(err);
 36            throw err;
 37        }
 38    }
 39
 40    async getAccount(id) {
 41        try {
 42            // Prepare get account query
 43            const accountParams = {
 44                TableName: ACCOUNT_TABLE,
 45                Key: {
 46                    id: id
 47                }
 48            };
 49
 50            const client = await dynamoDbClient.dynamoDbClient();
 51
 52            console.log("Retrieving account with id " + id);
 53
 54            // get account from DynamoDB
 55            const accountItem = await client.get(accountParams).promise();
 56
 57            console.log("Retrieved account with id " + id);
 58
 59            return accountItem.Item;
 60        } catch (err) {
 61            console.log(err);
 62            throw err;
 63        }
 64    }
 65
 66    async getAccounts() {
 67        try {
 68            // Prepare account scan query
 69            const accountParams = {
 70                TableName: ACCOUNT_TABLE,
 71            };
 72
 73            const client = await dynamoDbClient.dynamoDbClient();
 74
 75            console.log("Retrieving accounts");
 76
 77            // get account from DynamoDB
 78            const accountItems = await client.scan(accountParams).promise();
 79
 80            console.log("Retrieved accounts");
 81
 82            return accountItems.Items;
 83        } catch (err) {
 84            console.log(err);
 85            throw err;
 86        }
 87    }
 88
 89    async updateAccount(id, accountUpdateRequest) {
 90        try {
 91            // Prepare update account query
 92            const timeStamp = new Date().getTime();
 93            const accountItem = {
 94                TableName: ACCOUNT_TABLE,
 95                Key: {
 96                    id: id,
 97                },
 98                ExpressionAttributeNames: {
 99                    '#account_name': 'name',
100                },
101                ExpressionAttributeValues: {
102                    ':newName': accountUpdateRequest.name,
103                    ':newIban': accountUpdateRequest.iban,
104                    ':updatedAt': timeStamp
105                },
106                UpdateExpression: 'SET #account_name = :newName, iban = :newIban, updatedAt = :updatedAt',
107                ReturnValues: 'NONE'
108            };
109
110            const client = await dynamoDbClient.dynamoDbClient();
111
112            console.log("Updating account with id " + id);
113
114            // insert account in DynamoDB
115            await client.update(accountItem).promise();
116
117            console.log("Updated account with id " + id);
118
119            return accountItem.Item;
120        } catch (err) {
121            console.log(err);
122            throw err;
123        }
124    }
125
126
127    async deleteAccount(id) {
128        try {
129            // Prepare delete account query
130            const accountParams = {
131                TableName: ACCOUNT_TABLE,
132                Key: {
133                    id: id
134                }
135            };
136
137            const client = await dynamoDbClient.dynamoDbClient();
138
139            console.log("Deleting account with id " + id);
140
141            // delete account from DynamoDB
142            await client.delete(accountParams).promise();
143
144            console.log("Deleted account with id " + id);
145
146            return;
147        } catch (err) {
148            console.log(err);
149            throw err;
150        }
151    }
152}
153
154exports.AccountRepository = AccountRepository;
155

dynamodb-client.js

When Serverless framework is started in offline mode then IS_OFFLINE env variable is set to true. For offline DynamoDB we need to provide additional options based on this env variable as evident from following code -

 1const AWS = require("aws-sdk");
 2AWS.config.update({region: "us-west-2"});
 3
 4exports.dynamoDbClient = async () => {
 5    try {
 6        let options = {};
 7
 8        if (process.env.IS_OFFLINE === 'true') {
 9            // local dynamodb client config
10            options = {
11                region: 'localhost',
12                endpoint: 'http://localhost:8000'
13            }
14        }
15
16        // Create DynamoDB document client
17        return new AWS.DynamoDB.DocumentClient(options);
18    } catch (err) {
19        console.log(err);
20        throw err;
21    }
22};
23

serverless.yml

Now let’s configure functions in serverless.yml. We also need to add the resource for DynamodDB table.

 1service: serverless-api-lambda-dynamodb-example
 2
 3custom:
 4  accountTableName: 'Account-${self:provider.stage}'
 5  dynamodb:
 6      start:
 7        migrate: true
 8
 9provider:
10  name: aws
11  runtime: nodejs8.10
12
13  stage: dev
14  region: us-west-2
15
16  iamRoleStatements:
17    - Effect: Allow
18      Action:
19        - dynamodb:Query
20        - dynamodb:Scan
21        - dynamodb:GetItem
22        - dynamodb:PutItem
23        - dynamodb:UpdateItem
24        - dynamodb:DeleteItem
25      Resource:
26        - { "Fn::GetAtt": ["AccountDynamoDBTable", "Arn" ] }
27  environment:
28    ACCOUNT_TABLE: ${self:custom.accountTableName}
29
30functions:
31  createAccount:
32    handler: account-controller.createAccount
33    events:
34      - http:
35          path: accounts
36          method: post
37
38  getAccount:
39    handler: account-controller.getAccount
40    events:
41      - http:
42          path: accounts/{id+}
43          method: get
44
45  getAccounts:
46    handler: account-controller.getAccounts
47    events:
48      - http:
49          path: accounts
50          method: get
51
52  updateAccounts:
53    handler: account-controller.updateAccount
54    events:
55      - http:
56          path: accounts/{id+}
57          method: put
58
59  deleteAccount:
60    handler: account-controller.deleteAccount
61    events:
62      - http:
63          path: accounts/{id+}
64          method: delete
65
66  helloWorld:
67    handler: hello-controller.helloWorld
68    events:
69      - http:
70          path: hello
71          method: get
72
73resources:
74  Resources:
75    AccountDynamoDBTable:
76      Type: 'AWS::DynamoDB::Table'
77      Properties:
78        AttributeDefinitions:
79          - AttributeName: 'id'
80            AttributeType: 'S'
81        KeySchema:
82          - AttributeName: 'id'
83            KeyType: 'HASH'
84        ProvisionedThroughput:
85          ReadCapacityUnits: 1
86          WriteCapacityUnits: 1
87        TableName: ${self:custom.accountTableName}
88
89plugins:
90  - serverless-dynamodb-local
91  - serverless-offline

Let’s fire it up locally using following command -

$ sls offline start
                            
Dynamodb Local Started, Visit: http://localhost:8000/shell
Serverless: DynamoDB - created table Account-dev
Serverless: Starting Offline: dev/us-west-2.

Serverless: Routes for createAccount:
Serverless: POST /accounts

Serverless: Routes for getAccount:
Serverless: GET /accounts/{id*}

Serverless: Routes for getAccounts:
Serverless: GET /accounts

Serverless: Routes for updateAccounts:
Serverless: PUT /accounts/{id*}

Serverless: Routes for deleteAccount:
Serverless: DELETE /accounts/{id*}

Serverless: Routes for helloWorld:
Serverless: GET /hello

Serverless: Offline listening on http://localhost:3000

As evident from console log Lambda functions and respective endpoints are provisioned. We are good to deploy it at AWS using following command -

$ sls deploy
                            
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (6.31 MB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
..................................................................................................................
Serverless: Stack update finished...
Service Information
service: serverless-api-lambda-dynamodb-example
stage: dev
region: us-west-2
stack: serverless-api-lambda-dynamodb-example-dev
api keys:
  None
endpoints:
  POST - https://e5atxwgxtk.execute-api.us-west-2.amazonaws.com/dev/accounts
  GET - https://e5atxwgxtk.execute-api.us-west-2.amazonaws.com/dev/accounts/{id+}
  GET - https://e5atxwgxtk.execute-api.us-west-2.amazonaws.com/dev/accounts
  PUT - https://e5atxwgxtk.execute-api.us-west-2.amazonaws.com/dev/accounts/{id+}
  DELETE - https://e5atxwgxtk.execute-api.us-west-2.amazonaws.com/dev/accounts/{id+}
  GET - https://e5atxwgxtk.execute-api.us-west-2.amazonaws.com/dev/hello
functions:
  createAccount: serverless-api-lambda-dynamodb-example-dev-createAccount
  getAccount: serverless-api-lambda-dynamodb-example-dev-getAccount
  getAccounts: serverless-api-lambda-dynamodb-example-dev-getAccounts
  updateAccounts: serverless-api-lambda-dynamodb-example-dev-updateAccounts
  deleteAccount: serverless-api-lambda-dynamodb-example-dev-deleteAccount
  helloWorld: serverless-api-lambda-dynamodb-example-dev-helloWorld

Now that everything is in place, let’s test the REST API using following curl commands -

Create Account -

curl -i -X POST \                                                                            
  https://e5atxwgxtk.execute-api.us-west-2.amazonaws.com/dev/accounts \
  -H 'content-type: application/json' \
  -d '{
        "name":"Foo",
        "iban":"NL25ABNA0435472492"
}'

HTTP/2 201 
content-type: application/json
content-length: 128
date: Mon, 21 May 2018 19:37:38 GMT
x-amzn-requestid: 6ac40494-5d2e-11e8-8b67-718f823c1ffb
x-amz-apigw-id: HQHwPFayPHcFccQ=
x-amzn-trace-id: Root=1-5b032001-f509e6d2538974637bc5ebae
x-cache: Miss from cloudfront
via: 1.1 a459bf9dec7bba4e0a329e8ab2ebd928.cloudfront.net (CloudFront)
x-amz-cf-id: kL3IMgzi4EwmRdQOCZ7fJUyaJKv8fqF3Xju-0MaJes6UgKTwwO0YIw==

{"id":"6b480180-5d2e-11e8-9275-39f4e1e8b11f","name":"Foo","iban":"NL25ABNA0435472492","createdAt":1526931458456,"updatedAt":1526931458456}

Get Account -

curl -i -X GET \ 
  https://e5atxwgxtk.execute-api.us-west-2.amazonaws.com/dev/accounts/6b480180-5d2e-11e8-9275-39f4e1e8b11f
  
HTTP/2 200 
content-type: application/json
content-length: 128
date: Mon, 21 May 2018 19:42:33 GMT
x-amzn-requestid: 1a7b8a28-5d2f-11e8-ace2-3932708348fd
x-amz-apigw-id: HQIeTGmXvHcF5wg=
x-amzn-trace-id: Root=1-5b032128-bbcf64f5c4982abd6f346b31
x-cache: Miss from cloudfront
via: 1.1 9ece10f886f26459a29d505f7dc15d23.cloudfront.net (CloudFront)
x-amz-cf-id: iNDcWZXuJHr02GStW0rUpS9Qrjnq_SI_MV_pmFgWDYKGq_Ygk1J7eg==

{"id":"6b480180-5d2e-11e8-9275-39f4e1e8b11f","name":"Foo","iban":"NL25ABNA0435472492","createdAt":1526931458456,"updatedAt":1526931458456}

Update Account -

curl -i -X PUT \ 
  https://e5atxwgxtk.execute-api.us-west-2.amazonaws.com/dev/accounts/6b480180-5d2e-11e8-9275-39f4e1e8b11f \
  -H 'content-type: application/json' \
  -d '{
        "name":"Bar",
        "iban":"NL25ABNA0435472492"
}'

HTTP/2 200 
content-type: application/json
content-length: 0
date: Mon, 21 May 2018 19:43:20 GMT
x-amzn-requestid: 36d8e4cc-5d2f-11e8-bdd8-83a00846daaf
x-amz-apigw-id: HQIlvGWuvHcFZ0A=
x-amzn-trace-id: Root=1-5b032157-fa9d45eda83d906634f26893
x-cache: Miss from cloudfront
via: 1.1 ce2b03db99d40501c5695fce9dfbb777.cloudfront.net (CloudFront)
x-amz-cf-id: VGNK0CozL3ipCbGWxsrGZjmArmt_pF9FmOutyCPpwbEp8I0h5uYKIQ==

Get Accounts -

curl -i -X GET \
  https://e5atxwgxtk.execute-api.us-west-2.amazonaws.com/dev/accounts                                      

HTTP/2 200
content-type: application/json
content-length: 130
date: Mon, 21 May 2018 19:44:46 GMT
x-amzn-requestid: 69a5f4e4-5d2f-11e8-9194-b13541233785
x-amz-apigw-id: HQIzEHgCPHcFbIQ=
x-amzn-trace-id: Root=1-5b0321ad-c1cc417235030daa049d978f
x-cache: Miss from cloudfront
via: 1.1 4edcf55d6938e557aa2c6e71997d17b4.cloudfront.net (CloudFront)
x-amz-cf-id: XfB58gtI2DtRyWp2c5Qxk3bXg8ympTJyv3MaEH1BN_y8pCDOxs588Q==

[{"iban":"NL25ABNA0435472492","createdAt":1526931458456,"id":"6b480180-5d2e-11e8-9275-39f4e1e8b11f","name":"Bar","updatedAt":1526931800589}]

Delete Account -

curl -i -X DELETE \
  https://e5atxwgxtk.execute-api.us-west-2.amazonaws.com/dev/accounts/6b480180-5d2e-11e8-9275-39f4e1e8b11f
 
HTTP/2 200 
content-type: application/json
content-length: 0
date: Mon, 21 May 2018 19:45:35 GMT
x-amzn-requestid: 87718603-5d2f-11e8-8513-ffa125d6edb3
x-amz-apigw-id: HQI64FymvHcFwWw=
x-amzn-trace-id: Root=1-5b0321df-b021dddac58e20662121f796
x-cache: Miss from cloudfront
via: 1.1 c035b03e455c334ee837503784ad41c8.cloudfront.net (CloudFront)
x-amz-cf-id: KX1uEWob2gImG_KZVQzJZm1mi0lKHM5Aa0c5NvtvJi-rl9jOGxh4tg==

Once you are done playing with the API, you can delete the stack using following command -

sls remove
         
Serverless: Getting all objects in S3 bucket...
Serverless: Removing objects in S3 bucket...
Serverless: Removing Stack...
Serverless: Checking Stack removal progress...
......................................................................
Serverless: Stack removal finished...

Source code is available on GitHub

That’s it. In next blog we’ll do something more advanced using Lambda.

comments powered by Disqus