Creating a serverless API using AWS Lambda Powertools and CDK
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:
- Tracing
- Logging
- Event handling
- Typing / Validation
- 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 resolve
s 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:
- API Gateway creation
- Lambda handler configuration
- Resource path
{proxy+}
creation 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:
- Using the AWS lambda layer construct provided by AWS (requires Docker)
- 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!