In this series, we will learn how to set up a serverless framework example for Golang and AWS Lambda. First of all, we are going to walk through how to create an AWS account and then set up a serverless framework with AWS (Lamba).
Lambda is a compute service that lets you run code without provisioning or managing servers. Lambda runs your code on a high-availability compute infrastructure and performs all of the administration of the compute resources, including server and operating system maintenance, capacity provisioning and automatic scaling, and logging. With Lambda, you can run code for virtually any type of application or backend service. All you need to do is supply your code in one of the languages that Lambda supports.
Introduction to AWS
Reason to use Lambda AWS
As long as you can run your application code in the Lambda standard runtime environment and use the resources that Lambda offers, Lambda is an excellent computing solution for many application scenarios.
When employing Lambda, your code is the only thing you are accountable for. The compute fleet, which provides a balance of memory, CPU, network, and other resources to run your code is managed by Lambda. You cannot log in to compute instances or alter the operating system on the given runtimes since Lambda maintains these resources. Lambda manages capacity, monitors, and logs your Lambda functions on your behalf, among other operational and administrative tasks.
Lambda features
You can create Lambda applications that are readily expandable, secure, and scalable by using the characteristics listed below:
- Concurrency and scaling controls: You have perfectly alright control over the scaling and responsiveness of your production applications thanks to concurrency and scaling features like concurrency limits and provisioned concurrency.
- Functions defined as container images: Build, test, and deploy your Lambda functions using the container image tooling, processes, and dependencies that you choose.
- Code signing: Code signing for Lambda offers trust and integrity rules that enable you to confirm that only original code released by authorized developers is used in your Lambda services.
- Extensions for lambdas: To improve your Lambda functions, use Lambda extensions. Use extensions, for instance, to more easily combine Lambda with your preferred monitoring, observability, and security solutions.
- Database access: A database proxy manages a pool of database connections and relays queries from a function. This enables a function to reach high concurrency levels without exhausting database connections.
- File systems access: You can configure a function to mount an Amazon Elastic File System (Amazon EFS) file system to a local directory. With Amazon EFS, your function code can access and modify shared resources safely and at high concurrency.
Create a Lambda function with the console
To get started with Lambda, use the Lambda console to create a function. You can author functions in the Lambda console, or with an IDE toolkit, command line tools, or the AWS SDKs. The Lambda console provides a code editor for non-compiled languages that lets you modify and test code quickly. The AWS Command Line Interface (AWS CLI) gives you direct access to the Lambda API for advanced configuration and automation use cases.
Create the function
You use the console to design a Lambda function in this introductory exercise. The default code that Lambda generates is used by the function. For non-compiled languages, the Lambda console offers a code editor that makes it simple to change and test code.
To create a Lambda function with the console
If you do not have an AWS account, go to the signup page. Once you got your account, follow the below steps to create lambda functions:
- Open the Functions page of the Lambda console.
- Choose to Create function.
- Under Basic information, do the following:
- For the Function name, enter
hello-lambda-func
. - For Runtime, confirm that Go1.x is selected. Note that Lambda provides runtimes for .NET (PowerShell, C#), Java, Node.js, Python, and other programming languages
- For the Function name, enter
- Choose to Create function.
Lambda creates a Go function and an execution role that grants the function permission to upload logs. The Lambda function assumes the execution role when you invoke your function and uses the execution role to create credentials for the AWS SDK and to read data from event sources.
Upload source code
Here is an example of creating a function to query print out the welcome message. First, you have to install was library:
go get github.com/aws/aws-lambda-go/lambda
We will write a function called handler (you can name anything here) and use the function lambda.Start(handler)
so that Lambda can run the above function. The maximum of the zip file is 10MB.
package main
import "github.com/aws/aws-lambda-go/lambda"
func handler() (string, error) {
return "Welcome to Serverless world", nil
}
func main() {
lambda.Start(handler)
}
Zip it back into a zip file that can be uploaded to AWS Lambda:
GOOS=linux go build -o main main.go
After creating a lambda, our next step is defining a function by uploading a zip file or choosing from the Amazon S3 location.
Invoke the Lambda function
Use the sample event data provided in the console to call your Lambda function. To invoke a function:
After selecting your function, choose the Test tab.
Select New event from the Test event menu. Leave the default hello-world selection in Template. For this test, give it a name, and take notice of the sample event template below:
{
"key1": "value1",
"key2": "value2",
"key3": "value3"
}
Select Test, then select Save changes. Up to ten test events per function can be created by each user. Other users cannot access those test cases.
Upon successful completion, view the results in the console. We have multiple helpful information such as:
- Execution result: hows the execution status has succeeded. To view the function execution results, expand Details.
- Summary:Â the key information reported
- Log output:Â the log that Lambda generates for each invocation
Run the function (choose Test) a few more times to gather some metrics that you can view in the next step. Choose the Monitor tab. This page shows graphs for the metrics that Lambda sends to CloudWatch.
Create a lambda function to get people list
Here is an example of writing a function to return a list of people:
package main
import (
"encoding/json"
"github.com/aws/aws-lambda-go/lambda"
)
type Person struct {
Id int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func list() (string, error) {
people := []Person{
{Id: 1, Name: "GoLinuxCloud", Email: "golinu@gmail.com"},
{Id: 2, Name: "Admin", Email: "admin@gmail.com"},
}
res, _ := json.Marshal(&people)
return string(res), nil
}
func main() {
lambda.Start(list)
}
Now, we can build a binary file by running the below command:
GOOS=linux go build -o listAll listAll.go
Add it to a zip folder then upload it to AWS lambda as instructed in the previous chapter:
And we can invoke this function from the Test menu:
Amazon API Gateway
What is AWS API Gateway
Amazon API Gateway is an AWS service for creating, publishing, maintaining, monitoring, and securing REST, HTTP, and WebSocket APIs at any scale. API developers can create APIs that access AWS or other web services, as well as data stored in the AWS Cloud. As an API Gateway API developer, you can create APIs for use in your own client applications. Or you can make your APIs available to third-party app developers.
API Gateway creates RESTful APIs that:
- Are HTTP-based.
- Enable stateless client-server communication.
- Implement standard HTTP methods such as GET, POST, PUT, PATCH, and DELETE.
Part of AWS serverless infrastructure
Together with AWS Lambda, API Gateway forms the app-facing part of the AWS serverless infrastructure.
For an app to call publicly available AWS services, you can use Lambda to interact with required services and expose Lambda functions through API methods in API Gateway. AWS Lambda runs your code on a highly available computing infrastructure. It performs the necessary execution and administration of computing resources. To enable serverless applications, API Gateway supports streamlined proxy integrations with AWS Lambda and HTTP endpoints.
Create an AWS API Gateway
Open Web Console and look for API Gateway and then Build a REST API (without private)
Set up your protocol and then click Create API
The next step is setting all the resources and endpoint URLs for your gateway.
Because we are integrating the gateway with AWS lambda functions so we can tick Use Lambda Proxy integration: Requests will be proxied to Lambda with request details available in the `event` of your handler function.
In the next step, we will deploy our REST API. Go to Actions tab, select Deploy API and follow the instructions:
Now we can see the URL path for our deployment:
We can test our get people list API using Postman:
We can see that there is an error here. The Lambda function's response must adhere to API Gateway's guidelines in order for Lambda and API Gateway to work together. We have to change our code to return a Response struct:
type Response struct {
StatusCode int `json:"statusCode"`
Body string `json:"body"`
}
Our lambda function will be:
package main
import (
"encoding/json"
"github.com/aws/aws-lambda-go/lambda"
)
type Person struct {
Id int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
type Response struct {
StatusCode int `json:"statusCode"`
Body string `json:"body"`
}
func list() (Response, error) {
people := []Person{
{Id: 1, Name: "GoLinuxCloud", Email: "golinu@gmail.com"},
{Id: 2, Name: "Admin", Email: "admin@gmail.com"},
}
res, _ := json.Marshal(&people)
return Response{
StatusCode: 200,
Body: string(res),
}, nil
}
func main() {
lambda.Start(list)
}
If we import the function again, now we can test our API:
For standardization, AWS also provides us with the Golang SDK so that we can avoid these errors and write code faster. Instead of having to create the struct Response ourselves, we use the SDK as follows, download the package go get github.com/aws/aws-lambda-go/events
, update main.go, we use the built-in struct APIGatewayProxyResponse:
package main
import (
"encoding/json"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
type Person struct {
Id int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func list() (events.APIGatewayProxyResponse, error) {
people := []Person{
{Id: 1, Name: "GoLinuxCloud", Email: "golinu@gmail.com"},
{Id: 2, Name: "Admin", Email: "admin@gmail.com"},
}
res, _ := json.Marshal(&people)
return events.APIGatewayProxyResponse{
StatusCode: 200,
Headers: map[string]string{
"Content-Type": "application/json",
},
Body: string(res),
}, nil
}
func main() {
lambda.Start(list)
}
API query person by id
Next we will create API get person by id. We have to send the id as a parameter and then use APIGatewayProxyRequest
to extract this parameter from the request. We define an endpoint to handle this API for example /{id}. The function code:
package main
import (
"encoding/json"
"strconv"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
type Person struct {
Id int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func getPerson(req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
people := []Person{
{Id: 1, Name: "GoLinuxCloud", Email: "golinu@gmail.com"},
{Id: 2, Name: "Admin", Email: "admin@gmail.com"},
{Id: 3, Name: "Test", Email: "test@gmail.com"},
}
// get the query param
id, err := strconv.Atoi(string(req.PathParameters["id"]))
if err != nil {
return events.APIGatewayProxyResponse{
StatusCode: 400,
Body: err.Error(),
}, nil
}
res, err := json.Marshal(people[id-1])
// return an error if can not found person
if err != nil {
return events.APIGatewayProxyResponse{
StatusCode: 500,
Body: err.Error(),
}, nil
}
return events.APIGatewayProxyResponse{
StatusCode: 200,
Headers: map[string]string{
"Content-Type": "application/json",
},
Body: string(res),
}, nil
}
func main() {
lambda.Start(getPerson)
}
Create a new resource for our gateway:
Create GET method for that resource:
Deploy it to the existed one that we created in the above section:
Now we can test the API using Postman:
Serverless architecture
You can create a web API with an HTTP endpoint for your Lambda function by using Amazon API Gateway. API Gateway provides tools for creating and documenting web APIs that route HTTP requests to Lambda functions. You can secure access to your API with authentication and authorization controls. Your APIs can serve traffic over the internet or can be accessible only within your VPC.
Resources in your API define one or more methods, such as GET or POST. Methods have an integration that routes requests to a Lambda function or another integration type. You can define each resource and method individually, or use special resource and method types to match all requests that fit a pattern. A proxy resource catches all paths beneath a resource. The ANY
 method catches all HTTP methods.
So now our server architecture will be:
Simple Go Http Client to test APIs
Here is an example of HTTP client to test our API:
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
// get all people request
url := "https://2edzqmpfc1.execute-api.us-east-1.amazonaws.com/stage/"
method := "GET"
client := &http.Client{}
req, err := http.NewRequest(method, url, nil)
if err != nil {
fmt.Println(err)
return
}
res, err := client.Do(req)
if err != nil {
fmt.Println(err)
return
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("All people:")
fmt.Println(string(body))
fmt.Println("------------------")
// get one person request
url1 := "https://2edzqmpfc1.execute-api.us-east-1.amazonaws.com/stage/3"
req1, err := http.NewRequest(method, url1, nil)
if err != nil {
fmt.Println(err)
return
}
res1, err := client.Do(req1)
if err != nil {
fmt.Println(err)
return
}
defer res1.Body.Close()
body, err = ioutil.ReadAll(res1.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Query person with id=3")
fmt.Println(string(body))
fmt.Println("------------------")
url2 := "https://2edzqmpfc1.execute-api.us-east-1.amazonaws.com/stage/10"
req2, err := http.NewRequest(method, url2, nil)
if err != nil {
fmt.Println(err)
return
}
res2, err := client.Do(req2)
if err != nil {
fmt.Println(err)
return
}
defer res2.Body.Close()
body, err = ioutil.ReadAll(res2.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Query person with id=10")
fmt.Println(string(body))
}
Output:
All people request:
[{"id":1,"name":"GoLinuxCloud","email":"golinu@gmail.com"},{"id":2,"name":"Admin","email":"admin@gmail.com"}]
------------------
Query person with id=3
{"id":3,"name":"Test","email":"test@gmail.com"}
------------------
Query person with id=10
{"message": "Internal server error"}
Summary
In this article, we learned about building a REST API in a Serverless model with API Gateway and AWS Lambda. This is a very simple example to illustrate the components of a serverless system; in reality, we need to use tools to automate deployment like terraform, write CI/CD to automate code deployment, and various adjustments to improve system performance and reduce operational risks. If you have more questions or need any clarification, you can ask below in the comments section.
References
https://aws.amazon.com/api-gateway/
https://aws.amazon.com/lambda/
https://docs.aws.amazon.com/lambda/latest/dg/getting-started.html
https://www.golinuxcloud.com/aws-cli-tutorial/
https://docs.aws.amazon.com/lambda/latest/dg/lambda-golang.html
https://docs.aws.amazon.com/lambda/latest/dg/golang-package.html