Karabonkgoatau
The Cloud Journal
Published in
7 min readSep 20, 2023

--

AWS lambda and API gateway in TERRAFORM.

In this tutorial, you create a REST API through which you invoke a Lambda function using an HTTP request. Your Lambda function will perform create, read, update, and delete (CRUD) operations on a Mariadb. This function is provided here for demonstration, but you will learn to configure an API Gateway REST API that can invoke any Lambda function.

Amazon Web Services (AWS) API Gateway and AWS Lambda are commonly used services for building and deploying serverless applications and APIs. To set up and use these services effectively, you’ll need various resources and configurations. Below, I’ll list and explain the key resources required for AWS API Gateway and Lambda functions:

AWS API Gateway Resources:

  1. API Gateway: This is the core resource that acts as the front door to your APIs. It provides HTTP endpoints for clients to access your application or services. You can create and configure APIs within the API Gateway console or using the AWS CLI.
  2. API Gateway Stage: Stages represent different versions or environments of your API, such as “dev,” “test,” or “prod.” Each stage can have its own configuration, deployment settings, and deployment history.
  3. API Gateway Resources and Methods: Within your API Gateway, you define resources and methods. Resources represent the paths in your API (e.g., /users), while methods are the HTTP verbs (e.g., GET, POST) associated with those resources. You configure each method to connect to a Lambda function or other AWS service.
  4. API Gateway Authorizers: Authorizers control access to your API by validating user credentials or tokens. You can configure authorizers to authenticate users before allowing access to specific API methods.
  5. API Gateway Custom Domain: To use your own domain name (e.g., api.example.com) instead of the default API Gateway URL, you’ll need to configure a custom domain. This involves setting up a domain name, SSL/TLS certificates, and linking it to your API Gateway stage.

AWS Lambda Function Resources:

  1. Lambda Function: This is the serverless compute resource that executes your application code in response to API Gateway requests or other events. Each Lambda function has its own configuration, code, and runtime environment.
  2. Lambda Execution Role: To give Lambda functions access to AWS services and resources, you need to create an IAM (Identity and Access Management) role and attach it to your Lambda function. This role defines what permissions the Lambda function has.
  3. Deployment Package: Your Lambda function code and any dependencies should be packaged as a ZIP file or container image (for Lambda Container support). AWS Lambda retrieves this package and runs your code when triggered.
  4. Environment Variables: You can set environment variables for your Lambda function to provide configuration settings, secrets, or other runtime parameters. These variables can be accessed within your code.
  5. Triggers: For API Gateway integration, you’ll configure API Gateway as a trigger for your Lambda function. This tells Lambda to execute your code when specific API endpoints are accessed. Additionally, Lambda can be triggered by other AWS services like S3, SNS, or CloudWatch Events.

6. VPC Configuration (Optional): If your Lambda function needs to access resources within a Virtual Private Cloud (VPC), you’ll need to configure the function to run within the VPC and specify appropriate security groups and subnets.

7. Dead Letter Queue (DLQ) (Optional): Amazon SQS supports dead-letter queues (DLQ), which other queues (source queues) can target for messages that can’t be processed (consumed) successfully. Dead-letter queues are useful for debugging your application or messaging system because they let you isolate unconsumed messages to determine why their processing didn’t succeed

These are the core resources and configurations needed to set up and operate AWS API Gateway and Lambda functions. Depending on your specific use case, you may require additional AWS services or resources, such as DynamoDB for database storage or AWS Cognito for user authentication and management.

Now lets have a look at the terraform code:

Main.tf

main.tf will contain the main set of configuration for your module

Bucket creation and ownership controls.S3 Object Ownership is an Amazon S3 bucket-level setting that you can use to control ownership of objects uploaded to your bucket and to disable or enable access control lists (ACLs). By default, Object Ownership is set to the Bucket owner enforced setting and all ACLs are disabled.

# Bucket creation and ownership controls
resource "aws_s3_bucket" "lambda_bucket" {
bucket = random_pet.lambda_bucket_name.id
}
resource "aws_s3_bucket_ownership_controls" "Lambda_Bucket_Ownership_Controls" {
bucket = aws_s3_bucket.lambda_bucket.id

rule {
object_ownership = "BucketOwnerPreferred"
}
}

This function takes an incoming event object from Lambda and logs it to the console.Then it returns an object which API Gateway will use to generate an HTTP response

# This function takes an incoming event object from Lambda and logs it to the console. 
# Then it returns an object which API Gateway will use to generate an HTTP response

resource "aws_s3_object" "lambda_hello_world" {
bucket = aws_s3_bucket.lambda_bucket.id

key = "hello-world.zip"
source = data.archive_file.lambda_hello_world.output_path

etag = filemd5(data.archive_file.lambda_hello_world.output_path)
}

The following code is used to define your Lambda function and related resources

#The following code is used to define your Lambda function and related resources.
resource "aws_lambda_function" "hello_world" {
function_name = "HelloWorld"

s3_bucket = aws_s3_bucket.lambda_bucket.id
s3_key = aws_s3_object.lambda_hello_world.key

runtime = "nodejs18.x" # Make sure its the latest version of nodejs run npm npm@latest -g to update in terminal
handler = "hello.handler"

source_code_hash = data.archive_file.lambda_hello_world.output_base64sha256

role = aws_iam_role.lambda_exec.arn
}

resource "aws_cloudwatch_log_group" "hello_world" {
name = "/aws/lambda/${aws_lambda_function.hello_world.function_name}"

retention_in_days = 30
}

Create Iam role:An IAM role is an IAM identity that you can create in your account that has specific permissions. An IAM role is similar to an IAM user, in that it is an AWS identity with permission policies that determine what the identity can and cannot do in AWS

