DEV Community

Cover image for Serverless Golang REST API with AWS Lambda
Preetham
Preetham

Posted on

Serverless Golang REST API with AWS Lambda

In this post, let us go through the process of deploying a Golang API to AWS Lambda and handling requests through AWS API Gateway, a process which is very straight forward and takes a few minutes once we know the steps.

I am assuming that you have a basic knowledge of Go and an AWS account. If you don’t know anything about AWS Lambda and API Gateway, don’t worry, all we need to get started is explained here. If you already have a good understanding of Lambda and API Gateway, feel free to skip some of the explanations.

I was inspired from may other posts and one of the main reason I am writing this post is because most of the posts involves explanation of additional components like databases, cache, etc which is really useful but not a straightforward post on getting started with AWS Lambda Golang REST API methodologies with API gateway as there are many use-cases without the need of any database or state to be maintained.

So we can take a look at working with databases like dynamodb in a different post and in this post, we will create a simple rest api which does the below

Method Route Action
GET /mirror?name=xxx Returns the query parameter name like a mirror and an error if name is not found
POST /mirror Accepts a json payload and returns it back like a mirror and returns an error when json is invalid or payload is empty

With these APIs, we will get an understanding of how to work with the http methods and parsing the incoming request.

Table Of Contents

  1. Setting up the aws cli
  2. Creating Simple Golang REST API
  3. Creating and deploying an AWS Lambda function
  4. Setting up the HTTP API in AWS API Gateway
  5. Deploying the HTTP API created

Setting up the aws cli

  1. Installation and basic usage instructions can be found here. We can verify the installation as below
aws --version

aws-cli/2.2.44 Python/3.8.8 Darwin/20.5.0 exe/x86_64 prompt/off
Enter fullscreen mode Exit fullscreen mode
  1. We need to setup an AWS IAM user with programmatic access permission for the CLI to use. Detailed official instructions can be found here here and you can also check this post for a detailed walkthrough. For getting started, we will attach AdministratorAccess policy to this user but for production, recommended to use a restricted policy with only the necessary levels of access. Note down the aws_access_key_id and aws_secret_access_key obtained.
  2. Configure the CLI to use the credentials of the IAM user we just created. We will use the region us-east-2 and the output format as json (json is easy to read).
$ aws configure
AWS Access Key ID (None): aws_access_key_id
AWS Secret Access Key (None): aws_secret_access_key
Default region name (None): us-east-2
Default output format (None): json
Enter fullscreen mode Exit fullscreen mode

More detailed instructions can be found here

Creating Simple Golang REST API

1 .GO Installation

Though the deployment mode is serverless in AWS cloud, we would need Go installed in your machine for compiling the go package.
Follow the instructions in the official page for installing go in your machine.
Verify that Go is installed.

$ go version

go version go1.17.2 darwin/amd64
Enter fullscreen mode Exit fullscreen mode

2 .Creating the Folder structure

mkdir serverlessgolang
cd serverlessgolang
Enter fullscreen mode Exit fullscreen mode

3 .Initiate the project

go mod init <project-name>
Enter fullscreen mode Exit fullscreen mode

The above command initiates the project and creates a file called go.mod - which is similar to package.json in nodejs.


