DEV Community

Cover image for How to Invoke a Lambda Function via API Gateway Locally Using Terraform and LocalStack (PDF Generator – Part 3)
Faruq Abdulsalam
Faruq Abdulsalam

Posted on

How to Invoke a Lambda Function via API Gateway Locally Using Terraform and LocalStack (PDF Generator – Part 3)

In my previous article, How to Build and Test a PDF Generator Lambda Using LocalStack on Your Local Machine (Part 2: Using Terraform), we set up the foundational infrastructure, including S3, ECR, a Lambda function, and an IAM role using Terraform.

In this part, we’ll take it further by integrating API Gateway to invoke the Lambda function locally using LocalStack—allowing you to simulate real-world AWS behavior entirely on your local machine. This is particularly useful when you need to trigger Lambda functions via HTTP, just as you would in a production environment.

I’ve kept this guide simple and practical, so all you need is some basic Terraform knowledge to follow along.

Let's get started!

Prerequisites

Before proceeding, ensure you have the following installed:

  1. Docker
  2. Python 3.9+
  3. LocalStack account
  4. LocalStack CLI
  5. LocalStack Desktop
  6. Terraform

1. locals.tf
In this step, we'll introduce two new local variables specifically for the API Gateway resources. These variables define the names for the API Gateway and its deployment stage:

locals {
  ...
  api_gateway_name       = "pdf-generator-api"
  api_gateway_stage_name = "prod"
  ...
}

Enter fullscreen mode Exit fullscreen mode

2. main.tf
Add the following code blocks below to the main.tf file:

  • rest api
resource "aws_api_gateway_rest_api" "lambda_api" {
  name        = local.api_gateway_name
  description = "API Gateway for invoking the PDF Generator Lambda function"

  endpoint_configuration {
    types = ["REGIONAL"]
  }
}
Enter fullscreen mode Exit fullscreen mode

This block above creates the API Gateway REST API. We assign its name using the local variable we defined earlier: local.api_gateway_name.

The endpoint_configuration is set to REGIONAL, which is suitable because; we wont be using CloudFront and the API is not private(it will be accessible within the region).

  • resource
resource "aws_api_gateway_resource" "pdf_generator_api_root" {
  rest_api_id = aws_api_gateway_rest_api.lambda_api.id
  parent_id   = aws_api_gateway_rest_api.lambda_api.root_resource_id
  path_part   = "generate-pdf"
}
Enter fullscreen mode Exit fullscreen mode

This block creates a new API Gateway resource, linking it to the previously created REST API using rest_api_id. We also define the parent_id and set the path to /generate-pdf, which establishes the resource's URL path under the API Gateway.

  • method
resource "aws_api_gateway_method" "pdf_generator_api_method" {
  rest_api_id      = aws_api_gateway_rest_api.lambda_api.id
  resource_id      = aws_api_gateway_resource.pdf_generator_api_root.id
  http_method      = "POST"
  authorization    = "NONE"
  api_key_required = false

}
Enter fullscreen mode Exit fullscreen mode

This block creates the method for the resource above. It links the method to the REST API as well as the resource, and specifies the HTTP method as POST. Since we don't need authorization or an API key in this case, we set them to "NONE" and false, respectively.

  • integration
resource "aws_api_gateway_integration" "pdf_generator_api_integration" {
  rest_api_id = aws_api_gateway_rest_api.lambda_api.id
  resource_id = aws_api_gateway_resource.pdf_generator_api_root.id
  http_method = aws_api_gateway_method.pdf_generator_api_method.http_method

  type                    = "AWS_PROXY"
  integration_http_method = "POST"
  uri                     = aws_lambda_function.pdf_generator.invoke_arn

  depends_on = [aws_lambda_function.pdf_generator]

}
Enter fullscreen mode Exit fullscreen mode

This block creates the integration for the method above, linking it to the REST API, the resource, and the HTTP method. It specifies an AWS_PROXY integration type, which allows direct communication between API Gateway and the Lambda function. The integration HTTP method is set to POST, and the URI points to the Lambda function’s invoke ARN. The depends_on attribute ensures that the Lambda function is created before the integration is established.

  • deployment
