STEP-BY-STEP GUIDE
101 AWS Lambda tutorial for Go developers — API Gateway (Part 2)
Simple showcase with Terraform

In the first part of this guide, we saw how to create a simple Lambda function in Go. Here we will expand on that and make our function callable from the internet. We will integrate API Gateway with a Lambda function on the backend. When a client calls our function URL, API Gateway sends the request to the Lambda function and returns the function’s response to the client.
In order to run this example, you will need access to an AWS account. You are all set if you have already walked through the first part. If not, take a look into the AWS credentials chapter in part 1.
Running the example
Let’s get something working. Then we will explore the accompanying Go code and terraform configuration available on GitHub.
Step into the handler folder from the folder where this readme file is located. It contains a simple Go Lambda function prepared for HTTP API Gateway integration. The build.sh script will create a Lambda function deployment package. For a more detailed description of what it does check out the first part.
cd handler
../../scripts/build.sh
After this, we will have function.zip in the handler folder.
For Terraform, I suggest that you set a global plugin-cache folder. This will reuse plugins between projects, saving you time and disk space if you work with multiple different Terraform projects.
echo 'plugin_cache_dir="$HOME/.terraform.d/plugin-cache"' > $HOME/.terraformrc
mkdir -p $HOME/.terraform.d/plugin-cache
Now move to the terraform folder, where we will spend the rest of the time. terraform init will prepare plugins and download them into the plugin-cache.
cd ../terraform
terraform init
Execute the Terraform configuration and create our infrastructure:
terraform apply --auto-approve
after ~20 seconds the expected output looks something like:
Apply complete! Resources: 10 added, 0 changed, 0 destroyed.Outputs:endpoint = "https://in2keb62qf.execute-api.eu-central-1.amazonaws.com"
function_arn = "arn:aws:lambda:eu-central-1:052548195718:function:api-example-handler"
function_name = "api-example-handler"
url = "https://in2keb62qf.execute-api.eu-central-1.amazonaws.com/handler"
endpoint is the location of our API Gateway, url is the location on which you can reach our Lambda function. We can use the terraform output command to list some of these outputs whenever needed. For example, this will show the URL of the Lambda function:
echo $(terraform output -raw url)
we can use that URL to execute the function:
curl $(terraform output -raw url)
the expected output is something like:
Hello from arn:aws:lambda:eu-central-1:052548195718:function:api-example-handler
Now we can play with changing the Go code and sending input and building response of the function. To deploy your code changes, run the build.sh script again and then terraform apply to update the infrastructure.
When you want to remove all created resources, run:
terraform destroy --auto-approve
You can, of course, return to the apply step and create them again.
Go handler code