Its a good approach to give the project-name as a repo link (github.com/yourid/ and also to keep this folder structure as part of git, but not compulsory.

4 .Create the golang file and install the packages

Just proceed, we will create this file and proceed with the explanation of each part below.

vi main.go
Enter fullscreen mode Exit fullscreen mode

and in the editor

package main

import (
    "fmt"

    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
    "github.com/valyala/fastjson"
)

func HandleRequest(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    ApiResponse := events.APIGatewayProxyResponse{}
    // Switch for identifying the HTTP request
    switch request.HTTPMethod {
    case "GET":
        // Obtain the QueryStringParameter
        name := request.QueryStringParameters["name"]
        if name != "" {
            ApiResponse = events.APIGatewayProxyResponse{Body: "Hey " + name + " welcome! ", StatusCode: 200}
        } else {
            ApiResponse = events.APIGatewayProxyResponse{Body: "Error: Query Parameter name missing", StatusCode: 500}
        }

    case "POST":    
        //validates json and returns error if not working
        err := fastjson.Validate(request.Body)

        if err != nil {
            body := "Error: Invalid JSON payload ||| " + fmt.Sprint(err) + " Body Obtained" + "||||" + request.Body
            ApiResponse = events.APIGatewayProxyResponse{Body: body, StatusCode: 500}
        } else {
            ApiResponse = events.APIGatewayProxyResponse{Body: request.Body, StatusCode: 200}
        }

    }
    // Response
    return ApiResponse, nil
}

func main() {
    lambda.Start(HandleRequest)
}
Enter fullscreen mode Exit fullscreen mode

Now after creating the file, we can install all the required modules using

go mod tidy
Enter fullscreen mode Exit fullscreen mode

which will download and install the imported files in main.go or if any other go files present in the folder.


The alternative approach is to manually install the packages

go get <package-name>
---
go get github.com/aws/aws-lambda-go
go get github.com/valyala/fastjson
Enter fullscreen mode Exit fullscreen mode

4.1 .Code and Package Explanation

Now, let us go through in detail the packages imported and the code

Package github.com/aws/aws-lambda-go/lambda

This package implements the Lambda programming model for Go and is used to start or initiate the lambda handler. Any Golang Lambda function must have this package.

In the function main(), we call lambda.start() and pass in the HandleRequest() as the lambda handler function.
Our handler function, as programmed will parse the request and returns a welcome message incase of Get method and returns the input body in case of POST method.

More details on this package can be found here.

Package github.com/aws/aws-lambda-go/events

This package provides input types for Lambda functions that process AWS events. There are a variety of events which can be consumed by the lambda function, in which we will be working on API Gateway events and the package also provides two useful types APIGatewayProxyRequest and APIGatewayProxyResponse which contain useful information about incoming HTTP requests and send responses that the API Gateway understands.

type APIGatewayProxyRequest struct {
    Resource              string                        `json:"resource"` // The resource path defined in API Gateway
    Path                  string                        `json:"path"`     // The url path for the caller
    HTTPMethod            string                        `json:"httpMethod"`
    Headers               map[string]string             `json:"headers"`
    QueryStringParameters map[string]string             `json:"queryStringParameters"`
    PathParameters        map[string]string             `json:"pathParameters"`
    StageVariables        map[string]string             `json:"stageVariables"`
    RequestContext        APIGatewayProxyRequestContext `json:"requestContext"`
    Body                  string                        `json:"body"`
    IsBase64Encoded       bool                          `json:"isBase64Encoded,omitempty"`
}

type APIGatewayProxyResponse struct {
    StatusCode      int               `json:"statusCode"`
    Headers         map[string]string `json:"headers"`
    Body            string            `json:"body"`
    IsBase64Encoded bool              `json:"isBase64Encoded,omitempty"`
}
Enter fullscreen mode Exit fullscreen mode

here,
request is of type events.APIGatewayProxyRequest and
response is of type events.APIGatewayProxyResponse

and using

  • request.HTTPMethod we identify the GET and POST invocations
  • request.Body we get the request body
  • request.QueryStringParameters we get the query parameter name
  • response.Body we send the response message to the user
  • response.StatusCode we send the response status code

Notice how in all cases the error value returned from our lambda handler is nil. This is because the AWS API Gateway doesn't accept error objects when you're using it in conjunction with a lambda proxy integration (they would result in a 'malformed response' errors again). So we need to manage errors fully within our lambda function and return the appropriate HTTP response.

Package github.com/valyala/fastjson

This package contains json parsing and validating methods which are faster than default json package.
We are using the method fastjson.Validate(request.Body) to validate the json.

We can also validate the input payload in the API gateway, which is recommended as it will not invoke the lambda function on wrong inputs and saving cost. We will look at it later.

5. Creating an executable and zipping

env GOOS=linux GOARCH=amd64 go build -o output/main
Enter fullscreen mode Exit fullscreen mode

here, we are setting the environment variable

  • GOOS=linux, implying build the executable for linux OS
  • GOARCH=amd64, implying that the executable is build for execution in amd64 cpu architechture
  • -o <path/file> is the output file name

Next, we need to zip the file to upload it to AWS Lambda function

zip -j output/function.zip output/main
Enter fullscreen mode Exit fullscreen mode

this will create a zip file

Creating and deploying an AWS Lambda function

1. IAM Role

Before deploying a lambda function, We need to set up an IAM role which defines the permission that our lambda function will have when it is running.

For now let's set up a lambda-function-executor role and attach the AWSLambdaBasicExecutionRole managed policy to it. This will give our lambda function the basic permissions it need to run and log to the AWS cloudwatch service.

First we have to create a trust policy JSON file. This will essentially instruct AWS to allow lambda services to assume the lambda-function-executor role

File: ./tmp/trust-policy.json

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "lambda.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

Then use the aws iam create-role command to create the role with this trust policy

aws iam create-role --role-name lambda-function-executor \
--assume-role-policy-document file://./tmp/trust-policy.json
Enter fullscreen mode Exit fullscreen mode

Response:

{
    "Role": {
        "Path": "/",
        "RoleName": "lambda-function-executor",
        "RoleId": "AROAZ5VSPHO6WRWZRHZBX",
        "Arn": "arn:aws:iam::682200349629:role/lambda-function-executor",
        "CreateDate": "2022-01-29T13:54:20+00:00",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "Service": "lambda.amazonaws.com"
                    },
                    "Action": "sts:AssumeRole"
                }
            ]
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Let us store the ARN value returned in a variable

