how.wtf

Creating a serverless API using AWS Lambda Powertools and CDK

· Thomas Taylor

API Gateway and Lambda Logo architecture image

Amazon API Gateway + AWS Lambda is a powerful duo for creating easy-to-deploy scalable infrastructure that is immediately accessible by thousands of users. For this post, we will create an API Gateway with an lambda proxy integration using the AWS Lambda Powertools library and the AWS CDK.

In just a few lines of code, we’ll have our API deployed and ready to go! This tutorial assumes you know what the Amazon API Gateway and AWS Lambda services are.

What is AWS Lambda Powertools

AWS Lambda Powertools is an SDK released by Amazon that aims to increase developer productiviy by bundling similar functionality together:

  1. Tracing
  2. Logging
  3. Event handling
  4. Typing / Validation
  5. Middleware

and more!

With this SDK, we’ll build a single lambda integration that will handle all the routes of our API Gateway.

If you’ve used Flask, Spring Boot, express, etc., this following code snippet should look familiar:

 1from aws_lambda_powertools import Logger, Tracer
 2from aws_lambda_powertools.event_handler import APIGatewayRestResolver
 3from aws_lambda_powertools.logging import correlation_paths
 4from aws_lambda_powertools.utilities.typing import LambdaContext
 5from aws_lambda_powertools.utilities.data_classes import event_source
 6from aws_lambda_powertools.utilities.data_classes.api_gateway_authorizer_event import (
 7    APIGatewayAuthorizerRequestEvent,
 8)
 9
10tracer = Tracer()
11logger = Logger()
12app = APIGatewayRestResolver()
13
14
15@app.get("/pets")
16@tracer.capture_method
17def get_pets():
18    return {"pets": []}
19
20
21@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST)
22@tracer.capture_lambda_handler
23@event_source(data_class=APIGatewayAuthorizerRequestEvent)
24def lambda_handler(
25    event: APIGatewayAuthorizerRequestEvent, context: LambdaContext
26) -> dict:
27    return app.resolve(event.raw_event, context)

The APIGatewayRestResolver behaves like a router. The lambda_handler processes the API Gateway event then resolves the route using the information that was passed from the lambda context. We’ll reference this example when creating our own.

How to deploy an API Gateway with Proxy Lambda with Powertools

Follow the instructions below to deploy a Lambda Function with Powertools bundled for an API Gateway proxy integration.

Create a requirements.txt

For this tutorial, version 2.118.0 of the AWS CDK was used.

1aws-cdk-lib==2.118.0
2constructs>=10.0.0,<11.0.0

Install the dependencies

1pip3 install -r requirements.txt

Create /handler folder with an index.py file

1import json
2
3
4def handler(event, context):
5    return {"statusCode": 200, "body": json.dumps({"message": "Hello world!"})}

Create stack.py with an API Gateway and a lambda router

 1from os import path
 2
 3import aws_cdk as cdk
 4import aws_cdk.aws_lambda as lambda_
 5import aws_cdk.aws_apigateway as apigateway
 6
 7
 8class ApiStack(cdk.Stack):
 9    def __init__(self, scope: cdk.App, construct_id: str, **kwargs) -> None:
10        super().__init__(scope, construct_id, **kwargs)
11
12        lambda_function = lambda_.Function(
13            self,
14            "Function",
15            runtime=lambda_.Runtime.PYTHON_3_11,
16            handler="index.handler",
17            code=lambda_.Code.from_asset(path.join(path.dirname(__file__), "handler")),
18        )
19
20        api = apigateway.LambdaRestApi(self, "API", handler=lambda_function)

The AWS CDK provides “L3” constructs; constructs that abstract away configuration we otherwise explicitly need define. In the case above, the L3 construct named LambdaRestApi handles a few things:

  1. API Gateway creation
  2. Lambda handler configuration
  3. Resource path {proxy+} creation
  4. ANY method creation

Create app.py

1import aws_cdk as cdk
2
3from stack import ApiStack
4
5app = cdk.App()
6ApiStack(app, "ApiStack")
7
8app.synth()

Create cdk.json

1{
2  "app": "python3 app.py"
3}

The directory structure should look like this:

1project/
2├── app.py
3├── cdk.json
4├── handler
5│   └── index.py
6├── requirements.txt
7└── stack.py

