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.

OpenDataNYC Link

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: