Build Serverless Applications using CDK and SAM
Tags: aws, serverless, cdk, sam
Published: 2021-05-21 15:56:56

AWS recently announced the public preview of Serverless Application Model (SAM) support for CDK. SAM is an open-source framework that can be used to build, test and deploy serverless applications on AWS. It provides a Lambda-like execution environment that lets you locally build, test, and debug applications. Previously this could only be defined by SAM templates but now it is also possible through the AWS Cloud Development Kit (CDK)!

![alt text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6k2bhe9sr1ukxxusudvo.png)
 
I will guide you through a small demo project to demonstrate how to build a serverless application with AWS CDK and test it locally with AWS SAM.

We will build a simple REST API which shows the current bid or ask price of a certain cryptocurrency on Binance (exchange), expressed in the value of Bitcoin. 

The API expects two query parameters:
- coin: (ETH, DOG, LINK, DOT, ...)
- type: (bid or ask price)

Example of the API call:
```
$ curl "http://127.0.0.1:3000/crypto?type=ask&coin=ETH"
{"coin": "ETH", "price": 0.066225}
```

The setup in AWS will also be pretty straight forward. 
We will set up a Lambda proxy integration in API Gateway

![alt text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0jeqxpod37dzyst85djr.png)

To get started, we need to install the AWS CDK CLI and create a new CDK project. I use Python as client language. 
```
$ npm install -g aws-cdk
$ cdk init app --language python
```
The project structure looks like this:  
```
.
├── README.md
├── app.py
├── cdk.json
├── requirements.txt
├── sam_cdk_demo
│   ├── __init__.py
│   └── sam_cdk_demo_stack.py
└── setup.py
```

The file `sam_cdk_demo/sam_cdk_demo_stack.py` should contain our code to define the AWS cloud resources we need but first let's start with writing our Lambda.

Create a folder inside the root of the project called _"lambda"_ and add a `handler.py`. The [ccxt](https://github.com/ccxt/ccxt) library is used by our Lambda to interact with the Binance API. The Lambda itself is very basic on purpose.

```python
import ccxt
import json


# use CCXT library to connect with Binance API
exchange = getattr(ccxt, 'binance')({
    'timeout': 3000,
    'enableRateLimit': True
})

def get_current_price(coin_name, price_type):
    # fetch latest ticker data for coin pair xxx/BTC
    ticker = exchange.fetch_ticker('{}/BTC'.format(coin_name))
    # get ask/bid price from ticket data
    current_price = ticker[price_type]
    return current_price

def lambda_handler(event, context):
    # get values from query string parameters
    coin = event['queryStringParameters']['coin']
    price = event['queryStringParameters']['type']

    # CCXT exchange expects coin in uppercase
    valid_coin = coin.upper()

    # get current price based on coin name and price type (ask/bid)
    current_price = get_current_price(valid_coin, price)

    return {
        'statusCode': 200,
        'headers': { 
            'Content-Type': 'application/json'
        },
        'body': json.dumps({
            'coin': valid_coin,
            'price': current_price,
        })
    }
```

Don't forget to add a `requirements.txt` inside the folder to make the ccxt library available to the Lambda.
```
ccxt==1.50.13
```

The Lambda is ready! Now we will use AWS CDK to define our AWS infrastructure. We need to deploy the Lambda and create an API Gateway in front of it. Update the file `demo/demo_stack.py`. We keep the code pretty basic again.

```python
from aws_cdk import (
    aws_lambda as _lambda,
    aws_apigateway as apigw,
    core,
)

class CdkLambdaSamStack(core.Stack):

    def __init__(self, scope: core.Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # creating Lambda function that will be triggered by the API Gateway
        get_price_handler = _lambda.Function(self,'CryptoFunction',
            handler='handler.lambda_handler',
            runtime=_lambda.Runtime.PYTHON_3_8,
            code=_lambda.Code.asset('lambda'),
            timeout=core.Duration.seconds(30),
        )

        # create REST API
        api = apigw.RestApi(self, 'crypto-api')

        # add resource /crypto
        resource = api.root.add_resource('crypto')

        # create Lambda integration 
        get_crypto_integration = apigw.LambdaIntegration(get_price_handler)

        # add method which requires two query string parameteres (coin and type)    
        resource.add_method(
            http_method='GET',
            integration=get_crypto_integration,
            request_parameters={
                'method.request.querystring.coin': True,
                'method.request.querystring.type': True
            },
            request_validator_options=apigw.RequestValidatorOptions(validate_request_parameters=True)
        )
```
Update the `requirements.txt` in the project root with the necessary modules.
```
aws-cdk.core
aws-cdk.aws_lambda
aws-cdk.aws_apigateway
```

Start the Python virtual environment which is created by CDK and install the modules.
```
$ source .venv/bin/activate
(.venv)$ pip3 install -r requirements.txt
```

We will use AWS SAM to test our setup locally. It's important to mention that you need to have [Docker](https://docs.docker.com/get-docker/) installed. We will use Docker to build our code. The Lambda will also run inside as a  Lambda-like Docker container.

Prepare the deployment artifact.
```
(.venv)$ sam-beta-cdk build --use-container
```

Start the local API Gateway.
```
$ sam-beta-cdk local start-api
...
* Running on http://127.0.0.1:3000/
```

We can use a tool like [Postman](https://www.postman.com/) (or `curl` or just your browser) to perform calls against our API.
 
![alt text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xa74mjmgtbvj1prmbg5k.png)
 

It takes a few seconds to execute the function because AWS SAM is spinning up a Docker container to execute our code. After the execution the container is destroyed. 

When everything looks fine we can deploy it to AWS. 
```
(.venv)$ cdk bootstrap
(.venv)$ cdk deploy -a .aws-sam/build
```

Now test against the deployed API.
![alt text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p5wwbqs1kech0j05bmli.png)
 
![alt text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r49pzbb9scyg77dlj5zn.png)

We were able to test our API and Lambda using the new Serverless Application Model integration with CDK! You can find all code [on my GitHub](https://github.com/lvthillo/sam-cdk-demo). Be aware that this feature is in preview. Feel free to do more extensive testing. You can report bugs and submit feature requests to the [SAM opensource repository](https://github.com/aws/aws-sam-cli).