Creating Serverless API & Lambda Functions in golang
sam init
Which template source would you like to use?
1 - AWS Quick Start Templates
2 - Custom Template Location
Choice: 1
Choose an AWS Quick Start application template
1 - Hello World Example
2 - Data processing
3 - Hello World Example with Powertools for AWS Lambda
4 - Multi-step workflow
5 - Scheduled task
6 - Standalone function
7 - Serverless API
8 - Infrastructure event management
9 - Lambda Response Streaming
10 - Serverless Connector Hello World Example
11 - Multi-step workflow with Connectors
12 - GraphQLApi Hello World Example
13 - Full Stack
14 - Lambda EFS example
15 - DynamoDB Example
16 - Machine Learning
Template: 1
Use the most popular runtime and package type? (Python and zip) [y/N]: n
Which runtime would you like to use?
1 - dotnet8
2 - dotnet6
3 - go (provided.al2)
4 - go (provided.al2023)
5 - graalvm.java11 (provided.al2)
6 - graalvm.java17 (provided.al2)
7 - java21
8 - java17
9 - java11
10 - java8.al2
11 - nodejs20.x
12 - nodejs18.x
13 - nodejs16.x
14 - python3.9
15 - python3.8
16 - python3.12
17 - python3.11
18 - python3.10
19 - ruby3.3
20 - ruby3.2
21 - rust (provided.al2)
22 - rust (provided.al2023)
Runtime: 4
What package type would you like to use?
1 - Zip
2 - Image
Package type: 1
Based on your selections, the only dependency manager available is mod.
We will proceed copying the template using mod.
Would you like to enable X-Ray tracing on the function(s) in your application? [y/N]: y
X-Ray will incur an additional cost. View https://aws.amazon.com/xray/pricing/ for more details
Would you like to enable monitoring using CloudWatch Application Insights?
For more info, please view https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch-application-insights.html [y/N]: y
AppInsights monitoring may incur additional cost. View https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/appinsights-what-is.html#appinsights-pricing for more details
Would you like to set Structured Logging in JSON format on your Lambda functions? [y/N]: y
Structured Logging in JSON format might incur an additional cost. View https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs.html#monitoring-cloudwatchlogs-pricing for more details
Project name [sam-app]: BobbyMofongoAPI
Here's what my folder structure currently looks like:
➜ BobbyMofongoAPI tree .
.
├── Makefile
├── README.md
├── events
│ └── event.json
├── hello-world
│ ├── go.mod
│ ├── go.sum
│ ├── main.go
│ └── main_test.go
├── lambda-authorizer Ω
│ ├── go.mod
│ ├── go.sum
│ ├── main.go
│ └── main_test.go
├── samconfig.toml
└── template.yaml *
opt+z
Ω
Ω
- I created this folder and everything inside.
*
- I had to update some things in here...
I created the lambda authorizer but did not apply it within the template, because, I don't know how. I'm planning to go to the browser and do it manually, and see how the template file has changed.
Want to run things locally? Two ways!
printf '{"yourJSONEventObject": {"Goes": "Here"}}' | sam local invoke LambdaAuthorizer --event -
Or
sam local invoke LambdaAuthorizer --event ./lambda-authorizer/fixtures/valid.json
I like the first, but I can see that it'll get annoying after a while.
Woah, I learned a lot. Lets see if I can document all of it...
9/25/2024
The API changed quite a bit. Here's the run down. Currently using "Famous Baby Names" in NYC Open Data.
Here's my folder structure:
➜ BobbyMofongoAPI0923 git:(main) ✗ tree .
.
├── Makefile
├── README.md
├── events
│ └── event.json
├── famous-baby-names
│ ├── go.mod
│ ├── go.sum
│ └── main.go
├── hello-world
│ ├── go.mod
│ ├── go.sum
│ ├── main.go
│ └── main_test.go
├── lambda-authorizer
│ ├── go.mod
│ ├── go.sum
│ ├── main.go
│ └── main_test.go
├── samconfig.toml
└── template.yaml
Issues I ran into:
Folder Structure:
As you can see, each function takes a go.mod
, go.sum
, main.go
, and maybe a main_test.go
. I personally am not a fan of this, as I would like to keep each function as it's own entity. I'm going to try and see if, say, if I created a User API, can I have one user
, with different methods associated to that folder, eg: GET, POST, PUT
.
Lambda Authorizer:
Here's what the Lambda Authorizer looks like:
func handler(request events.APIGatewayV2CustomAuthorizerV2Request) (events.APIGatewayV2CustomAuthorizerSimpleResponse, error) {
h := request.Headers
a, exists := h["authorization"]
validToken := os.Getenv("LAMBDA_KEY")
if !exists {
return events.APIGatewayV2CustomAuthorizerSimpleResponse{
IsAuthorized: false,
Context: map[string]interface{}{
"message": "token not provided",
},
}, nil
}
if a != validToken {
return events.APIGatewayV2CustomAuthorizerSimpleResponse{
IsAuthorized: false,
Context: map[string]interface{}{
"message": "invalid token",
},
}, nil
}
return events.APIGatewayV2CustomAuthorizerSimpleResponse{
IsAuthorized: true,
Context: nil,
}, nil
}
func main() {
lambda.Start(handler)
}
There's multiple ways to implement a Lambda Authorizer. There's a Simple
response and a IAM
response. I chose to do a simple response. That way, I can just keep a private key and check against that. I imagine in bigger projects, we need to ensure that the response is an IAM role that can only call that function.
Here's what a simple response looks like:
{
"isAuthorized": true/false,
"context": {
"exampleKey": "exampleValue"
}
}
One thing that tripped me the fuck up, was the fact that I had to be mindful of the method signature.
Ensure you pair func handler(request events.APIGatewayV2CustomAuthorizerV2Request) (events.APIGatewayV2CustomAuthorizerSimpleResponse, error) {
within your lambda authorizer, and add the following to the template. Oh yea, the TEMPLATE. That messed me up too!!
Template tricks.
So there's some things in the template that confused me quite a bit. First, the authorizer, if it'll be used across the entire app, should be in the Globals
section. Here's an example:
Globals:
Function:
Timeout: 5
MemorySize: 128
Tracing: Active
LoggingConfig:
LogFormat: JSON
LogGroup: BobbyMofongo-09-23
HttpApi: # HttpApi is different than Api.
Auth:
DefaultAuthorizer: LambdaAuthorizerViaStaticKey # Name of your authorizer
Authorizers:
LambdaAuthorizerViaStaticKey:
FunctionArn: !GetAtt LambdaAuthorizerViaStaticKey.Arn
Identity:
Headers:
- authorization
AuthorizerPayloadFormatVersion: 2.0 # I'm not sure what this mean, but go off.
EnableFunctionDefaultPermissions: true
EnableSimpleResponses: true # This is required if you want to use the simple response example I used above.
...
Resources:
...
LambdaAuthorizerViaStaticKey:
Type: AWS::Serverless::Function
Metadata:
BuildMethod: go1.x
Properties:
CodeUri: lambda-authorizer/
Handler: bootstrap
Runtime: provided.al2023
Architectures:
- x86_64
Implementing Lambda:
Given that I used the AuthorizerPayloadFormatVersion: 2.0, I also need to ensure I use the v2 version of the requests for my lambda:
func handler(request events.APIGatewayV2HTTPRequest) (events.APIGatewayV2HTTPResponse, error) {
An error response looks like this:
var popularBabyNameResponses []PopularBabyNameResponse
err = json.NewDecoder(resp.Body).Decode(&popularBabyNameResponses)
if err != nil {
return events.APIGatewayV2HTTPResponse{
StatusCode: 500,
Headers: nil,
MultiValueHeaders: nil,
Body: "",
IsBase64Encoded: false,
Cookies: nil,
}, err
}
The Resource for the function within the template doc looks like this:
Resources:
[//]...
FamousBabyNameFunction:
Type: AWS::Serverless::Function
Metadata:
BuildMethod: go1.x
Properties:
CodeUri: famous-baby-names/
Handler: bootstrap
Runtime: provided.al2023
Architectures:
- x86_64
Events:
CatchAll:
Type: HttpApi
Properties:
Path: /famous-baby-names-in-nyc
Method: GET
Auth:
Authorizer: LambdaAuthorizerViaStaticKey
Events?, What is that?! -- It allows you to add what's called implicit values. I honestly can't tell you what CatchAll:
does, but it allows one to be able to add httpapi routes to the lambda function without generating an API resource entirely. It's pretty nice, cause it keeps it based off of that one lambda function vs having to go back and forth to the api resource. When I create a new lambda function, and add to that function's events, the HttpApi auto-attaches the url to it.
Env Vars: I need to learn how to deal...
Right now, I had to create the lambda authorizer, then add the LAMBDA_KEY
to the function from the console. I'd hate to have to do that. I'm not sure if making any changes to the lambda will override the environment variables. That would get tiring real quick.
Running your entire API locally.
Really cool stuff, you can run all your functions locally with the following command:
sam local start-api
This will kick up your lambdas within docker containers, pointing to http://127.0.0.1:3000
I was running sam local start-api --disable-authorizer --debug
to debug some things.
adding --disable-authorizer
will allow you to call the function straight up, which is nice :sweat_smile: