Serverless REST api for Amazon SNS


So it has been a while since I posted some technical posts. Now … this is something that touches us all – the lack of time in the jungle of ongoing projects 🙂 However today we will look into something which I find quite nice for developing of new applications.

Solution is based on serverless framework. Now before we go on – we all know that serverless is a nice catchy word for ‘someone’ else computer and operation problem :)’ . But idea is simple – I’m using AWS – and there it spins me up lambda functions with associated API gateway.

I decided to create this solution to have unified way of deploying and interacting with AWS services in a way that would be easiest for me to consume. However for someone else it might not be the best. Also to be on safe side – this code is really version v1.0.0 so it will get updates as we go ( PR always welcome )

The repository for this write up is available under https://github.com/RafPe/serverless-api-sns

Solution folder structure

The solution structure is created as follows

total 32
-rw-r--r--    1 rafpe  450652656   1.0K Sep  9 17:49 LICENSE
-rw-r--r--    1 rafpe  450652656   2.2K Sep 10 15:07 README.md
drwxr-xr-x  234 rafpe  450652656   7.8K Sep  5 23:53 node_modules
-rw-r--r--    1 rafpe  450652656   255B Sep 10 14:19 package.json
-rw-r--r--    1 rafpe  450652656   3.6K Sep 10 14:21 serverless.yml
drwxr-xr-x    7 rafpe  450652656   238B Sep 10 14:52 sns

and the SNS folder:

├── attributes
├── endpoint
│   ├── create.js
│   ├── delete.js
│   └── list.js
├── messages
├── models
│   └── endpoint.create.json
└── topics


Now since this is not a coding school and I have used really simple code I will not be going into details there. I just might say code has some portions which are repeated and could be wrapped into common methods 😉 did not have time to take a look into that one yet.

For the rest it is using standard aws libraries to execute most of the actions



Is the heart of your deployment. It describes what will be created and how to link those things together. For more advanced examples you should check out docs.serverless.com

# Welcome to Serverless!
# This file is the main config file for your service.
# It's very minimal at this point and uses default values.
# You can always add more config options for more control.
# We've included some commented out config examples here.
# Just uncomment any of them to get that config option.
# For full config options, check the docs:
#    docs.serverless.com
# Happy Coding!

service: api

# You can pin your service to only deploy with a specific Serverless version
# Check out our docs for more details
# frameworkVersion: "=X.X.X"

  name: aws
  role: xmyCustRole1 
    - myApiKey  
  runtime: nodejs6.10
  region: eu-west-1  

stage: dev

    handler: sns/endpoint/create.create
      - http:
          path: endpoint/create
          method: post
          cors: true
          private: true

    handler: sns/endpoint/delete.delete
      - http:
          path: endpoint/delete
          method: delete
          cors: true
          private: true

    handler: sns/endpoint/list.list
      - http:
          path: endpoint/list
          method: post
          cors: true
          private: true

    # PetsModelNoFlatten: 
    #   Type: "AWS::ApiGateway::Model"
    #   Properties: 
    #     RestApiId: {Ref: ApiGatewayRestApi}
    #     ContentType: "application/json"
    #     Description: "Schema for Pets example"
    #     Name: "PetsModelNoFlatten"
    #     Schema: 
    #       Fn::Join: 
    #         - ""
    #         - 
    #           - "{"
    #           - "   \"$schema\": \"http://json-schema.org/draft-04/schema#\","
    #           - "   \"title\": \"PetsModelNoFlatten\","
    #           - "   \"type\": \"array\","
    #           - "   \"items\": {"
    #           - "       \"type\": \"object\","
    #           - "       \"properties\": {"
    #           - "           \"number\": { \"type\": \"integer\" },"
    #           - "           \"class\": { \"type\": \"string\" },"
    #           - "           \"salesPrice\": { \"type\": \"number\" }"
    #           - "       }"
    #           - "   }"
    #           - "}"
      Type: AWS::IAM::Role
        Path: /my/cust/path/
        RoleName: xmyCustRole1
          Version: '2012-10-17'
            - Effect: Allow
                  - lambda.amazonaws.com
              Action: sts:AssumeRole
          - PolicyName: myPolicyName
              Version: '2012-10-17'
                - Effect: Allow # note that these rights are given in the default policy and are required if you want logs out of your lambda(s)
                    - logs:CreateLogGroup
                    - logs:CreateLogStream
                    - logs:PutLogEvents
                  Resource: arn:aws:logs:*:*:log-group:/aws/lambda/*:*:*
                - Effect: Allow # note that these rights are given in the default policy and are required if you want logs out of your lambda(s)
                    - sns:CreatePlatformEndpoint
                  Resource: arn:aws:sns:*:*:*           
                - Effect: "Allow"
                    - "s3:PutObject"
                      - ""
                      - - "arn:aws:s3:::"
                        - "Ref" : "ServerlessDeploymentBucket"



IAM policy

To make this all a bit more secure I defined here my specific IAM Role with custom permissions for actions – So if you would need to extend permisions required you would need to look into that resources as well



In my code you will find that I validate if specific parameters are received from the request. Now this is again something that

  1. Could be done better by taking this logic out into common functions or …
  2. even better to use the API gateway validators

I therefore went ahead and created my self json schema using the following online schema generator. With that one done I had to ‘escape’ those characters and then create a policy using serverless resource

      Type: "AWS::ApiGateway::Model"
        RestApiId: {Ref: ApiGatewayRestApi}
        ContentType: "application/json"
        Description: "Schema for Pets example"
        Name: "PetsModelNoFlatten"
            - ""
              - "{"
              - "   \"$schema\": \"http://json-schema.org/draft-04/schema#\","
              - "   \"title\": \"PetsModelNoFlatten\","
              - "   \"type\": \"array\","
              - "   \"items\": {"
              - "       \"type\": \"object\","
              - "       \"properties\": {"
              - "           \"number\": { \"type\": \"integer\" },"
              - "           \"class\": { \"type\": \"string\" },"
              - "           \"salesPrice\": { \"type\": \"number\" }"
              - "       }"
              - "   }"
              - "}"

This is all nice but the problem I experience now is that I cannot in programatic way find out how to apply required body validators to specific methods using serverless. Maybe something I will find out later.



Deploying is easy as running

serverless deploy

and the output should look like

Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (37.84 KB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
Serverless: Stack update finished...
Service Information
service: api
stage: dev
region: eu-west-1
api keys:
  myApiKey: ID0d9P4Vgi82l2YvndLwi81FA63lCup1adNQX7eD
  POST - https://isr61ohvhl.execute-api.eu-west-1.amazonaws.com/dev/endpoint/create
  DELETE - https://isr61ohvhl.execute-api.eu-west-1.amazonaws.com/dev/endpoint/delete
  POST - https://isr61ohvhl.execute-api.eu-west-1.amazonaws.com/dev/endpoint/list
  create: api-dev-create
  delete: api-dev-delete
  list: api-dev-list



Now this is the part I like the most 🙂 Fun starts here when you play around with the working solution. If you got any feedback I would be more than happy to hear about it.







One Comment

Leave a Reply

Your email address will not be published. Required fields are marked *