Asset 31Asset 32
Navigate back to the homepage

Building an API endpoint with AWS Lambda and Go

Sebastian Ojeda
September 19th, 2019 · 3 min read

Serverless cloud functions (also called functions as a service, or FaaS) have revolutionized the way we think about computing. We no longer have to worry about scaling resources, configuring servers, and upgrading or maintaining expensive hardware. Instead, cloud providers like AWS, Microsoft Azure, and Google Cloud have allowed us to use the latest hardware, infintely scale with load, and get up and running with little to no configuration. All with just a few keystokes or the click of a button!

In 2018, AWS announced Go support for AWS Lambda. With this, we are now able to use the full power of FaaS with Go. So naturally, we’re going to build an API endpoint with Go. Let’s get to it.

Setup

The first thing we need to do is install the awscli tool. For this, make sure you have the latest version of Python installed on your machine. Then we need to install pip, the package manager for Python.

1curl -O https://bootstrap.pypa.io/get-pip.py
2
3python3 get-pip.py --user

Once that’s complete, we can finally install the cli.

1pip3 install awscli --upgrade --user

Now we need to configure our cli. If you’re new to AWS, then you’ll need to set up your first IAM user. I’ve provided a link to that here.

Don’t forget to save your credentials in a safe place. You’ll need them in a moment.

To set up the cli, we can run

1aws configure

and follow the prompts to provide our credentials.

Creating our Lambda function

Now that we have the hard part out of the way, let’s write the handler function for our endpoint. First, we’re going to use the github.com/aws/aws-lambda-go/lambda package to help us implement the lambda programming model in Go.

1go get github.com/aws/aws-lambda-go/lambda

Now, finally, we can write our function. In this example, it will be a simple function that takes a name and returns a greeting.

1package main
2
3import (
4 "fmt"
5
6 "github.com/aws/aws-lambda-go/lambda"
7)
8
9// Request is the struct being decoded. In this case, it
10// simply comprises of a name.
11type Request struct {
12 Name string `json:"name"`
13}
14
15// Response will encode our greeting.
16type Response struct {
17 Message string `json:"message"`
18}
19
20// Handler is the function Lambda will call once invoked.
21func Handler(r Request) (Response, error) {
22 return Response{Message: fmt.Sprintf("Hello, %s!", r.Name)}, nil
23}
24
25func main() {
26 lambda.Start(Handler)
27}

Deploying to AWS

Now our function is ready to be deployed. From here on out, it will be a few simple commands before we are off to the races.

1GOOS=linux go build main.go
2
3zip function.zip main

We should now have a binary executable named main in our working directory. Hooray!

Now let’s create a Lambda that we will deploy the zipfile to. In AWS, a Lambda must assume a user role, which in turn specifies a series of policies that limit the resources our function can access. To get started, let’s create a user for this function.

1LAMBDA_USER=serverless-go-user
2
3aws iam create-user --user-name $LAMBDA_USER

We now need to create a policy for this user. In policy.json add the following

1{
2 "Version": "2012-10-17",
3 "Statement": [
4 {
5 "Effect": "Allow",
6 "Action": ["iam:*", "lambda:*"],
7 "Resource": "*"
8 }
9 ]
10}

and now let’s attach that policy to our user

1aws iam put-user-policy \
2--user-name $LAMBDA_USER \
3--policy-name lambda-all-access \
4--policy-document file://policy.json

The last few steps before we can create out function is that we need to create a role for our lambda user.

1aws iam create-access-key --user-name $LAMBDA_USER
2
3# We must configure the cli to work with these new credentials
4aws configure --profile $LAMBDA_USER

And now, using the below role.json, we can create our role, and finally our function as well.

1{
2 "Version": "2012-10-17",
3 "Statement": [
4 {
5 "Effect": "Allow",
6 "Principal": { "AWS": "*" },
7 "Action": "sts:AssumeRole"
8 }
9 ]
10}
1aws iam create-role \
2--role-name serverless-go-role \
3--assume-role-policy-document file://role.json
4
5# We can now switch back to using the default profile
6export AWS_PROFILE=default
7
8# Don't forget to save the function arn. We will need it later.
9# We will be saving it as FUNC_ARN for this post
10FUNCTION_NAME=serverless-go-api
11
12aws lambda create-function --function-name $FUNCTION_NAME \
13--runtime go1.x --role $FUNC_ARN \
14--handler main --zip-file fileb://function.zip

