Linking an OpenAPI Specification to AWS Lambda Functions using AWS SAM

Somebody who likes to code
Search for a command to run...

Somebody who likes to code
No comments yet. Be the first to comment.
The RDS Data API is a service that allows us to interact with databases hosted on Amazon RDS using HTTP REST API calls. It provides a secure and easy-to-use interface for executing SQL queries, managing transactions, and fetching results without requ...
Google processes tens of thousands of code changes every day across a codebase of hundreds of millions of lines. The secret isn't magic tooling — it's a documented, principled approach to code review

Amazon Simple Notification Service (SNS) looks straightforward on the surface — publish a message, deliver it to subscribers — but the gap between a topic that "works in dev" and one that is productio

Amazon Simple Queue Service (SQS) is deceptively simple to get started with, but surprisingly easy to misconfigure in ways that cause silent message loss, runaway costs, duplicate processing storms, o

HTTP API is the right default choice for the majority of .NET serverless workloads. At 70% less cost than REST API, lower latency, and native JWT authorization, it covers most production requirements

AWS Lambda has become a cornerstone of modern serverless architectures, but writing a function that "just works" is very different from writing one that is performant, cost-effective, secure, and read

The OpenAPI Specification (OAS) defines a standard, language-agnostic interface to HTTP APIs which allows both humans and computers to discover and understand the capabilities of the service without access to source code, documentation, or through network traffic inspection. When properly defined, a consumer can understand and interact with the remote service with a minimal amount of implementation logic.
In the .NET world, documenting our APIs using the OpenAPI specification is a relatively simple task, as demonstrated in the following link. How can we accomplish the same when working with AWS Lambda functions? In this article, we will attempt to answer that question, although with a little extra effort.
Install the AWS CLI.
Install the AWS SAM CLI.
Download the sample code here. Open the solution and navigate to the template.yaml file:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
SAM Template
Globals:
Function:
Timeout: 30
MemorySize: 512
Architectures:
- x86_64
Environment:
Variables:
TABLE_NAME:
Ref: DynamoTable
Resources:
DynamoTable:
Type: AWS::Serverless::SimpleTable
Properties:
PrimaryKey:
Name: petId
Type: String
TableName: petstable
ApiGatewayApi:
Type: AWS::Serverless::Api
Properties:
Name: petsapi
StageName: Prod
Cors:
AllowMethods: "'*'"
AllowHeaders: "'*'"
AllowOrigin: "'*'"
RegisterFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: dotnet6
Handler: PetsApi::PetsApi.Function::Register
CodeUri: ./src/PetsApi/
Policies:
- DynamoDBCrudPolicy:
TableName:
Ref: DynamoTable
Events:
RegisterPet:
Type: Api
Properties:
Path: /pets
Method: post
RestApiId:
Ref: ApiGatewayApi
GetFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: dotnet6
Handler: PetsApi::PetsApi.Function::Get
CodeUri: ./src/PetsApi/
Policies:
- DynamoDBCrudPolicy:
TableName:
Ref: DynamoTable
Events:
GetPet:
Type: Api
Properties:
Path: /pets/{petId}
Method: get
RestApiId:
Ref: ApiGatewayApi
ListFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: dotnet6
Handler: PetsApi::PetsApi.Function::List
CodeUri: ./src/PetsApi/
Policies:
- DynamoDBCrudPolicy:
TableName:
Ref: DynamoTable
Events:
ListPet:
Type: Api
Properties:
Path: /pets
Method: get
RestApiId:
Ref: ApiGatewayApi
EditFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: dotnet6
Handler: PetsApi::PetsApi.Function::Edit
CodeUri: ./src/PetsApi/
Policies:
- DynamoDBCrudPolicy:
TableName:
Ref: DynamoTable
Events:
EditPet:
Type: Api
Properties:
Path: /pets/{petId}
Method: put
RestApiId:
Ref: ApiGatewayApi
Outputs:
PetsApi:
Description: "API Gateway endpoint URL"
Value:
Fn::Sub: "https://${ApiGatewayApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/pets/"
The application consists of four Lambda functions for registering, editing, listing, and retrieving pets from storage (DynamoDB). All the Lambda functions are accessible through an API Gateway, where we have added CORS settings to allow access from any origin (we do not recommend this for real applications). Deploy the application running sam build and sam deploy --guided.
In the solution, navigate to the openapi.yaml file:
openapi: "3.0.1"
info:
title: "petsapi"
version: "1.0"
paths:
/pets:
get:
summary: List pets
responses:
200:
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/ArrayOfPet'
post:
summary: Register pet
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/RegisterPetRequest'
required: true
responses:
200:
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/RegisterPetResponse'
/pets/{petId}:
get:
summary: Get pet
parameters:
- name: "petId"
in: "path"
required: true
schema:
type: "string"
responses:
200:
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
400:
description: Not found
put:
summary: Edit pet
parameters:
- name: "petId"
in: "path"
required: true
schema:
type: "string"
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/EditPetRequest'
required: true
responses:
200:
description: Successful operation
400:
description: Not found
components:
schemas:
ArrayOfPet:
type: "array"
items:
$ref: "#/components/schemas/Pet"
RegisterPetRequest:
type: object
properties:
type:
type: string
name:
type: string
required:
- type
- name
RegisterPetResponse:
type: object
properties:
petId:
type: string
required:
- petId
EditPetRequest:
type: object
properties:
type:
type: string
name:
type: string
required:
- name
- type
Pet:
type: object
properties:
petId:
type: string
type:
type: string
name:
type: string
required:
- petId
- type
- name
Navigate to https://editor.swagger.io/ and paste the content above to see a visual representation of the OpenAPI specification:

