Intro to Serverless API Workshop

Evolution of development systems

hardware

virtual machine

Cloud-upload-files-server

containers and clusters

Serverless Computing

functions as a service (FaaS)

serverless databases

state machines

Lets Get Started

node.js
v4 or higher
Atom IDE
unless you love vim / emacs or have another IDE
AWS Account
can get for free

Get `em if you don't have `em

serverless platform account
login with github account

Super quick serverless start

mkdir ~/intro-to-serverless
cd ~/intro-to-serverless
mkdir hello-world-serverless
cd hello-world-serverless
npm install -g serverless
serverless login
serverless create -t aws-nodejs

In Terminal:

In serverless.com:

1. Create an app called: 
   hello-world-serverless

Super quick start, cont.

1. Open project folder: 
   intro-to-serverless
2. Open file: intro-to-serverless/hello-world-   serverless/serverless.yml
3. Change line #14 - #16 to:

In Atom:

service: hello-world-serverless
app: serverless-workshop
tenant: [put your serverless id / github id here]
serverless deploy

In Terminal:

4. After line #60 add:
    events:
      - http:
          path: /hello
          method: get
cd ~/intro-to-serverless
mkdir puppy-tracker && cd puppy-tracker
npm init -f
npm install --save express serverless-http aws-sdk body-parser
npm install --save-dev serverless-offline serverless-dynamodb-local

In Terminal:

In serverless.com:

1. Create a new app called: 
   puppy-tracker

Puppy Tracker API

1. In project folder folder intro-to-serverless/puppy-tracker: 
   create index.js file

In Atom:

Puppy Tracker cont..

const serverless = require('serverless-http');
const express = require('express')
const app = express()

app.get('/', function (req, res) {
  res.send('Hello World!')
})

module.exports.handler = serverless(app);

In Atom

1. In index.js:
service: puppy-tracker
tenant: [put your serverless id / github id here]
app: puppy-tracker

provider:
  name: aws
  runtime: nodejs8.10
  stage: dev
  region: us-east-1

functions:
  app:
    handler: index.handler
    events:
      - http: ANY /
      - http: 'ANY {proxy+}'
Create new file serverless.yml file in same directory and
​add the following code:
Deploy:
serverless deploy -v
service: puppy-tracker
tenant: [put your serverless id / github id here]
app: puppy-tracker

custom:
  tableName: 'puppies-table-${self:provider.stage}'

provider:
  name: aws
  runtime: nodejs8.10
  stage: dev
  region: us-east-1
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:Query
        - dynamodb:Scan
        - dynamodb:GetItem
        - dynamodb:PutItem
        - dynamodb:UpdateItem
        - dynamodb:DeleteItem
      Resource:
        - { "Fn::GetAtt": ["PuppiesDynamoDBTable", "Arn" ] }
  environment:
    PUPPIES_TABLE: ${self:custom.tableName}

serverless.yml

functions:
  app:
    handler: index.handler
    events:
      - http: ANY /
      - http: 'ANY {proxy+}'
  getUser:
    handler: index.handler
    events:
      - http: 'GET /puppy/{proxy+}'
  createUser:
    handler: index.handler
    events:
      - http: 'POST /puppies'

resources:
  Resources:
    PuppiesDynamoDBTable:
      Type: 'AWS::DynamoDB::Table'
      Properties:
        AttributeDefinitions:
          -
            AttributeName: puppyId
            AttributeType: S
        KeySchema:
          -
            AttributeName: puppyId
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1
        TableName: ${self:custom.tableName}
serverless.yml
const serverless = require('serverless-http');
const bodyParser = require('body-parser');
const express = require('express')
const app = express()
const AWS = require('aws-sdk');


const PUPPIES_TABLE = process.env.PUPPIES_TABLE;
const dynamoDb = new AWS.DynamoDB.DocumentClient();

app.use(bodyParser.json({ strict: false }));

app.get('/', function (req, res) {
  res.send('Hello World!')
})

index.js
// Get Puppy endpoint
app.get('/puppies/:puppyId', function (req, res) {
  const params = {
    TableName: PUPPIES_TABLE,
    Key: {
      puppyId: req.params.puppyId,
    },
  }

  dynamoDb.get(params, (error, result) => {
    if (error) {
      console.log(error);
      res.status(400).json({ error: 'Could not get puppy' });
    }
    if (result.Item) {
      const {puppyId, name} = result.Item;
      res.json({ puppyId, name });
    } else {
      res.status(404).json({ error: "Puppy not found" });
    }
  });
})
index.js
// Create Puppy endpoint
app.post('/puppies', function (req, res) {
  const { puppyId, name } = req.body;
  if (typeof puppyId !== 'string') {
    res.status(400).json({ error: '"puppyId" must be a string' });
  } else if (typeof name !== 'string') {
    res.status(400).json({ error: '"name" must be a string' });
  }

  const params = {
    TableName: PUPPIES_TABLE,
    Item: {
      puppyId: puppyId,
      name: name,
    },
  };

  dynamoDb.put(params, (error) => {
    if (error) {
      console.log(error);
      res.status(400).json({ error: 'Could not create puppy' });
    }
    res.json({ puppyId, name });
  });
})

module.exports.handler = serverless(app);
index.js

Test Routes

$ serverless deploy -v
Deploy:
curl -H "Content-Type: application/json" \
-X POST ${BASE_DOMAIN}/puppies \
-d '{"puppyId": "rudy1234", "name": "Rudy"}'
Run curl command to create puppy:
curl -H "Content-Type: application/json" \
-X GET ${BASE_DOMAIN}/puppies/rudy1234
Retrieve puppy:
export BASE_DOMAIN=[you can find this in your dashboard or after a deploy]
Set the BASE_DOMAIN variable to your unique domain and base path so it's easier to reuse:

Local Development

plugins:
  - serverless-dynamodb-local
  - serverless-offline

custom:
  tableName: 'puppies-table-${self:provider.stage}'
  dynamodb:
    start:
      migrate: true
In serverless.yml:
 - add dynamodb to custom block
 - add new plugins block
serverless dynamodb install
Run command to install DynamoDB local:

Local Development

const serverless = require('serverless-http');
const bodyParser = require('body-parser');
const express = require('express')
const app = express()
const AWS = require('aws-sdk');

const PUPPIES_TABLE = process.env.PUPPIES_TABLE;

const IS_OFFLINE = process.env.IS_OFFLINE;
let dynamoDb;
if (IS_OFFLINE === 'true') {
  dynamoDb = new AWS.DynamoDB.DocumentClient({
    region: 'localhost',
    endpoint: 'http://localhost:8000'
  })
  console.log(dynamoDb);
} else {
  dynamoDb = new AWS.DynamoDB.DocumentClient();
};

app.use(bodyParser.json({ strict: false }));

... rest of application code ...
In index.js:
 - change beginning to following:

Local Development

$ serverless offline start
Start offline server:
curl -H "Content-Type: application/json" \
-X POST http://localhost:3000/puppies \
-d '{"puppyId": "rudy1234", "name": "Rudy"}'
In seperate terminal run curl command to create puppy:
curl -H "Content-Type: application/json" \
-X GET http://localhost:3000/puppies/rudy1234
Retrieve puppy:

Resources

Intro to Serverless API Workshop

By Julia Jacobs

Intro to Serverless API Workshop

  • 488