Define Lambda function handlers in Go
The Lambda function handler is the method in your function code that processes events. When your function is invoked, Lambda runs the handler method. Your function runs until the handler returns a response, exits, or times out.
This page describes how to work with Lambda function handlers in Go, including project setup, naming conventions, and best practices. This page also includes an example of a Go Lambda function that takes in information about an order, produces a text file receipt, and puts this file in an Amazon Simple Storage Service (S3) bucket. For information about how to deploy your function after writing it, see Deploy Go Lambda functions with .zip file archives or Deploy Go Lambda functions with container images.
Topics
- Setting up your Go handler project
- Example Go Lambda function code
- Handler naming conventions
- Defining and accessing the input event object
- Accessing and using the Lambda context object
- Valid handler signatures for Go handlers
- Using the AWS SDK for Go v2 in your handler
- Accessing environment variables
- Using global state
- Code best practices for Go Lambda functions
Setting up your Go handler project
A Lambda function written in Gogo mod init
command:
go mod init
example-go
Here, example-go
is the module name. You can replace this with
anything. This command initializes your project and generates the
go.mod
file that lists your project's dependencies.
Use the go get
command to add any external dependencies to your
project. For example, for all Lambda functions in Go, you must include the
github.com/aws/aws-lambda-go/lambdago get
command:
go get github.com/aws/aws-lambda-go
Your function code should live in a Go file. In the following example, we name this
file main.go
. In this file, you implement your core function logic in
a handler method, as well as a main()
function that calls this handler.
Example Go Lambda function code
The following example Go Lambda function code takes in information about an order, produces a text file receipt, and puts this file in an Amazon S3 bucket.
Example main.go
Lambda function
package main import ( "context" "encoding/json" "fmt" "log" "os" "strings" "github.com/aws/aws-lambda-go/lambda" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/s3" ) type Order struct { OrderID string `json:"order_id"` Amount float64 `json:"amount"` Item string `json:"item"` } var ( s3Client *s3.Client ) func init() { // Initialize the S3 client outside of the handler, during the init phase cfg, err := config.LoadDefaultConfig(context.TODO()) if err != nil { log.Fatalf("unable to load SDK config, %v", err) } s3Client = s3.NewFromConfig(cfg) } func uploadReceiptToS3(ctx context.Context, bucketName, key, receiptContent string) error { _, err := s3Client.PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucketName, Key: &key, Body: strings.NewReader(receiptContent), }) if err != nil { log.Printf("Failed to upload receipt to S3: %v", err) return err } return nil } func handleRequest(ctx context.Context, event json.RawMessage) error { // Parse the input event var order Order if err := json.Unmarshal(event, &order); err != nil { log.Printf("Failed to unmarshal event: %v", err) return err } // Access environment variables bucketName := os.Getenv("RECEIPT_BUCKET") if bucketName == "" { log.Printf("RECEIPT_BUCKET environment variable is not set") return fmt.Errorf("missing required environment variable RECEIPT_BUCKET") } // Create the receipt content and key destination receiptContent := fmt.Sprintf("OrderID: %s\nAmount: $%.2f\nItem: %s", order.OrderID, order.Amount, order.Item) key := "receipts/" + order.OrderID + ".txt" // Upload the receipt to S3 using the helper method if err := uploadReceiptToS3(ctx, bucketName, key, receiptContent); err != nil { return err } log.Printf("Successfully processed order %s and stored receipt in S3 bucket %s", order.OrderID, bucketName) return nil } func main() { lambda.Start(handleRequest) }
This main.go
file contains the following sections of code:
-
package main
: In Go, the package containing yourfunc main()
function must always be namedmain
. -
import
block: Use this block to include libraries that your Lambda function requires. -
type Order struct {}
block: Define the shape of the expected input event in this Go struct. -
var ()
block: Use this block to define any global variables that you'll use in your Lambda function. -
func init() {}
: Include any code you want Lambda to run during the during the initialization phase in thisinit()
method. -
func uploadReceiptToS3(...) {}
: This is a helper method that's referenced by the mainhandleRequest
handler method. -
func handleRequest(ctx context.Context, event json.RawMessage) error {}
: This is the main handler method, which contains your main application logic. -
func main() {}
: This is a required entry point for your Lambda handler. The argument to thelambda.Start()
method is your main handler method.
For this function to work properly, its execution role
must allow the s3:PutObject
action. Also, ensure that you define the
RECEIPT_BUCKET
environment variable. After a successful invocation, the Amazon S3 bucket
should contain a receipt file.
Handler naming conventions
For Lambda functions in Go, you can use any name for the handler. In this example, the handler
method name is handleRequest
. To reference the handler value in your
code, you can use the _HANDLER
environment variable.
For Go functions deployed using a .zip deployment package,
the executable file that contains your function code must be named bootstrap
. In addition,
the bootstrap
file must be at the root of the .zip file. For Go functions deployed using a
container image, you can use any name for the executable file.
Defining and accessing the input event object
JSON is the most common and standard input format for Lambda functions. In this example, the function expects an input similar to the following:
{ "order_id": "12345", "amount": 199.99, "item": "Wireless Headphones" }
When working with Lambda functions in Go, you can define the shape of the expected input
event as a Go struct. In this example, we define a struct to represent an Order
:
type Order struct { OrderID string `json:"order_id"` Amount float64 `json:"amount"` Item string `json:"item"` }
This struct matches the expected input shape. After you define your struct, you can write a
handler signature that takes in a generic JSON type compatible with the
encoding/json standard library
func handleRequest(ctx context.Context, event json.RawMessage) error { // Parse the input event var order Order if err := json.Unmarshal(event, &order); err != nil { log.Printf("Failed to unmarshal event: %v", err) return err ... }
After this deserialization, you can access the fields of the order
variable. For example,
order.OrderID
retrieves the value of "order_id"
from the original input.
Note
The encoding/json
package can access only exported fields. To be exported, field names
in the event struct must be capitalized.
Accessing and using the Lambda context object
The Lambda context object contains information about
the invocation, function, and execution environment. In this example, we declared this
variable as ctx
in the handler signature:
func handleRequest(ctx context.Context, event json.RawMessage) error { ... }
The ctx context.Context
input is an optional argument in your function handler. For more
information about accepted handler signatures, see Valid handler signatures for Go handlers.
If you make calls to other services using the AWS SDK, the context object is required in a few key areas. For example, to properly initialize your SDK clients, you can load the correct AWS SDK configuration using the context object as follows:
// Load AWS SDK configuration using the default credential provider chain cfg, err := config.LoadDefaultConfig(ctx)
SDK calls themselves may require the context object as an input. For example, the
s3Client.PutObject
call accepts the context object as its first argument:
// Upload the receipt to S3 _, err = s3Client.PutObject(ctx, &s3.PutObjectInput{ ... })
Outside of AWS SDK requests, you can also use the context object for function monitoring. For more information about the context object, see Using the Lambda context object to retrieve Go function information.
Valid handler signatures for Go handlers
You have several options when building a Lambda function handler in Go, but you must adhere to the following rules:
-
The handler must be a function.
-
The handler may take between 0 and 2 arguments. If there are two arguments, the first argument must implement
context.Context
. -
The handler may return between 0 and 2 arguments. If there is a single return value, it must implement
error
. If there are two return values, the second value must implementerror
.
The following lists valid handler signatures. TIn
and TOut
represent types
compatible with the encoding/json standard library. For more information, see
func Unmarshal
-
func ()
-
func () error
-
func () (TOut, error)
-
func (TIn) error
-
func (TIn) (TOut, error)
-
func (context.Context) error
-
func (context.Context) (TOut, error)
-
func (context.Context, TIn) error
-
func (context.Context, TIn) (TOut, error)
Using the AWS SDK for Go v2 in your handler
Often, you'll use Lambda functions to interact with or make updates to other AWS resources. The simplest way to interface with these resources is to use the AWS SDK for Go v2.
Note
The AWS SDK for Go (v1) is in maintenance mode, and will reach end-of-support on July 31, 2025. We recommend that you use only the AWS SDK for Go v2 going forward.
To add SDK dependencies to your function, use the go get
command for the specific SDK clients
that you need. In the example code earlier, we used the config
library and the s3
library. Add these dependencies by running the following commands in the directory that contains your
go.mod
and main.go
files:
go get github.com/aws/aws-sdk-go-v2/config go get github.com/aws/aws-sdk-go-v2/service/s3
Then, import the dependencies accordingly in your function's import block:
import ( ... "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/s3" )
When using the SDK in your handler, configure your clients with the right settings. The simplest way to do this is to use the default credential provider chain. This example illustrates one way to load this configuration:
// Load AWS SDK configuration using the default credential provider chain cfg, err := config.LoadDefaultConfig(ctx) if err != nil { log.Printf("Failed to load AWS SDK config: %v", err) return err }
After loading this configuration into the cfg
variable, you can pass this variable into
client instantiations. The example code instantiates an Amazon S3 client as follows:
// Create an S3 client s3Client := s3.NewFromConfig(cfg)
In this example, we initialized our Amazon S3 client in the init()
function to
avoid having to initialize it every time we invoke our function. The problem is that in the
init()
function, Lambda doesn't have access to the context object. As a workaround, you
can pass in a placeholder like context.TODO()
during the initialization phase. Later,
when you make a call using the client, pass in the full context object. This workaround is also
described in Using the context in AWS SDK client initializations and calls.
After you configure and initialize your SDK client, you can then use it to interact with other AWS
services. The example code calls the Amazon S3 PutObject
API as follows:
_, err = s3Client.PutObject(ctx, &s3.PutObjectInput{ Bucket: &bucketName, Key: &key, Body: strings.NewReader(receiptContent), })
Accessing environment variables
In your handler code, you can reference any environment variables
by using the os.Getenv()
method. In this example, we reference the defined
RECEIPT_BUCKET
environment variable using the following line of code:
// Access environment variables bucketName := os.Getenv("RECEIPT_BUCKET") if bucketName == "" { log.Printf("RECEIPT_BUCKET environment variable is not set") return fmt.Errorf("missing required environment variable RECEIPT_BUCKET") }
Using global state
To avoid creating new resources every time you invoke your function, you can declare and modify
global variables outside of your Lambda function's handler code. You define these global variables in
a var
block or statement. In addition, your handler may declare an init()
function that is executed during the initialization phase.
The init
method behaves the same in AWS Lambda as it does in standard Go programs.
Code best practices for Go Lambda functions
Adhere to the guidelines in the following list to use best coding practices when building your Lambda functions:
-
Separate the Lambda handler from your core logic. This allows you to make a more unit-testable function.
-
Minimize the complexity of your dependencies. Prefer simpler frameworks that load quickly on execution environment startup.
-
Minimize your deployment package size to its runtime necessities. This will reduce the amount of time that it takes for your deployment package to be downloaded and unpacked ahead of invocation.
-
Take advantage of execution environment reuse to improve the performance of your function. Initialize SDK clients and database connections outside of the function handler, and cache static assets locally in the
/tmp
directory. Subsequent invocations processed by the same instance of your function can reuse these resources. This saves cost by reducing function run time.To avoid potential data leaks across invocations, don’t use the execution environment to store user data, events, or other information with security implications. If your function relies on a mutable state that can’t be stored in memory within the handler, consider creating a separate function or separate versions of a function for each user.
-
Use a keep-alive directive to maintain persistent connections. Lambda purges idle connections over time. Attempting to reuse an idle connection when invoking a function will result in a connection error. To maintain your persistent connection, use the keep-alive directive associated with your runtime. For an example, see Reusing Connections with Keep-Alive in Node.js.
-
Use environment variables to pass operational parameters to your function. For example, if you are writing to an Amazon S3 bucket, instead of hard-coding the bucket name you are writing to, configure the bucket name as an environment variable.
-
Avoid using recursive invocations in your Lambda function, where the function invokes itself or initiates a process that may invoke the function again. This could lead to unintended volume of function invocations and escalated costs. If you see an unintended volume of invocations, set the function reserved concurrency to
0
immediately to throttle all invocations to the function, while you update the code. -
Do not use non-documented, non-public APIs in your Lambda function code. For AWS Lambda managed runtimes, Lambda periodically applies security and functional updates to Lambda's internal APIs. These internal API updates may be backwards-incompatible, leading to unintended consequences such as invocation failures if your function has a dependency on these non-public APIs. See the API reference for a list of publicly available APIs.
-
Write idempotent code. Writing idempotent code for your functions ensures that duplicate events are handled the same way. Your code should properly validate events and gracefully handle duplicate events. For more information, see How do I make my Lambda function idempotent?
.