Skip to content

Commit

Permalink
docs(examples): add example for AWS SAM (#674)
Browse files Browse the repository at this point in the history
  • Loading branch information
bpauwels authored May 7, 2022
1 parent 95cccab commit 0b890fb
Show file tree
Hide file tree
Showing 13 changed files with 876 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,7 @@ site

# Generated API documentation (from TypeDoc)
/api

# SAM Example copies files
/examples/sam/src/handlers/*
!/examples/sam/src/handlers/COPY_LAMBDA_FUNCTIONS_HERE
94 changes: 94 additions & 0 deletions examples/lambda-functions/get-all-items.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { Metrics } from '@aws-lambda-powertools/metrics';
import { Logger } from '@aws-lambda-powertools/logger';
import { Tracer } from '@aws-lambda-powertools/tracer';
import { DocumentClient } from 'aws-sdk/clients/dynamodb';

// Create the PowerTools clients
const metrics = new Metrics();
const logger = new Logger();
const tracer = new Tracer();

// Create DynamoDB DocumentClient and patch it for tracing
const docClient = tracer.captureAWSClient(new DocumentClient());

// Get the DynamoDB table name from environment variables
const tableName = process.env.SAMPLE_TABLE;

/**
*
* Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format
* @param {Object} event - API Gateway Lambda Proxy Input Format
*
* Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
* @returns {Object} object - API Gateway Lambda Proxy Output Format
*
*/
export const getAllItemsHandler = async (event: APIGatewayProxyEvent, context: Context): Promise<APIGatewayProxyResult> => {
if (event.httpMethod !== 'GET') {
throw new Error(`getAllItems only accepts GET method, you tried: ${event.httpMethod}`);
}

// Tracer: Get facade segment created by AWS Lambda
const segment = tracer.getSegment();

// Tracer: Create subsegment for the function & set it as active
const handlerSegment = segment.addNewSubsegment(`## ${process.env._HANDLER}`);
tracer.setSegment(handlerSegment);

// Tracer: Annotate the subsegment with the cold start & serviceName
tracer.annotateColdStart();
tracer.addServiceNameAnnotation();

// Tracer: Add annotation for the awsRequestId
tracer.putAnnotation('awsRequestId', context.awsRequestId);

// Metrics: Capture cold start metrics
metrics.captureColdStartMetric();

// Logger: Add persistent attributes to each log statement
logger.addPersistentLogAttributes({
awsRequestId: context.awsRequestId,
});

// get all items from the table (only first 1MB data, you can use `LastEvaluatedKey` to get the rest of data)
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#scan-property
// https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html
let response;
try {
if (!tableName) {
throw new Error('SAMPLE_TABLE environment variable is not set');
}

const data = await docClient.scan({
TableName: tableName
}).promise();
const items = data.Items;

// Logger: All log statements are written to CloudWatch
logger.debug(`retrieved items: ${items?.length || 0}`);

response = {
statusCode: 200,
body: JSON.stringify(items)
};
} catch (err) {
tracer.addErrorAsMetadata(err as Error);
logger.error('Error reading from table. ' + err);
response = {
statusCode: 500,
body: JSON.stringify({ 'error': 'Error reading from table.' })
};
}

// Tracer: Close subsegment (the AWS Lambda one is closed automatically)
handlerSegment.close(); // (## index.handler)

// Tracer: Set the facade segment as active again (the one created by AWS Lambda)
tracer.setSegment(segment);

// All log statements are written to CloudWatch
logger.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`);

return response;
};
96 changes: 96 additions & 0 deletions examples/lambda-functions/get-by-id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { Metrics } from '@aws-lambda-powertools/metrics';
import { Logger } from '@aws-lambda-powertools/logger';
import { Tracer } from '@aws-lambda-powertools/tracer';
import { DocumentClient } from 'aws-sdk/clients/dynamodb';

// Create the PowerTools clients
const metrics = new Metrics();
const logger = new Logger();
const tracer = new Tracer();

// Create DynamoDB DocumentClient and patch it for tracing
const docClient = tracer.captureAWSClient(new DocumentClient());

// Get the DynamoDB table name from environment variables
const tableName = process.env.SAMPLE_TABLE;

/**
*
* Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format
* @param {Object} event - API Gateway Lambda Proxy Input Format
*
* Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
* @returns {Object} object - API Gateway Lambda Proxy Output Format
*
*/

export const getByIdHandler = async (event: APIGatewayProxyEvent, context: Context): Promise<APIGatewayProxyResult> => {
if (event.httpMethod !== 'GET') {
throw new Error(`getById only accepts GET method, you tried: ${event.httpMethod}`);
}
// Tracer: Get facade segment created by AWS Lambda
const segment = tracer.getSegment();

// Tracer: Create subsegment for the function & set it as active
const handlerSegment = segment.addNewSubsegment(`## ${process.env._HANDLER}`);
tracer.setSegment(handlerSegment);

// Tracer: Annotate the subsegment with the cold start & serviceName
tracer.annotateColdStart();
tracer.addServiceNameAnnotation();

// Tracer: Add annotation for the awsRequestId
tracer.putAnnotation('awsRequestId', context.awsRequestId);

// Metrics: Capture cold start metrics
metrics.captureColdStartMetric();

// Logger: Add persistent attributes to each log statement
logger.addPersistentLogAttributes({
awsRequestId: context.awsRequestId,
});

// Get the item from the table
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#get-property
let response;
try {
if (!tableName) {
throw new Error('SAMPLE_TABLE environment variable is not set');
}
if (!event.pathParameters) {
throw new Error('event does not contain pathParameters')
}
if (!event.pathParameters.id) {
throw new Error('PathParameter id is missing')
}

const data = await docClient.get({
TableName: tableName,
Key: { id: event.pathParameters.id },
}).promise();
const item = data.Item;
response = {
statusCode: 200,
body: JSON.stringify(item)
};
} catch (err) {
tracer.addErrorAsMetadata(err as Error);
logger.error('Error reading from table. ' + err);
response = {
statusCode: 500,
body: JSON.stringify({ 'error': 'Error reading from table.' })
};
}

// Tracer: Close subsegment (the AWS Lambda one is closed automatically)
handlerSegment.close(); // (## index.handler)

// Tracer: Set the facade segment as active again (the one created by AWS Lambda)
tracer.setSegment(segment);

// All log statements are written to CloudWatch
logger.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`);

return response;
};
97 changes: 97 additions & 0 deletions examples/lambda-functions/put-item.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { Metrics } from '@aws-lambda-powertools/metrics';
import { Logger } from '@aws-lambda-powertools/logger';
import { Tracer } from '@aws-lambda-powertools/tracer';
import { DocumentClient } from 'aws-sdk/clients/dynamodb';

// Create the PowerTools clients
const metrics = new Metrics();
const logger = new Logger();
const tracer = new Tracer();

// Create DynamoDB DocumentClient and patch it for tracing
const docClient = tracer.captureAWSClient(new DocumentClient());

// Get the DynamoDB table name from environment variables
const tableName = process.env.SAMPLE_TABLE;

/**
*
* Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format
* @param {Object} event - API Gateway Lambda Proxy Input Format
*
* Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
* @returns {Object} object - API Gateway Lambda Proxy Output Format
*
*/

export const putItemHandler = async (event: APIGatewayProxyEvent, context: Context): Promise<APIGatewayProxyResult> => {
if (event.httpMethod !== 'POST') {
throw new Error(`putItem only accepts POST method, you tried: ${event.httpMethod}`);
}
// Tracer: Get facade segment created by AWS Lambda
const segment = tracer.getSegment();

// Tracer: Create subsegment for the function & set it as active
const handlerSegment = segment.addNewSubsegment(`## ${process.env._HANDLER}`);
tracer.setSegment(handlerSegment);

// Tracer: Annotate the subsegment with the cold start & serviceName
tracer.annotateColdStart();
tracer.addServiceNameAnnotation();

// Tracer: Add annotation for the awsRequestId
tracer.putAnnotation('awsRequestId', context.awsRequestId);

// Metrics: Capture cold start metrics
metrics.captureColdStartMetric();

// Logger: Add persistent attributes to each log statement
logger.addPersistentLogAttributes({
awsRequestId: context.awsRequestId,
});

// Creates a new item, or replaces an old item with a new item
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#put-property
let response;
try {
if (!tableName) {
throw new Error('SAMPLE_TABLE environment variable is not set');
}
if (!event.body) {
throw new Error('Event does not contain body')
}

// Get id and name from the body of the request
const body = JSON.parse(event.body);
const id = body.id;
const name = body.name;

await docClient.put({
TableName: tableName,
Item: { id: id, name: name }
}).promise();
response = {
statusCode: 200,
body: JSON.stringify(body)
};
} catch (err) {
tracer.addErrorAsMetadata(err as Error);
logger.error('Error writing data to table. ' + err);
response = {
statusCode: 500,
body: JSON.stringify({ 'error': 'Error writing data to table.' })
};
}

// Tracer: Close subsegment (the AWS Lambda one is closed automatically)
handlerSegment.close(); // (## index.handler)

// Tracer: Set the facade segment as active again (the one created by AWS Lambda)
tracer.setSegment(segment);

// All log statements are written to CloudWatch
logger.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`);

return response;
};
28 changes: 28 additions & 0 deletions examples/lambda-functions/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"noImplicitAny": true,
"target": "ES2020",
"module": "commonjs",
"declaration": true,
"declarationMap": true,
"outDir": "lib",
"removeComments": false,
"strict": true,
"inlineSourceMap": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"pretty": true,
"esModuleInterop": true
},
"exclude": [ "./node_modules"],
"watchOptions": {
"watchFile": "useFsEvents",
"watchDirectory": "useFsEvents",
"fallbackPolling": "dynamicPriority"
},
"lib": [ "es2020" ],
"types": [
"node"
]
}
Loading

0 comments on commit 0b890fb

Please sign in to comment.