Integrating AWS Lambda Functions with API Gateway using Terraform
Learn how to create and deploy serverless APIs by integrating AWS Lambda functions with API Gateway using Terraform
Integrating AWS Lambda Functions with API Gateway using Terraform
Serverless architectures allow you to build and run applications without managing servers. This guide demonstrates how to create and deploy serverless APIs by integrating AWS Lambda with API Gateway using Terraform.
Video Tutorial
Learn more about managing AWS Lambda and API Gateway with Terraform in this comprehensive video tutorial:
Prerequisites
- AWS CLI configured with appropriate permissions
- Terraform installed (version 1.0.0 or later)
- Basic understanding of Lambda and API Gateway
- Node.js or Python for Lambda functions
Project Structure
serverless-terraform/
├── main.tf
├── variables.tf
├── outputs.tf
├── lambda.tf
├── api_gateway.tf
├── iam.tf
├── src/
│ └── hello-world/
│ ├── index.js
│ └── package.json
└── terraform.tfvars
Lambda Function Setup
Create src/hello-world/index.js
:
exports.handler = async (event) => {
const name = event.queryStringParameters?.name || 'World';
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
message: `Hello, ${name}!`,
timestamp: new Date().toISOString()
})
};
};
Create lambda.tf
:
# Archive the Lambda function code
data "archive_file" "lambda_zip" {
type = "zip"
source_dir = "${path.module}/src/hello-world"
output_path = "${path.module}/dist/hello-world.zip"
}
# Lambda function
resource "aws_lambda_function" "hello_world" {
filename = data.archive_file.lambda_zip.output_path
source_code_hash = data.archive_file.lambda_zip.output_base64sha256
function_name = "${var.project_name}-hello-world"
role = aws_iam_role.lambda.arn
handler = "index.handler"
runtime = "nodejs18.x"
environment {
variables = {
ENVIRONMENT = var.environment
}
}
tags = {
Name = "${var.project_name}-hello-world"
Environment = var.environment
}
}
# Lambda permission for API Gateway
resource "aws_lambda_permission" "api_gateway" {
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.hello_world.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_apigatewayv2_api.main.execution_arn}/*/*"
}
API Gateway Configuration
Create api_gateway.tf
:
# HTTP API Gateway
resource "aws_apigatewayv2_api" "main" {
name = "${var.project_name}-http-api"
protocol_type = "HTTP"
cors_configuration {
allow_headers = ["Content-Type", "Authorization"]
allow_methods = ["GET", "POST", "PUT", "DELETE"]
allow_origins = ["*"]
max_age = 300
}
tags = {
Name = "${var.project_name}-http-api"
Environment = var.environment
}
}
# API Gateway stage
resource "aws_apigatewayv2_stage" "main" {
api_id = aws_apigatewayv2_api.main.id
name = var.environment
auto_deploy = true
access_log_settings {
destination_arn = aws_cloudwatch_log_group.api_gateway.arn
format = jsonencode({
requestId = "$context.requestId"
ip = "$context.identity.sourceIp"
requestTime = "$context.requestTime"
httpMethod = "$context.httpMethod"
routeKey = "$context.routeKey"
status = "$context.status"
protocol = "$context.protocol"
responseTime = "$context.responseLatency"
integrationError = "$context.integrationErrorMessage"
})
}
tags = {
Name = "${var.project_name}-${var.environment}-stage"
Environment = var.environment
}
}
# API Gateway routes
resource "aws_apigatewayv2_route" "hello_world" {
api_id = aws_apigatewayv2_api.main.id
route_key = "GET /hello"
target = "integrations/${aws_apigatewayv2_integration.hello_world.id}"
}
# Lambda integration
resource "aws_apigatewayv2_integration" "hello_world" {
api_id = aws_apigatewayv2_api.main.id
integration_type = "AWS_PROXY"
integration_uri = aws_lambda_function.hello_world.invoke_arn
payload_format_version = "2.0"
}
# CloudWatch Log Group
resource "aws_cloudwatch_log_group" "api_gateway" {
name = "/aws/api-gateway/${var.project_name}"
retention_in_days = 30
tags = {
Name = "${var.project_name}-api-gateway-logs"
Environment = var.environment
}
}
IAM Configuration
Create iam.tf
:
# Lambda execution role
resource "aws_iam_role" "lambda" {
name = "${var.project_name}-lambda-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
}
]
})
tags = {
Name = "${var.project_name}-lambda-role"
Environment = var.environment
}
}
# Lambda basic execution policy
resource "aws_iam_role_policy_attachment" "lambda_basic" {
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
role = aws_iam_role.lambda.name
}
# API Gateway logging role
resource "aws_iam_role" "api_gateway" {
name = "${var.project_name}-api-gateway-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "apigateway.amazonaws.com"
}
}
]
})
tags = {
Name = "${var.project_name}-api-gateway-role"
Environment = var.environment
}
}
# API Gateway logging policy
resource "aws_iam_role_policy" "api_gateway_logging" {
name = "${var.project_name}-api-gateway-logging"
role = aws_iam_role.api_gateway.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams",
"logs:PutLogEvents",
"logs:GetLogEvents",
"logs:FilterLogEvents"
]
Resource = "*"
}
]
})
}
Variables Configuration
Create variables.tf
:
variable "aws_region" {
description = "AWS region"
type = string
default = "us-west-2"
}
variable "project_name" {
description = "Name of the project"
type = string
}
variable "environment" {
description = "Environment name"
type = string
default = "dev"
}
Output Configuration
Create outputs.tf
:
output "api_endpoint" {
description = "HTTP API Gateway endpoint"
value = aws_apigatewayv2_api.main.api_endpoint
}
output "lambda_function_name" {
description = "Name of the Lambda function"
value = aws_lambda_function.hello_world.function_name
}
output "lambda_function_arn" {
description = "ARN of the Lambda function"
value = aws_lambda_function.hello_world.arn
}
Best Practices
-
Lambda Functions
- Keep functions focused and small
- Handle errors properly
- Use environment variables
- Implement proper logging
-
API Gateway
- Enable CORS when needed
- Configure proper logging
- Use appropriate authentication
- Implement rate limiting
-
Security
- Use IAM roles with least privilege
- Enable API Gateway authorization
- Implement proper error handling
- Monitor API usage
Deployment Steps
- Initialize Terraform:
terraform init
- Create
terraform.tfvars
:
aws_region = "us-west-2"
project_name = "serverless-api"
environment = "dev"
- Review the plan:
terraform plan
- Apply the configuration:
terraform apply
Testing the API
- Using curl:
# Get the API endpoint from Terraform output
API_ENDPOINT=$(terraform output -raw api_endpoint)
# Test the API
curl "${API_ENDPOINT}/hello?name=John"
-
Using AWS Console:
- Navigate to API Gateway
- Select your API
- Use the test feature
-
Using Postman:
- Create a new request
- Use the API endpoint
- Add query parameters
Monitoring and Troubleshooting
-
CloudWatch Logs
- Lambda function logs
- API Gateway access logs
- Error tracking
-
Metrics
- Lambda execution metrics
- API Gateway metrics
- Custom metrics
-
X-Ray Tracing
- Enable X-Ray tracing
- Analyze request flow
- Debug issues
Advanced Features
- Custom Authorizers
resource "aws_apigatewayv2_authorizer" "main" {
api_id = aws_apigatewayv2_api.main.id
authorizer_type = "JWT"
identity_sources = ["$request.header.Authorization"]
name = "jwt-authorizer"
}
- Request Validation
resource "aws_apigatewayv2_model" "main" {
api_id = aws_apigatewayv2_api.main.id
content_type = "application/json"
name = "validation-model"
schema = jsonencode({
type = "object"
required = ["name"]
properties = {
name = { type = "string" }
}
})
}
- Usage Plans
resource "aws_api_gateway_usage_plan" "main" {
name = "${var.project_name}-usage-plan"
api_stages {
api_id = aws_apigatewayv2_api.main.id
stage = aws_apigatewayv2_stage.main.name
}
quota_settings {
limit = 1000
period = "DAY"
}
throttle_settings {
burst_limit = 5
rate_limit = 10
}
}
Conclusion
You’ve learned how to create a serverless API using AWS Lambda and API Gateway with Terraform. This setup provides:
- Scalable serverless architecture
- Automated deployment
- Proper monitoring and logging
- Secure API endpoints
Remember to:
- Follow security best practices
- Monitor API usage
- Implement proper error handling
- Keep Lambda functions focused and efficient