The handler code shows how to extract useful information from various available sources and return a response to the caller. The caller makes an HTTP request to the API Gateway endpoint. API Gateway packs that request and pushes a payload for the function invocation.
We are using the HTTP API Gateway proxy payload format 2.0 integration type. When we make an HTTP request to our endpoint, for example:
curl https://in2keb62qf.execute-api.eu-central-1.amazonaws.com/handler -d "request body"
the payload which API Gateway passes to our function looks like this:
{
"version": "2.0",
"routeKey": "ANY /handler",
"requestContext": {
"timeEpoch": 1644265482216,
"time": "07/Feb/2022:20:24:42 +0000",
"stage": "$default",
"routeKey": "ANY /handler",
"requestId": "NMDxogOhliAEJTg=",
"http": {
"userAgent": "curl/7.77.0",
"sourceIp": "93.140.84.169",
"protocol": "HTTP/1.1",
"path": "/handler",
"method": "POST"
},
"domainPrefix": "in2keb62qf",
"domainName": "in2keb62qf.execute-api.eu-central-1.amazonaws.com",
"apiId": "in2keb62qf",
"accountId": "123456789012"
},
"rawQueryString": "",
"rawPath": "/handler",
"isBase64Encoded": true,
"headers": {
"x-forwarded-proto": "https",
"x-forwarded-port": "443",
"x-forwarded-for": "93.140.84.169",
"x-amzn-trace-id": "Root=1-6201800a-351c97731d1f143b5094ee4c",
"user-agent": "curl/7.77.0",
"host": "in2keb62qf.execute-api.eu-central-1.amazonaws.com",
"content-type": "application/x-www-form-urlencoded",
"content-length": "12",
"accept": "*/*"
},
"body": "cmVxdWVzdCBib2R5"
}
The aws/aws-lambda-go package provides Go structs for unpacking this payload. For the request, that is APIGatewayV2HTTPRequest and the response that API Gateway expects is defined in APIGatewayV2HTTPResponse. We are using these two types in the signature of our handler function, and the lambda package will handle the unmarshaling of the request and marshaling of the response.
The code in the handler shows how to get the request body. But first, we need to decode it from base64.
Then we show how to get information from the Lambda environment. A full list of AWS Lambda environment variables can be found here.
The context provided to the function has an execution deadline. The function should complete before this deadline.
The runtime request information can be found in the lambdacontext.
In the end, we show how to create a response for the API Gateway. The status code, body and headers will be returned to the caller who made the HTTP request to the API Gateway.
Terraform configuration
The terraform configuration consists of three files:
- main.tf defines input and output variables
- function.tf defines the lambda function with the supporting resources
- api.tf defines the API Gateway
function.tf
In function.tf, we first create an IAM role for our function and attach the AWSLambdaBasicExecutionRole policy, which gives the function permission to upload logs to CloudWatch. Other common Lambda roles can be found here.
After that, we define the Cloudwatch log group for the function. The function can create a log group on its own if it doesn’t exist. We create it upfront to make it part of the terraform-managed resources, which means it will be deleted by terraform on infrastructure destroy.
We use the deployment package we prepared in the handler folder for the building function. The source_code_hash directive will trigger a function code update whenever file hash changes.
api.tf
Here we create an API Gateway. Then we define a mapping between the API Gateway and our function, a route on which function will be exposed. With all that in place, the function will be reachable on the internet.
We start with the definition of the API Gateway resource.
There are three flavors of API Gateway. The first one was REST API; it still has the most features. HTTP API overlaps with REST in many segments. It is a more ‘modern’ implementation. AWS claims that “HTTP APIs are up to 71% cheaper compared to REST APIs”. It is also a little simpler than REST API. The last API Gateway flavor is WebSocket, enabling bidirectional client to backend communication. I’ll save that for some future example.
Here we are using HTTP API Gateway. The terraform resource type aws_apigatewayv2_api will create an HTTP API Gateway if the protocol_type argument is set to HTTP. The other option for protocol_type is WEBSOCKET for creating WebSocket API Gateways. The CORS configuration enables browsers to access API while served from different domains.
The Gateway access logs will be stored in a CloudWatch log group. /aws/vendedlogs is a required prefix for services that create a vast number of log groups.
API Gateway can have multiple stages with different configurations (for example, dev beta prod…). Here we will only use the $default stage. It is a reserved name for the stage which is served from the base of our API’s URL. API Gateway stages and stage deployments can be powerful concepts but you should reserve them for more complicated scenarios. Until then, stick to the $default stage and automatic API Gateway deployment.
In access_log_settings we are configuring where to send access logs and how they will look.
The integration resource, aws_apigatewayv2_integration, is the place where we connect our function with the API Gateway. aws_apigatewayv2_route sets the path in HTTP request where function will be exposed. The route key “ANY /${var.route}” when the route variable is set to “handler” exposes the function on the /handler path for all types of HTTP requests (GET, POST, …).
In the end, we need to allow our API Gateway to invoke the function. By default, in AWS, every resource is created without explicit permissions, so we need to set them for each resource to resource access.
Conclusion
Congratulations! Now you know how to add API Gateway to your AWS Lambda function in Go via Terraform. Thanks for following this tutorial.
Thanks for reading!
Have you got any questions regarding this tutorial or building serverless backends in Go? Feel free to reach us anytime at @MantilDev.
👉 Build faster with Mantil. It is excellent to get profound knowledge of how something works, but there’s no fun in doing it manually repeatedly. Install an open-source development framework Mantil and API Gateways will be created automatically for all your Lambda projects.