Invoking our endpoint

Now that we have our function up and running in the cloud, we can now invoke our function with a simple command.

1aws lambda invoke \
2--invocation-type RequestResponse \
3--function-name $FUNCTION_NAME \
4--region us-east-1 \
5--payload '{"name":"Sebastian"}' \
6--profile $LAMBDA_USER \
7response.json

If everything has gone well up to this point, our response file should read

1{ "message": "Hello, Sebastian!" }

Now, it may seem a bit unusual that our response is written to a file, but that’s because there is a seperate service in AWS to deploy an API, and that’s called API Gateway. If we want our lambda function to be accessible to the world, we’ll need to use Gateway.

Adding API Gateway

To add Gateway to our Lambda function, we must:

Create a REST API deployment.

1aws apigateway create-rest-api --name 'serverless-go-gateway'

Create a url path that we will be requesting. Note we created an $API variable from the api id field of the response from the above command.

1# Save the parent id as ROOT. This refers to the root path
2aws apigateway get-resources --rest-api-id $API
3
4# Now we want the id of the '/greeting' path, which we will
5# save as RESOURCE
6aws apigateway create-resource --rest-api-id $API \
7--parent-id $ROOT \
8--path-part greeting

Attach an HTTP method to our path and a corresponding response status.

1aws apigateway put-method --rest-api-id $API \
2--resource-id $RESOURCE \
3--http-method POST \
4--authorization-type "NONE"
5
6aws apigateway put-method-response \
7--rest-api-id $API \
8--resource-id $RESOURCE \
9--http-method POST \
10--status-code 200

Add our lambda integration and corresponding HTTP response.

1aws apigateway put-integration \
2--rest-api-id $API \
3--resource-id $RESOURCE \
4--http-method POST \
5--type AWS \
6--integration-http-method POST \
7--uri arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/$FUNC_ARN/invocations
8
9aws apigateway put-integration-response \
10--rest-api-id $API \
11--resource-id $RESOURCE \
12--http-method POST \
13--status-code 200 \
14--selection-pattern ""

And finally, create a deployment and grant it permission to invoke the function.

1aws apigateway create-deployment --rest-api-id $API --stage-name dev
2
3# The '*' in the arn indicates this is for testing only. Your account id
4# can be found by running `aws sts get-caller-identity`
5aws lambda add-permission \
6--function-name $FUNCTION_NAME \
7--statement-id apigateway-dev-test \
8--action lambda:InvokeFunction \
9--principal apigateway.amazonaws.com \
10--source-arn "arn:aws:execute-api:us-east-1:$ACCOUNT:$API/*/POST/greeting"
11
12aws lambda add-permission \
13--function-name $FUNCTION_NAME \
14--statement-id apigateway-dev \
15--action lambda:InvokeFunction \
16--principal apigateway.amazonaws.com \
17--source-arn "arn:aws:execute-api:us-east-1:$ACCOUNT:$API/dev/POST/greeting"

This should wrap up things! We can now test our endpoint with a cURL command

1curl -d '{"name":"Sebastian"}' \
2'https://$API.execute-api.us-east-1.amazonaws.com/dev/greeting'
3
4{"message":"Hello, Sebastian!"}

We now have a working endpoint that we can invoke via an HTTP request to AWS API Gateway. Hopefully, you can start to see the power of cloud computing as we now have a FaaS and it took zero server configuration on our part! Now, this may look a little low-level, and that’s because it is. You can now use this as a foundation to build more complex tooling to fit your needs. If you want a solution that’s already baked, I suggest checking out Servless Framework as they provide great support for building serverless applications in Go!

More articles from Fullstack Go

Handling JSON in Go

Go was built for the modern world. So let's talk about encoding and decoding the most popular data-interchange format.

September 18th, 2019 · 1 min read

The ,ok idiom

Go encourages idiomatic code by design. If you've ever spent some time looking at Go source code, you've probably noticed this interesting pattern.

September 16th, 2019 · 1 min read
© 2019 Fullstack Go
Link to $https://twitter.com/fullstack_goLink to $https://github.com/fullstackgo