serverless web framework for Node.js on AWS

Simone Lusenti
Co-founder & Dev @ plasticpanda
CTO @ GSO Company spa

twitter.com/Lanzone31
github.com/lusentis

Background

AWS Lambda

Amazon API Gateway

Amazon CloudFront

AWS CloudFormation

AWS Lambda

// index.js

exports.handler = function (event, context, callback) {
   console.log('Received event:', JSON.stringify(event, null, 2));

   // logic here...

   callback(null, {
     response: 'Awesome!'
   });
};

deploying single functions to the AWS cloud, without any server to manage

$ aws lambda create-function \
    --function-name XXX \
    --function-code fileb://index.zip

Amazon API Gateway

creating and managing HTTP(S) Endpoints

# Call an AWS Lambda Function via HTTP (REST)
POST http://abcdef123.execute-api.eu-west-1.amazonaws.com/prod/weather
  • each HTTP path can be connected to an AWS Lambda Function
  • response can be text/html, application/json, text/plain, etc...

Creating the infrastructure

... using the AWS Console or the AWS CLI?


$ zip index.zip index.js node-modules/**/*

$ aws iam create-role \
    --role-name RoleForLambdaFunc1 \
    --policy-document ‘{JSON here...}’ \
    --trust-policy ‘{JSON here...}’ 

$ aws lambda create-function \
    --function-name LambdaFunc1 \
    --function-code fileb://index.zip \
    --runtime nodejs4.3 \
    --memory 512 \	
    --timeout 30 \
    --handler index.handler \
    --role arn:aws:iam:XXX:XXX:role/XXX
    
  • Easy to replicate
    • failover
    • redundancy in other regions
  • Tracking infrastructure changes
    • rolling back mistakes

Infrastructure-as-Code

Creating the infrastructure

  • Defined as plain text files

AWS CloudFormation

an easy way to create and manage a collection of related AWS resources, provisioning and updating them in an orderly and predictable fashion.

AWS CloudFormation

AWS CloudFormation

"API": {
  "Type": "AWS::ApiGateway::RestApi",
  "Properties": {
    "Description": "REST API for dawson app",
    "Name": "AppAPIDevel"
  }
},
"PermissionForLambdaBarAPI": {
  "Type": "AWS::Lambda::Permission",
  "Properties": {
    "Action": "lambda:InvokeFunction",
    "FunctionName": {
      "Fn::Sub": "\x24{LambdaBarAPI.Arn}"
    },
    "Principal": "apigateway.amazonaws.com",
    "SourceArn": {
      "Fn::Sub": "arn:aws:execute-api:\x24{AWS::Region}:\x24{AWS::AccountId}:\x24{API}/prod*"
    }
  }
},
"ExecutionRoleForLambdaBarAPI": {
  "Type": "AWS::IAM::Role",
  "Properties": {
    "AssumeRolePolicyDocument": {
      "Version": "2012-10-17",
      "Statement": [{
        "Effect": "Allow",
        "Principal": {
            "Service": [
              "lambda.amazonaws.com"
            ],
            "AWS": [{ "Fn::Sub": "arn:aws:iam::\x24{AWS::AccountId}:root" }]},
      "Action": [
        "sts:AssumeRole"
      ]
    }
  ]
},
"Path": "/",
"Policies": [
  {
    "PolicyName": "dawson-policy",
    "PolicyDocument": {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": [
            "logs:CreateLogGroup",
            "logs:CreateLogStream",
            "logs:PutLogEvents"
          ],
          "Resource": {
            "Fn::Sub": "arn:aws:logs:\x24{AWS::Region}
            :\x24{AWS::AccountId}:*"
          }
        },
        {
          "Effect": "Allow",
          "Action": [
            "cloudformation:DescribeStacks"
          ],
          "Resource": {
            "Fn::Join": [
              "",
              [
                "arn:aws:cloudformation:",
                {
                  "Ref": "AWS::Region"
                },
                ":",
                {
                  "Ref": "AWS::AccountId"
                },
                ":stack/",
                {
                  "Ref": "AWS::StackName"
                },
                "/*"
              ]
            ]
          }
        }
      ]
    }
  }
]
  }
},

serverless web framework for Node.js apps on AWS

dawson

0. Use cases

  1. API / Backend
  2. Single Page App + API
  3. Server-rendered Pages 

dawson

  • no boilerplate
  • no configuration
  • no runtime dependencies
// api.js

import upperFirst from 'lodash/upperFirst';

export function greet (event) {
    const name = event.params.path.name
    return `Hello ${upperFirst(name)}!`
}
greet.api = {
    path: 'greet/{name}'
}