resource "aws_iam_role" "lambda_exec" {
name = "serverless_lambda"

assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Sid = ""
Principal = {
Service = "lambda.amazonaws.com"
}
}
]
})
}

resource "aws_iam_role_policy_attachment" "lambda_policy" {
role = aws_iam_role.lambda_exec.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

Creation of the API gateway

This is the core resource that acts as the front door to your APIs. It provides HTTP endpoints for clients to access your application or services


#API Gateway is an AWS managed service that allows you to create and manage HTTP or WebSocket API It supports integration with AWS Lambda functions,
#allowing you to implement an HTTP API using Lambda functions to handle and respond to HTTP requests.
resource "aws_apigatewayv2_api" "lambda" {
name = "serverless_lambda_gw"
protocol_type = "HTTP"
}

resource "aws_apigatewayv2_stage" "lambda" {
api_id = aws_apigatewayv2_api.lambda.id

name = "serverless_lambda_stage"
auto_deploy = true
#This displays the logs in cloudwatch
access_log_settings {
destination_arn = aws_cloudwatch_log_group.api_gw.arn

format = jsonencode({
requestId = "$context.requestId"
sourceIp = "$context.identity.sourceIp"
requestTime = "$context.requestTime"
protocol = "$context.protocol"
httpMethod = "$context.httpMethod"
resourcePath = "$context.resourcePath"
routeKey = "$context.routeKey"
status = "$context.status"
responseLength = "$context.responseLength"
integrationErrorMessage = "$context.integrationErrorMessage"
}
)
}
}

To display the logs in the cloudwatch

Amazon CloudWatch collects and visualizes real-time logs, metrics, and event data in automated dashboards to streamline your infrastructure and application maintenance

  access_log_settings {
destination_arn = aws_cloudwatch_log_group.api_gw.arn

format = jsonencode({
requestId = "$context.requestId"
sourceIp = "$context.identity.sourceIp"
requestTime = "$context.requestTime"
protocol = "$context.protocol"
httpMethod = "$context.httpMethod"
resourcePath = "$context.resourcePath"
routeKey = "$context.routeKey"
status = "$context.status"
responseLength = "$context.responseLength"
integrationErrorMessage = "$context.integrationErrorMessage"
}
)
}

The AWS::ApiGatewayV2::Integration resource creates an integration for an API.

resource "aws_apigatewayv2_integration" "hello_world" {
api_id = aws_apigatewayv2_api.lambda.id

integration_uri = aws_lambda_function.hello_world.invoke_arn
integration_type = "AWS_PROXY"
integration_method = "POST"
}

resource "aws_apigatewayv2_route" "hello_world" {
api_id = aws_apigatewayv2_api.lambda.id

route_key = "GET /hello"
target = "integrations/${aws_apigatewayv2_integration.hello_world.id}"
}

resource "aws_cloudwatch_log_group" "api_gw" {
name = "/aws/api_gw/${aws_apigatewayv2_api.lambda.name}"

retention_in_days = 30
}

The AWS::Lambda::Permission resource grants an AWS service or another account permission to use a function. You can apply the policy at the function level, or specify a qualifier to restrict access to a single version or alias

resource "aws_lambda_permission" "api_gw" {
statement_id = "AllowExecutionFromAPIGateway"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.hello_world.function_name
principal = "apigateway.amazonaws.com"

source_arn = "${aws_apigatewayv2_api.lambda.execution_arn}/*/*"
}

Outputs: Output.tf

Output values make information about your infrastructure available on the command line, and can expose information for other Terraform configurations to use. Output values are similar to return values in programming languages.

# Output value definitions
output "lambda_bucket_name" {
description = "Name of the S3 bucket used to store function code."

value = aws_s3_bucket.lambda_bucket.id
}
#Used to define your Lambda function and related resources.
output "function_name" {
description = "Name of the Lambda function."

value = aws_lambda_function.hello_world.function_name
}

#The API Gateway stage will publish your API to a URL managed by AWS
output "base_url" {
description = "Base URL for API Gateway stage."

value = aws_apigatewayv2_stage.lambda.invoke_url
}

Provider.tf

Providers allow Terraform to interact with cloud providers, SaaS providers, and other APIs.Used to establish connection between VS code terrafrom and Aws account

#Used to establish connection between VS code terrafrom and Aws account
terraform {
required_version = "~> 1.4.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.1.0"
}
}
}
provider "aws" {
region = "eu-west-1"
alias = "NewAccount"
assume_role {
role_arn = "" #This is the deploy role in the account you are using to deploy the resources
}
default_tags {
tags = var.default_tags
}
}

Variables.tf

Input variables let you customize aspects of Terraform modules without altering the module’s own source code. This functionality allows you to share modules across different Terraform configurations, making your module composable and reusable

# Input variable definitions

variable "aws_region" {
description = "AWS region for all resources."

type = string
default = "us-east-1"
}

Hello.js: Lamdba function

Deploy the Node js Function to AWS Lambda. AWS Lambda is a serverless, event-driven compute service that lets you run code for virtually any type of application or backend service without provisioning or managing servers

// Lambda function code in Javascript
//deploy the NodeJS function to AWS Lambda

module.exports.handler = async (event) => {
console.log('Event: ', event);
let responseMessage = 'Hello, World!';
if (event.queryStringParameters && event.queryStringParameters['Name']) {
responseMessage = 'Hello, ' + event.queryStringParameters['Name'] + '!';
}

return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
message: responseMessage,
}),
}
}

Data.tf

Data sources allow Terraform to use information defined outside of Terraform, defined by another separate Terraform configuration, or modified by functions.

data "archive_file" "lambda_hello_world" {
type = "zip"

source_dir = "${path.module}/hello-world"
output_path = "${path.module}/hello-world.zip"
}

--

--