AWS SAM

I’ve been struggling lately with thoughts on how to better manage my API Gateway and Lambda stuff. The serverless framework is nice, I suppose, but I want to have more control over things so I can get my hands dirty when I want to.

The AWS SAM models is pretty close to what I want, but the documentation is just awful. See for yourself. But progress has been made, so I’ll share my findings here.

I’ll be going over how it works when you DON’T want to use swagger to define the API. Not doing so is a lot faster and easier to spin up, but swagger is probably the ‘right’ way to do this and opens up a lot of options for you.

First of all, you’ll need a bucket to store your Lambda code. I have a CloudFormation template that I use to make mine so I can easily reference it in other templates. Luckily, SAM is just a CloudFormation template with some special sauce, so that wasn’t a problem. Not only do you need this bucket, it needs to have the function code already there as a zip file.

Speaking of that code, it won’t work quite like other API Gateway code you may have used in the past. The inbound data looks different as SAM, by default, creates a proxy that will send you the event so that it looks like this (assuming a user lookup for ‘foo’ here) :


{
"resource": "/user/{Username}",
"path": "/user/foo",
"httpMethod": "GET",
"headers": null,
"queryStringParameters": null,
"pathParameters": {
"Username": "foo"
},
"stageVariables": null,
"requestContext": {
"path": "/user/{Username}",
"accountId": "REDACTED",
"resourceId": "REDACTED",
"stage": "test-invoke-stage",
"requestId": "test-invoke-request",
"identity": {
"cognitoIdentityPoolId": REDACTED,
"cognitoIdentityId": REDACTED,
"apiKey": "REDACTED",
"cognitoAuthenticationType": REDACTED,
"userArn": "REDACTED",
"apiKeyId": "REDACTED",
"userAgent": "Apache-HttpClient/4.5.x (Java/1.8.0_144)",
"accountId": "REDACTED",
"caller": "REDACTED",
"sourceIp": "test-invoke-source-ip",
"accessKey": "REDACTED",
"cognitoAuthenticationProvider": REDACTED,
"user": "REDACTED"
},
"resourcePath": "/user/{Username}",
"httpMethod": "GET",
"apiId": "REDACTED"
},
"body": null,
"isBase64Encoded": false
}

Which is kinda cool as you don’t need to worry so much about how things are going to map out. You can just access the headers, path, query, and body data directly.

You also need to make sure you respond correctly. You can’t just send the data back out the way you might be used to as the API mappings are built for you. Instead, your response should always look like this :


{
'statusCode': 200,
'body': JSON.stringify(your_data_here),
'isBase64Encoded': false
}

Change to suit your needs, obviously.

So if your code is built to handle that input/output scheme, then you’re ready to build your SAM template. Here’s a really simple one that is as bare bones as seemed reasonable. Keep in mind that SAM has lots of options, but this should help illustrate how a template can look :


AWSTemplateFormatVersion: '2010-09-09'

Description: Simple REST API

Transform: 'AWS::Serverless-2016-10-31'

Parameters:
Project:
Description: The project name this belongs to
Type: String
Site:
Description: The name of the site that will call the api
Type: String

Globals:
Function:
Runtime: nodejs6.10
MemorySize: 128
Timeout: 10
Handler: index.handler
Environment:
Variables:
USER_INFO_TABLE: !Sub ${Project}-user-info
Api:
EndpointConfiguration: REGIONAL
Cors: !Sub "'${Site}'"
CacheClusterEnabled: false

Resources:
UserInfoTable:
Type: AWS::Serverless::SimpleTable
Properties:
TableName: !Sub ${Project}-user-info
PrimaryKey:
Name: Username
Type: String
ProvisionedThroughput:
ReadCapacityUnits: 5
WriteCapacityUnits: 5
Tags:
Project: !Sub ${Project}

GetUserFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub ${Project}-GetUser
CodeUri:
Bucket: !ImportValue LambdaCodeBucket
Key: !Sub ${Project}/get_user.zip
Description: gets user by name
Policies:
- AWSLambdaExecute
Tags:
Project: !Sub ${Project}
Events:
GetResource:
Type: Api
Properties:
Path: /user/{Username}
Method: get

That template will ask you for a couple of parameters, and will assume the code will be in an S3 bucket. In this case my S3 bucket is defined in another CF template, but you can always just add it as a parameter or hardcode it.

If you build via the CF console, you’ll have to create a change set before it will let you execute the stack build, but once that’s done it goes pretty quickly.

This template will also build a DynamoDB table. I just tossed that in as a way to demonstrate how one can do that, but SAM has some limits on what you can do with Dynamo that will likely be fine for small prototypes, but not large projects.

In fact, using SAM like the above is pretty limiting overall, but it creates things in a way that is pretty easy to track and follow, and still allows you to alter things as needed.

Once I get swagger successfully working, I’ll update with a new post about how to use it to build more advanced/cleaner APIs.

AWS SAM