$ npm install -g dawson
$ dawson deploy

    URL: https://d12345678.cloudfront.net

1. Easy to start with

dawson

automatic transpilation to Node.js 4.3 via babel &

built-in support for
async / await and Promise

// api.js

import fetch from 'node-fetch';

export async function greet (event) {
    const response = 
        await fetch('http://apis.weather.com/today')

    const data = await response.json()

    return `Today's weather: ${data.weather}`;
}
greet.api = {
    path: 'weather/today'
}

2. State-of-the-art JS

dawson

dedicated IAM Role for each function
& fully-customizable permissions

out-of-the-box https:// & automatic provisoning of SSL/TLS Certs for custom domains (for free, via AWS ACM)

3. Built-in security

native support for API Gateway's
Custom Authorizers

dawson

safe Stack Policy to prevent accidental deletions of S3 Buckets, DynamoDB Tables, ...

3. Built-in security

dawson

CloudFront to cache assets &
proxy API requests

deploy and scale to multiple AWS Regions

4. Out-of-the-box scalability

cheap support for multiple stages and
unlimited* stages per Region

dawson

100%
CloudFormation

5. Easy to extend and opt-out

100% customizable

Templates

Stateless

// api.js

import dynamoDBTable
        from 'dawson-snippets/dynamodb-table';

const barTable = dynamoDBTable({
  tableLogicalName: 'TableBar',
  primaryKeyName: 'BarId',
  enableStream: true
});

export function customTemplateFragment() {
  return {
    Resources: barTable
  };
}

dawson

6. Pre-packaged basic infrastructure

// api.js

import pug from 'pug';
const templateFn = pug.compileFile('index.pug');

export function greet (event) {
    return templateFn({ now: Date.now() });
}
greet.api = {
    path: 'greet/{name}'
}

Public Assets

Public URL

dawson

Real-time log streaming


$ dawson log --follow -f userLogin

   dawson v0.23.0 
   myapp ↣ us-east-1 ↣ preview 
   2017-2-28 17:47:16 
 
Tailing logs for Lambda 'myappPreview-LambdaUserLogin-157OXJZ60ULMX' 
Feb 28, 2017 5:47 PM    START RequestId: a2133d5c-fdd5-11e6-9a94-915c63666d37 Version: $LATEST  
Feb 28, 2017 5:47 PM
    a2133d5c-fdd5-11e6-9a94-915c63666d37
    event { params: 
       { path: {},
         querystring: {},
         header: 
          { Accept: 'application/json',
            'Accept-Encoding': 'gzip',
            'Accept-Language': 'en-GB,en;q=0.8,en-US;q=0.6,it;q=0.4',
            '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': 'US',
            'Content-Type': 'application/json',

7. CLI integration with CloudWatch Logs

dawson

8. Awesome development experience


$ dawson dev

dawson

8. Awesome development experience


$ dawson dev

dawson

8. Awesome development experience


$ dawson dev

$ curl 'http://127.0.0.1:3000/prod/bar' -H 'content-type: application/json' -v
* Connected to 127.0.0.1 (127.0.0.1) port 3000 (#0)
> GET /prod/bar HTTP/1.1
> User-Agent: curl/7.50.1
> Accept: */*
> content-type: application/json
> 
< HTTP/1.1 200 OK
< Date: Tue, 28 Feb 2017 17:24:28 GMT
< Content-Type: application/json
< Content-Length: 77
< Connection: keep-alive

{ "foo": "lorem ipsum" }

dawson

8. Awesome development experience

  • runs functions with their own AWS permissions
  • lambci/docker-lambda
    • almost-native AWS Lambda environment
  • run app without deploying, via browser, curl, ...
    (functions & public assets)
  • can run functions locally in response to AWS Events

$ dawson dev

dawson

9. Getting started

// api.js

import upperFirst from 'lodash/upperFirst';

export function greet (event) {
    const name = event.params.path.name
    return `Hello ${upperFirst(name)}, 
            you look awesome!`
}
greet.api = {
    path: 'greet/{name}'
}

$ npm install -g dawson
$ export AWS_REGION=eu-west-1 AWS_PROFILE=bar
$ dawson deploy

    URL: https://d12345678.cloudfront.net

dawson-org/dawson-cli

dawson

(semver starting with 1.0.0)

https://dawson.sh

twitter.com/Lanzone31

This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.

dawson @ serverless meetup 20170303

By Simone Lusenti

dawson @ serverless meetup 20170303

  • 663