rolearn=arn:aws:iam::682200349629:role/lambda-function-executor
Enter fullscreen mode Exit fullscreen mode

Now the role has been created and we need to specify its permissions. we can attach the policy AWSLambdaBasicExecutionRole using the aws iam attach-role-policy command as below

aws iam attach-role-policy --role-name lambda-function-executor \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Enter fullscreen mode Exit fullscreen mode

We can find a list of other lambda permission policies here.

2. Deploy the golang build into Lambda function

Now, let us deploy the actual Lambda function to AWS using the command aws lambda create-function. This function takes the below flags and would take a few minutes to complete.

Flag Description
--function-name The name our lambda function will be called within AWS
--runtime The runtime environment for the lambda function (in our case "go1.x")
--role The ARN of the role we want the lambda function to assume when it is running (obtained in the above step)
--handler The name of the executable in the root of the zip file (in our case its main)
--zip-file Path to the zip file we created in Step 2.5
functionName=gettingstarted #you can give your preferred name
aws lambda create-function --function-name $functionName --runtime go1.x \
--role $rolearn \
--handler main --zip-file fileb://./output/function.zip
Enter fullscreen mode Exit fullscreen mode

Response

{
    "FunctionName": "gettingstarted",
    "FunctionArn": "arn:aws:lambda:us-east-2:682200349629:function:gettingstarted",
    "Runtime": "go1.x",
    "Role": "arn:aws:iam::682200349629:role/lambda-function-executor",
    "Handler": "main",
    "CodeSize": 4458249,
    "Description": "",
    "Timeout": 3,
    "MemorySize": 128,
    "LastModified": "2022-01-29T14:13:10.826+0000",
    "CodeSha256": "VwOZE7cyuYdCtMS2MH7KFaM9NgrQn2fChDyw3B2rnxE=",
    "Version": "$LATEST",
    "TracingConfig": {
        "Mode": "PassThrough"
    },
    "RevisionId": "e07cccd1-42b3-4c7b-9524-fc18b067a4d3",
    "State": "Pending",
    "StateReason": "The function is being created.",
    "StateReasonCode": "Creating",
    "PackageType": "Zip",
    "Architectures": [
        "x86_64"
    ]
}
Enter fullscreen mode Exit fullscreen mode

we can store the function arn in a variable

functionarn=arn:aws:lambda:us-east2:682200349629:function:gettingstarted
Enter fullscreen mode Exit fullscreen mode

