Serverless Node apps on AWS Lambda
Tags: node, serverless, aws
Published: 2019-06-22 10:02:16

## What is Serverless?

Functions as services (FaaS). These consist of ephemeral containers that auto-scale and have a pay-per-execution pricing.

Still confused?

It runs your functions in the cloud without the need of a server PERIOD.

## Pros

- **Low-Price**, the cost of this functions being executed are way lower than having your own custom host.
- **Auto-scale**, you don't need to worry about scaling the server since you don't really have a server. AWS Lambda will do it automatically for you as the function receives more and more requests.
- **Zero maintainability**, you don't need to maintain the server hosting your functions.

## Cons

- **Hot-cold fashioned**, the functions are turned off and then on after a request has been made, this will result in a delay on the response.
- **Environment blocked**, you can't install additional packages or software, so if your function depends on a third party package, you can forget about using AWS Lambda.
- **Different environments**, the changes you have made in one instance will not be guaranteed to be persisted in the next instance. All will be wiped out (randomly).

## How to battle against Cons

### Hot-cold fashioned

You can use [WarmUP](https://serverless.com/blog/keep-your-lambdas-warm/)

### Environment blocked

You could tell your function to consume an API that host the software you depend on.

### Different environments

You could store the files you need to persist using AWS S3

## Setup

Visit AWS AIM console, create a new User, next give him access to Programmatic access and finally give him `AdministratorAccess`. Once confirmed, store the **Access key ID** and the **Secret access key**.

Finally install the AWS CLI, and setup the access key **Access key ID** and the **Secret access key**.

`brew install awscli`

`aws configure`

## Serverless Framework

Install the serverless framework.

`npm install -g serverless`

## Manual setup

Let's setup the serverless framework in our app manually. And expose two API endpoints, `create user` and `get one user`.

`serverless create --template aws-nodejs --path api`

`cd api`

`mkdir todos`

```json
// api/package.json
{
  "name": "api-todos",
  "version": "1.0.0",
  "description": "Create and Get one Todo",
  "author": "",
  "license": "MIT",
  "dependencies": {
    "uuid": "^2.0.3"
  }
}
```

Install our dependencies `npm install` (AWS Lambda will install the dependencies as well).

```yml
# api/serverless.yml
service: api

provider:
  name: aws
  runtime: nodejs8.10
  environment:
    DYNAMODB_TABLE: ${self:service}-${opt:stage, self:provider.stage}
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:GetItem
        - dynamodb:PutItem
      Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}"

functions:
  create:
    handler: todos/create.create
    events:
      - http:
          path: todos
          method: post
          cors: true
  get:
    handler: todos/get.get
    events:
      - http:
          path: todos/{id}
          method: get
          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: ${self:provider.environment.DYNAMODB_TABLE}
```

Next create each function file.

```js
// api/todos/create.js
'use strict';

const AWS = require("aws-sdk");
const uuid = require ("uuid/v4");
const client = new AWS.DynamoDB.documentClient();

module.exports.create = async (event) => {
  const data =JSON.parse(event.body);
  const params = {
    TableName: "todos"'
    Item: {
      id: uuid(),
      text: data.text,
      checked: false
    }
  };

  await client.put(params).promise();
  return{
    statusCode: 200,
    body: JSON.stringify(data)
  };
};
```

```js
// api/todos/get.js

'use strict';
const AWS = require("aws-sdk");

const dynamoDb = new AWS.DynamoDB.DocumentClient();

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

  const result = await dynamoDb.get(params).promise();
  if (result.Item) {
    return {
      statusCode:200,
      body: JSON.stringify(result.Item)
    };
  } else {
    return {
      statusCode: 404,
      body: JSON.stringify({ message: "Couldn't find the todo item." })
    };
  }
}
```

- `event` is an object containing the request data.
- `context` is an object containing the AWS info.
- `callback` is a function that will be invoked with an error response as first argument or a valid response as second argument.

## Deploy

Deploying is one of the coolest part.

`sls deploy`

Once deployed you can test the function.

### Create a Todo

`curl -X POST https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/todos --data '{ "text": "Learn Serverless" }'
`
Output: 

`{"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":false,"updatedAt":1479138570824}%
`

### Get one Todo

`# Replace the <id> part with a real id from your todos table
curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/todos/<id>
`

Output:

`{"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":false,"updatedAt":1479138570824}%
`

### More about deployment
You can also deploy JUST the function (This is pretty fast).

`serverless deploy function -f create`

## DynamoDB

When you create a table, you specify how much provisioned throughput capacity you want to reserve for reads and writes. DynamoDB will reserve the necessary resources to meet your throughput needs while ensuring consistent, low-latency performance. You can change the provisioned throughput and increasing or decreasing capacity as needed.

## Concurrent executions

By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 100. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit).

## Handling lots of traffic

If you expect to have a lot of traffic it's recommended to switch to [the auto scaling option for DynamoDB](https://aws.amazon.com/blogs/aws/auto-scale-dynamodb-with-dynamic-dynamodb/).


## Real world use cases

AWS Lambda or any other Lambda could be used for multiple purposes, like:

- Data processing
- Backends
- IoT
- Bots

[Learn more about use cases](https://serverless.com/learn/use-cases/) 

## Final thoughts

No more servers? Well, certainly no. This will not replacement for serves (at least not now). However, it's a great tool to develop micro-services and many other things.