resource "aws_api_gateway_deployment" "lambda_api_deployment" {
  rest_api_id = aws_api_gateway_rest_api.lambda_api.id
  stage_name  = local.api_gateway_stage_name

  lifecycle {
    create_before_destroy = true
  }

  triggers = {
    redeployment = sha1(jsonencode([
      aws_api_gateway_resource.pdf_generator_api_root.id,
      aws_api_gateway_method.pdf_generator_api_method.id,
      aws_api_gateway_integration.pdf_generator_api_integration.id
    ]))
  }

  depends_on = [
    aws_api_gateway_integration.pdf_generator_api_integration
  ]

}
Enter fullscreen mode Exit fullscreen mode

This block creates a deployment for the API, linking it to the REST API and specifying the stage name, which was declared earlier as a local variable. The lifecycle block ensures that the deployment is created before any resources are destroyed, minimizing downtime. The triggers attribute uses a SHA1 hash of the resource, method, and integration IDs to trigger a redeployment when changes occur. The depends_on attribute ensures that the deployment happens only after the API integration is fully created.

  • permission
resource "aws_lambda_permission" "apigw_lambda_permission" {
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.pdf_generator.function_name
  principal     = "apigateway.amazonaws.com"
  source_arn    = "${aws_api_gateway_rest_api.lambda_api.execution_arn}/${local.api_gateway_stage_name}/*"

  lifecycle {
    replace_triggered_by = [aws_lambda_function.pdf_generator]
  }

  depends_on = [aws_lambda_function.pdf_generator, aws_api_gateway_deployment.lambda_api_deployment]
}
Enter fullscreen mode Exit fullscreen mode

This block grants the necessary permission for the API Gateway endpoint to invoke the Lambda function. The action is set to "lambda:InvokeFunction", allowing the API Gateway to trigger the Lambda. The source_arn specifies the ARN of the API Gateway’s execution role and stage. The lifecycle block ensures that the permission is replaced if the Lambda function changes, and the depends_on attribute ensures the permission is created only after the Lambda function and API Gateway deployment are in place.

3. outputs.tf
Finally, create a new file named outputs.tf in the root of the infrastructure directory and add this content.

output "rest_api_url" {
  description = "The URL of the API Gateway REST API"
  value       = "https://${aws_api_gateway_rest_api.lambda_api.id}.execute-api.localhost.localstack.cloud:4566/${local.api_gateway_stage_name}"
}
Enter fullscreen mode Exit fullscreen mode

This block creates an output for the URL of the API Gateway REST API. Normally, the URL follows the format: https://{rest_api_id}.execute-api.{region}.amazonaws.com/{stage_name}.

However, since we're working with LocalStack in our local environment, the URL format is slightly different. It omits the region and amazonaws.com parts, and instead uses localhost.localstack.cloud as the base domain. This allows us to interact with the API locally.

The rest_api_id is simply the ID of the REST API, and there’s no difference in how it's used here. The stage_name corresponds to the local variable defined earlier in the locals.tf file


At this point, we have completed the code needed to provision the API Gateway resources and retrieve the URL. The next step is to create the resources and test our new implementation.

Run tflocal plan to get an overview of all the AWS resources that will be created. Then, run tflocal apply to confirm and initiate the creation of the resources.

Afterward, you should see the output of your API Gateway URL in the terminal. As explained earlier, the URL will have a format similar to this: https://{your_rest_api_id}.execute-api.localhost.localstack.cloud:4566/prod


  • Invoke lambda function

To test the Lambda function, we won’t invoke it directly. Instead, we will use the URL mentioned above. You can use Postman to make a POST request to https://{your_rest_api_id}.execute-api.localhost.localstack.cloud:4566/prod/generate-pdf (API Gateway URL + the path to the resource we created generate-pdf ) with the following payload:

{"first_name": "John", "last_name": "Doe"}
Enter fullscreen mode Exit fullscreen mode

This is the same payload we previously used with the event.json file.

Alternatively, you can use curl to make the request. Here's the curl command:

curl -X POST https://{your_rest_api_id}.execute-api.localhost.localstack.cloud:4566/prod/generate-pdf \
  -H "Content-Type: application/json" \
  -d '{"first_name": "John", "last_name": "Doe"}'
Enter fullscreen mode Exit fullscreen mode

You should see the success message below:

{"message": "PDF generated and uploaded successfully!"}
Enter fullscreen mode Exit fullscreen mode

You can confirm that the PDF was successfully generated and stored in the S3 bucket by navigating to the bucket via the LocalStack Desktop and downloading the PDF.


Congratulations! 🎉

You’ve successfully set up your API Gateway, linked it to your Lambda function, and tested it locally using Terraform and LocalStack—all without needing an AWS account. Thanks for reading!

Top comments (0)