You can validate or list all the functions using

aws lambda list-functions
Enter fullscreen mode Exit fullscreen mode

Now our lambda function has been deployed and is now ready to use. we can try it out by using the aws lambda invoke command (which requires us to specify an output file for the response — we are using ./output/output.json in the snippet below).

aws lambda invoke --function-name gettingstarted ./output/output.json
Enter fullscreen mode Exit fullscreen mode

We got the output

{
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}
Enter fullscreen mode Exit fullscreen mode

Now the Lambda function has been created and deployed.

Setting up the HTTPS API in AWS API Gateway

1 . We can create a REST API using the command aws apigateway create-rest-api. we are choosing type as REGIONAL as we want the API gateway to be reachable from the region. We can also choose EDGE - if we want our API gateway to be accessible via AWS Cloudfront.

aws apigateway create-rest-api --name apitest --endpoint-configuration types=REGIONAL
Enter fullscreen mode Exit fullscreen mode

Response

{
    "id": "knvvn50y71",
    "name": "firstapi",
    "createdDate": "2022-01-29T20:14:39+05:30",
    "apiKeySource": "HEADER",
    "endpointConfiguration": {
        "types": [
            "REGIONAL"
        ]
    },
    "disableExecuteApiEndpoint": false
}
Enter fullscreen mode Exit fullscreen mode

Let us store the id returned, we will be using it a lot

restApiId=knvvn50y71
Enter fullscreen mode Exit fullscreen mode

2 . Next we need to get the id of the root API resource ("/"). We can retrieve this using the command aws apigateway get-resources using the api id obtained above

aws apigateway get-resources --rest-api-id $restApiId
Enter fullscreen mode Exit fullscreen mode

Response