Deploy the stack

1cdk deploy

After the stack is deployed, we’re given the API endpoint in a stack output:

1Outputs:
2ApiStack.APIEndpoint1793E782 = https://{api_id}.execute-api.us-east-1.amazonaws.com/prod/

Testing the proxy integration

At this point, we can send any HTTP request and always get back the same response:

1curl https://{api_id}.execute-api.us-east-1.amazonaws.com/prod/

Output:

1{"message": "Hello world!"}

Add Lambda Powertools lambda layer using AWS CDK + SAR

Rather than write our own routing function based on the API Gateway event, let’s use AWS Lambda Powertools.

I have two preferred options for adding the Lambda Powertools layer:

  1. Using the AWS lambda layer construct provided by AWS (requires Docker)
  2. Explicitly using the AWS managed layer

For the purposes of this tutorial, I’ll reference the lambda layer using the serverless application respository.

 1from os import path
 2
 3import aws_cdk as cdk
 4import aws_cdk.aws_lambda as lambda_
 5import aws_cdk.aws_apigateway as apigateway
 6import aws_cdk.aws_sam as sam
 7
 8POWERTOOLS_BASE_NAME = "AWSLambdaPowertools"
 9POWERTOOLS_VERSION = "2.30.2"
10POWERTOOLS_ARN = "arn:aws:serverlessrepo:eu-west-1:057560766410:applications/aws-lambda-powertools-python-layer"
11
12
13class ApiStack(cdk.Stack):
14    def __init__(self, scope: cdk.App, construct_id: str, **kwargs) -> None:
15        super().__init__(scope, construct_id, **kwargs)
16
17        powertools_app = sam.CfnApplication(
18            self,
19            f"{POWERTOOLS_BASE_NAME}Application",
20            location={
21                "applicationId": POWERTOOLS_ARN,
22                "semanticVersion": POWERTOOLS_VERSION,
23            },
24        )
25
26        powertools_layer_arn = powertools_app.get_att(
27            "Outputs.LayerVersionArn"
28        ).to_string()
29
30        powertools_layer_version = lambda_.LayerVersion.from_layer_version_arn(
31            self, f"{POWERTOOLS_BASE_NAME}", powertools_layer_arn
32        )
33
34        lambda_function = lambda_.Function(
35            self,
36            "Function",
37            runtime=lambda_.Runtime.PYTHON_3_11,
38            handler="index.handler",
39            code=lambda_.Code.from_asset(path.join(path.dirname(__file__), "handler")),
40            layers=[powertools_layer_version],
41        )
42
43        api = apigateway.LambdaRestApi(self, "API", handler=lambda_function)

Using AWS Lambda Powertools

Now that the lambda is successfully configured for AWS Lambda Powertools, let’s experiment using a small todo app:

 1from aws_lambda_powertools import Logger, Tracer
 2from aws_lambda_powertools.event_handler import APIGatewayRestResolver
 3from aws_lambda_powertools.logging import correlation_paths
 4from aws_lambda_powertools.utilities.typing import LambdaContext
 5
 6tracer = Tracer()
 7logger = Logger()
 8app = APIGatewayRestResolver()
 9
10
11@app.get("/tasks")
12@tracer.capture_method
13def get_tasks():
14    # database lookup goes here
15    return [{"id": "ptvWZ3", "text": "hello!"}, {"id": "cqDUr3", "text": "another!"}]
16
17
18@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST)
19@tracer.capture_lambda_handler
20def handler(event: dict, context: LambdaContext) -> dict:
21    return app.resolve(event, context)

Now we can test the different routes:

1curl https://{api_id}.execute-api.us-east-1.amazonaws.com/prod/

Output:

1{"statusCode":404,"message":"Not found"}

and the real /tasks endpoint:

1curl https://{api_id}.execute-api.us-east-1.amazonaws.com/prod/tasks

Output:

1[{"id":"ptvWZ3","text":"hello!"},{"id":"cqDUr3","text":"another!"}]

Conclusion

At this point, you will have a functioning AWS CDK application with an API Gateway Lambda Proxy integration setup! While the /tasks code is rudimentary, it provides the foundation for expansion for your own API!

Happy building!

#python   #aws   #aws-cdk   #serverless  

Reply to this post by email ↪