API Gateway can be configured using an OpenAPI specification that includes a set of extensions to capture most of its specific properties, such as integration with Lambda functions. The easiest way to set a custom OpenAPI specification is by modifying the existing one that is generated by default. Run the following command to export the OpenAPI specification from the API Gateway:
aws apigateway get-export --parameters extensions='apigateway' --rest-api-id <rest_api_id>--stage-name Prod --export-type oas30 openapi_with_extensions.yaml --accepts application/yaml
The file contents should look something like this:
openapi: "3.0.1"
info:
title: "petsapi"
version: "1.0"
servers:
- url: "https://ecpgr6wvb6.execute-api.us-east-2.amazonaws.com/{basePath}"
variables:
basePath:
default: "Prod"
paths:
/pets:
get:
x-amazon-apigateway-integration:
type: "aws_proxy"
httpMethod: "POST"
uri: "arn:aws:apigateway:us-east-2:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-2:025381531841:function:petsapi-ListFunction-QNclQ8uvBp8f/invocations"
passthroughBehavior: "when_no_match"
post:
x-amazon-apigateway-integration:
type: "aws_proxy"
httpMethod: "POST"
uri: "arn:aws:apigateway:us-east-2:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-2:025381531841:function:petsapi-RegisterFunction-h7T1XcNCNd30/invocations"
passthroughBehavior: "when_no_match"
options:
responses:
"200":
description: "200 response"
headers:
Access-Control-Allow-Origin:
schema:
type: "string"
Access-Control-Allow-Methods:
schema:
type: "string"
Access-Control-Allow-Headers:
schema:
type: "string"
content: {}
x-amazon-apigateway-integration:
type: "mock"
responses:
default:
statusCode: "200"
responseParameters:
method.response.header.Access-Control-Allow-Methods: "'*'"
method.response.header.Access-Control-Allow-Headers: "'*'"
method.response.header.Access-Control-Allow-Origin: "'*'"
responseTemplates:
application/json: "{}\n"
requestTemplates:
application/json: "{\n \"statusCode\" : 200\n}\n"
passthroughBehavior: "when_no_match"
/pets/{petId}:
get:
parameters:
- name: "petId"
in: "path"
required: true
schema:
type: "string"
x-amazon-apigateway-integration:
type: "aws_proxy"
httpMethod: "POST"
uri: "arn:aws:apigateway:us-east-2:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-2:025381531841:function:petsapi-GetFunction-wpx1KVbQl6fj/invocations"
passthroughBehavior: "when_no_match"
put:
parameters:
- name: "petId"
in: "path"
required: true
schema:
type: "string"
x-amazon-apigateway-integration:
type: "aws_proxy"
httpMethod: "POST"
uri: "arn:aws:apigateway:us-east-2:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-2:025381531841:function:petsapi-EditFunction-2iNi97wo5HYK/invocations"
passthroughBehavior: "when_no_match"
options:
parameters:
- name: "petId"
in: "path"
required: true
schema:
type: "string"
responses:
"200":
description: "200 response"
headers:
Access-Control-Allow-Origin:
schema:
type: "string"
Access-Control-Allow-Methods:
schema:
type: "string"
Access-Control-Allow-Headers:
schema:
type: "string"
content: {}
x-amazon-apigateway-integration:
type: "mock"
responses:
default:
statusCode: "200"
responseParameters:
method.response.header.Access-Control-Allow-Methods: "'*'"
method.response.header.Access-Control-Allow-Headers: "'*'"
method.response.header.Access-Control-Allow-Origin: "'*'"
responseTemplates:
application/json: "{}\n"
requestTemplates:
application/json: "{\n \"statusCode\" : 200\n}\n"
passthroughBehavior: "when_no_match"
components: {}
The presence of the x-amazon-apigateway-integration extension specifies the integration used for each method against the backend.
type: The type of integration with the backend. Use aws_proxy for integration with AWS Lambda functions.
httpMethod: The HTTP method used in the integration request. For Lambda function invocations, the value must be POST.
uri: The endpoint URI of the backend. For integrations of the aws_proxy type, the value should be arn:aws:apigateway:<region_arn>:lambda:path/2015-03-31/functions/<lambda_arn>/invocations.
passthroughBehavior: Specifies how a payload (with an unmapped content type) is passed through the integration request without any changes.
So, let's begin by replacing all the static uris with their corresponding values based on the Lambda functions defined in the template.yaml file:
openapi: "3.0.1"
info:
title: "petsapi"
version: "1.0"
paths:
/pets:
get:
x-amazon-apigateway-integration:
type: "aws_proxy"
httpMethod: "POST"
uri:
Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ListFunction.Arn}/invocations"
passthroughBehavior: "when_no_match"
post:
x-amazon-apigateway-integration:
type: "aws_proxy"
httpMethod: "POST"
httpMethod: "POST"
uri:
Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RegisterFunction.Arn}/invocations"
passthroughBehavior: "when_no_match"
options:
responses:
"200":
description: "200 response"
headers:
Access-Control-Allow-Origin:
schema:
type: "string"
Access-Control-Allow-Methods:
schema:
type: "string"
Access-Control-Allow-Headers:
schema:
type: "string"
content: {}
x-amazon-apigateway-integration:
type: "mock"
responses:
default:
statusCode: "200"
responseParameters:
method.response.header.Access-Control-Allow-Methods: "'*'"
method.response.header.Access-Control-Allow-Headers: "'*'"
method.response.header.Access-Control-Allow-Origin: "'*'"
responseTemplates:
application/json: "{}\n"
requestTemplates:
application/json: "{\n \"statusCode\" : 200\n}\n"
passthroughBehavior: "when_no_match"
/pets/{petId}:
get:
parameters:
- name: "petId"
in: "path"
required: true
schema:
type: "string"
x-amazon-apigateway-integration:
type: "aws_proxy"
httpMethod: "POST"
uri:
Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetFunction.Arn}/invocations"
passthroughBehavior: "when_no_match"
put:
parameters:
- name: "petId"
in: "path"
required: true
schema:
type: "string"
x-amazon-apigateway-integration:
type: "aws_proxy"
httpMethod: "POST"
uri:
Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${EditFunction.Arn}/invocations"
passthroughBehavior: "when_no_match"
options:
parameters:
- name: "petId"
in: "path"
required: true
schema:
type: "string"
responses:
"200":
description: "200 response"
headers:
Access-Control-Allow-Origin:
schema:
type: "string"
Access-Control-Allow-Methods:
schema:
type: "string"
Access-Control-Allow-Headers:
schema:
type: "string"
content: {}
x-amazon-apigateway-integration:
type: "mock"
responses:
default:
statusCode: "200"
responseParameters:
method.response.header.Access-Control-Allow-Methods: "'*'"
method.response.header.Access-Control-Allow-Headers: "'*'"
method.response.header.Access-Control-Allow-Origin: "'*'"
responseTemplates:
application/json: "{}\n"
requestTemplates:
application/json: "{\n \"statusCode\" : 200\n}\n"
passthroughBehavior: "when_no_match"
components: {}
The next step involves merging the content of the openapi.yaml file with the text provided above:
openapi: "3.0.1"
info:
title: "petsapi"
version: "1.0"
paths:
/pets:
get:
summary: List pets
x-amazon-apigateway-integration:
httpMethod: "POST"
uri:
Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ListFunction.Arn}/invocations"
passthroughBehavior: "when_no_match"
type: "aws_proxy"
responses:
200:
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/ArrayOfPet'
post:
summary: Register pet
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/RegisterPetRequest'
required: true
x-amazon-apigateway-integration:
httpMethod: "POST"
uri:
Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RegisterFunction.Arn}/invocations"
passthroughBehavior: "when_no_match"
type: "aws_proxy"
responses:
200:
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/RegisterPetResponse'
/pets/{petId}:
get:
summary: Get pet
parameters:
- name: "petId"
in: "path"
required: true
schema:
type: "string"
x-amazon-apigateway-integration:
httpMethod: "POST"
uri:
Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetFunction.Arn}/invocations"
passthroughBehavior: "when_no_match"
type: "aws_proxy"
responses:
200:
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
400:
description: Not found
put:
summary: Edit pet
parameters:
- name: "petId"
in: "path"
required: true
schema:
type: "string"
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/EditPetRequest'
required: true
x-amazon-apigateway-integration:
httpMethod: "POST"
uri:
Fn::Sub: "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${EditFunction.Arn}/invocations"
passthroughBehavior: "when_no_match"
type: "aws_proxy"
responses:
200:
description: Successful operation
400:
description: Not found
components:
schemas:
ArrayOfPet:
type: "array"
items:
$ref: "#/components/schemas/Pet"
RegisterPetRequest:
type: object
properties:
type:
type: string
name:
type: string
required:
- type
- name
RegisterPetResponse:
type: object
properties:
petId:
type: string
required:
- petId
EditPetRequest:
type: object
properties:
type:
type: string
name:
type: string
required:
- name
- type
Pet:
type: object
properties:
petId:
type: string
type:
type: string
name:
type: string
required:
- petId
- type
- name
The final step is to modify the ApiGatewayApi resource in the template.yaml file:
ApiGatewayApi:
Type: AWS::Serverless::Api
Properties:
Name: petsapi
StageName: Prod
Cors:
AllowMethods: "'*'"
AllowHeaders: "'*'"
AllowOrigin: "'*'"
OpenApiVersion: '3.0.1'
DefinitionBody:
'Fn::Transform':
Name: 'AWS::Include'
Parameters:
Location: openapi_with_extensions.yaml
The Fn::Transform and AWS::Include directives instruct to retrieve the file from the file system and dynamically evaluate all items before inserting them into the final template that is executed.
Now that we have our specification ready run sam build and sam deploy --guided to deploy the application and aws apigateway get-export --parameters extensions='apigateway' --rest-api-id <rest_api_id>--stage-name Prod --export-type oas30 openapi_specification.yaml --accepts application/yaml to download the new version of the OpenAPI specification generated by API gateway. Copy the content of the file into https://editor.swagger.io/:

Due to the CORS configuration we are using, we can test the API directly from the SwaggerUI itself. In conclusion, by following the steps presented in this article, we can effectively document your AWS Lambda functions using the OpenAPI specification, making it easier for humans and machines to understand and interact with your service. The final code can be found here. Thanks, and happy coding.