{
    "items": [
        {
            "id": "4hsrin7w54",
            "path": "/"
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

Again, let us take a note of the root-path-id value this returns as parentId (root=> here is parent)

parentId=4hsrin7w54
Enter fullscreen mode Exit fullscreen mode

3 . Now we need to create a new resource under the root path — specifically a resource for the URL path /mirror. We can do this by using the aws apigateway create-resource command with the --path-part parameter

aws apigateway create-resource --rest-api-id $restApiId \
--parent-id $parentId --path-part mirror
Enter fullscreen mode Exit fullscreen mode

Response

{
    "id": "rgrcck",
    "parentId": "4hsrin7w54",
    "pathPart": "mirror",
    "path": "/mirror"
}
Enter fullscreen mode Exit fullscreen mode

Again, let us take a note of the resource-id this returns.

resourceId=rgrcck
Enter fullscreen mode Exit fullscreen mode

Let us understand that it's possible to include placeholders within the path by wrapping part of the path in curly braces. For example, a --path-part parameter of mirror/{name} would match requests to /mirror/goku and /mirror/vegeta, and the value of name would be made available to our lambda function via the events object. We can also make a placeholder greedy by postfixing it with a +. A common idiom is to use the parameter --path-part {proxy+} if we want to match all requests regardless of their path.

4 . But we will not be doing it, but rather read the values from a query parameter as decided in the start. Let's get back to our /mirror resource and use the aws apigateway put-method command to register the HTTP method of ANY. This will mean that our /mirror resource will respond to all requests regardless of their HTTP method.

aws apigateway put-method --rest-api-id $restApiId \
--resource-id $resourceId --http-method ANY \
--authorization-type NONE
Enter fullscreen mode Exit fullscreen mode

Response

{
    "httpMethod": "ANY",
    "authorizationType": "NONE",
    "apiKeyRequired": false
}
Enter fullscreen mode Exit fullscreen mode

5 . Now let us integrate the api gateway with the lambda function using the command aws apigateway put-integration.

Flag Description
--type Value should always be AWS_PROXY and this ensures the AWS API Gateway sends information about the HTTP request as an event to the lambda function and also automatically transform lambda function output to a HTTP response.
--integration-http-method Value should be POST Let us not confuse this with what HTTP methods our API resource responds to.
--uri parameter needs to take the format below
arn:aws:apigateway:<region>:lambda:path/2015-03-31/functions/<our-lambda-function-arn>/invocations
Enter fullscreen mode Exit fullscreen mode

If we have not copied our lambda function arn, we can get it using

aws lambda list-functions
Enter fullscreen mode Exit fullscreen mode

The account id of the current cli can be found using the below command

aws sts get-caller-identity
Enter fullscreen mode Exit fullscreen mode

Now call the command

region=us-east-2
accountId=682200349629
uri="arn:aws:apigateway:""$region"":lambda:path/2015-03-31/functions/arn:aws:lambda:""$region"":""$accountId"":function:""$functionName""/invocations"
####
aws apigateway put-integration --rest-api-id $restApiId \
--resource-id $resourceId --http-method ANY --type AWS_PROXY \
--integration-http-method POST \
--uri $uri
Enter fullscreen mode Exit fullscreen mode

Response

    "type": "AWS_PROXY",
    "httpMethod": "POST",
    "uri": "arn:aws:apigateway:us-east-2:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-2:682200349629:function:gettingstarted/invocations",
    "passthroughBehavior": "WHEN_NO_MATCH",
    "timeoutInMillis": 29000,
    "cacheNamespace": "rgrcck",
    "cacheKeyParameters": []
}
Enter fullscreen mode Exit fullscreen mode

6 . Now, when we test the REST API with the command aws apigateway test-invoke-method we will see an error

aws apigateway test-invoke-method --rest-api-id $restApiId --resource-id $resourceId --http-method "GET"
Enter fullscreen mode Exit fullscreen mode

Error Response

{
    "status": 500,
    "body": "{\"message\": \"Internal server error\"}",
    "headers": {
        "x-amzn-ErrorType": "InternalServerErrorException"
    },
    "multiValueHeaders": {
        "x-amzn-ErrorType": [
            "InternalServerErrorException"
        ]
    },
    "log": "Execution log for request 198564f9-28e4-4578-895d-290af2f6dee6\nSat Jan 29 16:36:37 UTC 2022 : Starting execution for request: 198564f9-28e4-4578-895d-290af2f6dee6\nSat Jan 29 16:36:37 UTC 2022 : HTTP Method: GET, Resource Path: /mirror\nSat Jan 29 16:36:37 UTC 2022 : Method request path: {}\nSat Jan 29 16:36:37 UTC 2022 : Method request query string: {}\nSat Jan 29 16:36:37 UTC 2022 : Method request headers: {}\nSat Jan 29 16:36:37 UTC 2022 : Method request body before transformations: \nSat Jan 29 16:36:37 UTC 2022 : Endpoint request URI: https://lambda.us-east-2.amazonaws.com/2015-03-31/functions/arn:aws:lambda:us-east-2:682200349629:function:gettingstarted/invocations\nSat Jan 29 16:36:37 UTC 2022 : Endpoint request headers: {X-Amz-Date=20220129T163637Z, x-amzn-apigateway-api-id=knvvn50y71, Accept=application/json, User-Agent=AmazonAPIGateway_knvvn50y71, Host=lambda.us-east-2.amazonaws.com, X-Amz-Content-Sha256=a33d05de2f7337bb6ae5302f16240b5f3e42be56141d90f6954dc0372771ea72, X-Amzn-Trace-Id=Root=1-61f56d15-1e3289d5c10359a5bb24075f, x-amzn-lambda-integration-tag=198564f9-28e4-4578-895d-290af2f6dee6, Authorization=*********************************************************************************************************************************************************************************************************************************************************************************************************************************************21274e, X-Amz-Source-Arn=arn:aws:execute-api:us-east-2:682200349629:knvvn50y71/test-invoke-stage/GET/mirror, X-Amz-Security-Token=IQoJb3JpZ2luX2VjEMj//////////wEaCXVzLWVhc3QtMiJHMEUCIQCE/xgic5zfU9dT5XjUcGjHKmXwV+z2m36xVB2BxHKFqgIgOjEBR08BSPnA6inOTdhJ2P92c0OA9cJaaQzA/zS [TRUNCATED]\nSat Jan 29 16:36:37 UTC 2022 : Endpoint request body after transformations: {\"resource\":\"/mirror\",\"path\":\"/mirror\",\"httpMethod\":\"GET\",\"headers\":null,\"multiValueHeaders\":null,\"queryStringParameters\":null,\"multiValueQueryStringParameters\":null,\"pathParameters\":null,\"stageVariables\":null,\"requestContext\":{\"resourceId\":\"rgrcck\",\"resourcePath\":\"/mirror\",\"httpMethod\":\"GET\",\"extendedRequestId\":\"Mt37aHfKiYcF_QQ=\",\"requestTime\":\"29/Jan/2022:16:36:37 +0000\",\"path\":\"/mirror\",\"accountId\":\"682200349629\",\"protocol\":\"HTTP/1.1\",\"stage\":\"test-invoke-stage\",\"domainPrefix\":\"testPrefix\",\"requestTimeEpoch\":1643474197654,\"requestId\":\"198564f9-28e4-4578-895d-290af2f6dee6\",\"identity\":{\"cognitoIdentityPoolId\":null,\"cognitoIdentityId\":null,\"apiKey\":\"test-invoke-api-key\",\"principalOrgId\":null,\"cognitoAuthenticationType\":null,\"userArn\":\"arn:aws:iam::682200349629:user/preetham\",\"apiKeyId\":\"test-invoke-api-key-id\",\"userAgent\":\"aws-cli/2.2.44 Python/3.8.8 Darwin/20.5.0 exe/x86_64 prompt/off command/apigateway.test-invoke-method\",\"accountId\":\"682200349629\",\"caller\":\"AIDA [TRUNCATED]\nSat Jan 29 16:36:37 UTC 2022 : Sending request to https://lambda.us-east-2.amazonaws.com/2015-03-31/functions/arn:aws:lambda:us-east-2:682200349629:function:gettingstarted/invocations\nSat Jan 29 16:36:37 UTC 2022 : Execution failed due to configuration error: Invalid permissions on Lambda function\nSat Jan 29 16:36:37 UTC 2022 : Method completed with status: 500\n",
"latency": 36
}
Enter fullscreen mode Exit fullscreen mode

So if we try to isolate the error, it says
Execution failed due to configuration error: Invalid permissions on Lambda function

This is because our API gateway doesn't have permission to execute our lambda function

7 . we can use aws lambda add-permission command to give the API permissions to invoke the lambda

we are generating a random uid using uuidgen command and the region and accountId is part of the session variable as we have defined in one of the previous steps

uid=`uuidgen`
sourceArn="arn:aws:execute-api:""$region"":""$accountId"":""$restApiId""/*/*/*"
aws lambda add-permission --profile preetham --function-name $functionName --statement-id $uid \
--action lambda:InvokeFunction --principal apigateway.amazonaws.com \
--source-arn $sourceArn
Enter fullscreen mode Exit fullscreen mode

Response

{
    "Statement": "{\"Sid\":\"E7B0C866-B985-4F0D-8DEA-AE560A04EBFE\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"apigateway.amazonaws.com\"},\"Action\":\"lambda:InvokeFunction\",\"Resource\":\"arn:aws:lambda:us-east-2:682200349629:function:gettingstarted\",\"Condition\":{\"ArnLike\":{\"AWS:SourceArn\":\"arn:aws:execute-api:us-east-2:682200349629:knvvn50y71/*/*/*\"}}}"
}
Enter fullscreen mode Exit fullscreen mode

8 . we can test the api gateway again

aws apigateway test-invoke-method --rest-api-id $restApiId --resource-id $resourceId --http-method "GET"
Enter fullscreen mode Exit fullscreen mode

Response

{
    "status": 500,
    "body": "Error: Query Parameter name missing",
    "headers": {
        "X-Amzn-Trace-Id": "Root=1-61f57957-74340f97bf9f0046a2c78983;Sampled=0"
    },
    "multiValueHeaders": {
        "X-Amzn-Trace-Id": [
            "Root=1-61f57957-74340f97bf9f0046a2c78983;Sampled=0"
        ]
    },
    "log": "Execution log for request 0f285ec1-58ec-46be-be7b-2f002efcdc75\nSat Jan 29 17:28:55 UTC 2022 : Starting execution for request: 0f285ec1-58ec-46be-be7b-2f002efcdc75\nSat Jan 29 17:28:55 UTC 2022 : HTTP Method: GET, Resource Path: /mirror\nSat Jan 29 17:28:55 UTC 2022 : Method request path: {}\nSat Jan 29 17:28:55 UTC 2022 : Method request query string: {}\nSat Jan 29 17:28:55 UTC 2022 : Method request headers: {}\nSat Jan 29 17:28:55 UTC 2022 : Method request body before transformations: \nSat Jan 29 17:28:55 UTC 2022 : Endpoint request URI: https://lambda.us-east-2.amazonaws.com/2015-03-31/functions/arn:aws:lambda:us-east-2:682200349629:function:gettingstarted/invocations\nSat Jan 29 17:28:55 UTC 2022 : Endpoint request headers: {X-Amz-Date=20220129T172855Z, x-amzn-apigateway-api-id=knvvn50y71, Accept=application/json, User-Agent=AmazonAPIGateway_knvvn50y71, Host=lambda.us-east-2.amazonaws.com, X-Amz-Content-Sha256=7890b63bd5654eb21207f4af1bcb751ba84c0498a9a9929e309dafd30f9e8cc8, X-Amzn-Trace-Id=Root=1-61f57957-74340f97bf9f0046a2c78983, x-amzn-lambda-integration-tag=0f285ec1-58ec-46be-be7b-2f002efcdc75, Authorization=*********************************************************************************************************************************************************************************************************************************************************************************************************************************************649380, X-Amz-Source-Arn=arn:aws:execute-api:us-east-2:682200349629:knvvn50y71/test-invoke-stage/GET/mirror, X-Amz-Security-Token=IQoJb3JpZ2luX2VjEMn//////////wEaCXVzLWVhc3QtMiJHMEUCIBki4ycUlnDnl5O2JNLu3QZ4YxX6e+oAoPgBMXAZDNb+AiEA6DzZsiRL6GIEAmFjwfPISLGV2cQiMW+IIGk0AG5 [TRUNCATED]\nSat Jan 29 17:28:55 UTC 2022 : Endpoint request body after transformations: {\"resource\":\"/mirror\",\"path\":\"/mirror\",\"httpMethod\":\"GET\",\"headers\":null,\"multiValueHeaders\":null,\"queryStringParameters\":null,\"multiValueQueryStringParameters\":null,\"pathParameters\":null,\"stageVariables\":null,\"requestContext\":{\"resourceId\":\"rgrcck\",\"resourcePath\":\"/mirror\",\"httpMethod\":\"GET\",\"extendedRequestId\":\"Mt_lvGoICYcF6zQ=\",\"requestTime\":\"29/Jan/2022:17:28:55 +0000\",\"path\":\"/mirror\",\"accountId\":\"682200349629\",\"protocol\":\"HTTP/1.1\",\"stage\":\"test-invoke-stage\",\"domainPrefix\":\"testPrefix\",\"requestTimeEpoch\":1643477335759,\"requestId\":\"0f285ec1-58ec-46be-be7b-2f002efcdc75\",\"identity\":{\"cognitoIdentityPoolId\":null,\"cognitoIdentityId\":null,\"apiKey\":\"test-invoke-api-key\",\"principalOrgId\":null,\"cognitoAuthenticationType\":null,\"userArn\":\"arn:aws:iam::682200349629:user/preetham\",\"apiKeyId\":\"test-invoke-api-key-id\",\"userAgent\":\"aws-cli/2.2.44 Python/3.8.8 Darwin/20.5.0 exe/x86_64 prompt/off command/apigateway.test-invoke-method\",\"accountId\":\"682200349629\",\"caller\":\"AIDA [TRUNCATED]\nSat Jan 29 17:28:55 UTC 2022 : Sending request to https://lambda.us-east-2.amazonaws.com/2015-03-31/functions/arn:aws:lambda:us-east-2:682200349629:function:gettingstarted/invocations\nSat Jan 29 17:28:56 UTC 2022 : Received response. Status: 200, Integration latency: 292 ms\nSat Jan 29 17:28:56 UTC 2022 : Endpoint response headers: {Date=Sat, 29 Jan 2022 17:28:56 GMT, Content-Type=application/json, Content-Length=103, Connection=keep-alive, x-amzn-RequestId=60d92f89-317c-4c29-894d-c157bcc9b79f, x-amzn-Remapped-Content-Length=0, X-Amz-Executed-Version=$LATEST, X-Amzn-Trace-Id=root=1-61f57957-74340f97bf9f0046a2c78983;sampled=0}\nSat Jan 29 17:28:56 UTC 2022 : Endpoint response body before transformations: {\"statusCode\":500,\"headers\":null,\"multiValueHeaders\":null,\"body\":\"Error: Query Parameter name missing\"}\nSat Jan 29 17:28:56 UTC 2022 : Method response body after transformations: Error: Query Parameter name missing\nSat Jan 29 17:28:56 UTC 2022 : Method response headers: {X-Amzn-Trace-Id=Root=1-61f57957-74340f97bf9f0046a2c78983;Sampled=0}\nSat Jan 29 17:28:56 UTC 2022 : Method completed with status: 500\n",
    "latency": 307
}
Enter fullscreen mode Exit fullscreen mode

We can see that the error

Error: Query Parameter name missing
Enter fullscreen mode Exit fullscreen mode

which is the same as configured in our golang code. Yay !!

This is fine, but now we cannot execute this via an URL.

Deploying the HTTP API created

We can run the below command to make it live

aws apigateway create-deployment --rest-api-id $restApiId \
--stage-name staging
Enter fullscreen mode Exit fullscreen mode

Response

{
    "id": "4pdblq",
    "createdDate": 1522929303
}
Enter fullscreen mode Exit fullscreen mode

Now, the url can be accessed by
https://<rest-api-id>.execute-api.<region>.amazonaws.com/staging/mirror

url="https://""$restApiId"".execute-api.""$region"".amazonaws.com/staging/mirror"
Enter fullscreen mode Exit fullscreen mode

GET Method

curl --location --request GET $url
Enter fullscreen mode Exit fullscreen mode

Response

Hey Goku welcome!
Enter fullscreen mode Exit fullscreen mode

POST Method

curl --location --request POST $url \
--header 'Content-Type: application/json' \
--data-raw '{
    "country" : "India"
}'
Enter fullscreen mode Exit fullscreen mode

Response

{
    "country" : "India"
}
Enter fullscreen mode Exit fullscreen mode

Now, for our function, if we want to make any changes or rebuild the code, we can rebuild it, zip it and upload it by by the command

aws lambda update-function-code --function-name $functionName \
--zip-file fileb:///tmp/function.zip
Enter fullscreen mode Exit fullscreen mode

With this, we will be able to build a simple AWS Lambda REST API with Golang. Now, you can edit the handler function to suit your requirement.

Cover Image Source: https://blog.travelex.io/blazing-fast-microservice-with-go-and-lambda-d30d95290f28

Discussion (3)

Collapse
ra07 profile image
Rak07s

Really well written Preetham. Thanks for sharing :)

Collapse
angela_dimon_fab1d39963a2 profile image
Angela Dimon

Great guide! One question: for the part
Creating and deploying an AWS Lambda function

  1. IAM Role File: ./tmp/trust-policy.json

Where is the tmp folder located? Is it one we need to create in the local project directory or the already existing one in the Mac HD drive?

Thank you!

Collapse
benjakuben profile image
Ben Jakuben

Thank you, Preetham. This was clear and well written and incredibly helpful!

I had one small hiccup: I had to remove --profile preetham from step 7 when adding the new permission.