diff --git a/_instructor/core-concepts/1-http-hello-world/README.md b/_instructor/core-concepts/1-http-hello-world/README.md index d97902a..e142004 100644 --- a/_instructor/core-concepts/1-http-hello-world/README.md +++ b/_instructor/core-concepts/1-http-hello-world/README.md @@ -5,6 +5,7 @@ This lesson will walk through creating a basic function triggered by a http GET - [Lesson Steps](#lesson-steps) - [Complete code](#complete-code) +- [Additional Resources](#additional-resources) ## Lesson Steps diff --git a/_instructor/core-concepts/1-http-hello-world/handler.js b/_instructor/core-concepts/1-http-hello-world/handler.js index 7266aee..a87f437 100644 --- a/_instructor/core-concepts/1-http-hello-world/handler.js +++ b/_instructor/core-concepts/1-http-hello-world/handler.js @@ -1,5 +1,5 @@ -module.exports.hello = (event, context, callback) => { +export const hello = (event, context) => { // WORKSHOP_START /* Step 1. In this_file, Create a `200` response code and return the `event` data in the response body. @@ -8,7 +8,7 @@ module.exports.hello = (event, context, callback) => { For more details, see the http event docs link http://bit.ly/2mkgV4P */ const response = {} - return callback(null, response); + return response; // WORKSHOP_END // FINAL_START const response = { @@ -19,7 +19,6 @@ module.exports.hello = (event, context, callback) => { event: event, }), } - /** callback(error, response) */ - return callback(null, response) + return response // FINAL_END } diff --git a/_instructor/core-concepts/1-http-hello-world/serverless.yml b/_instructor/core-concepts/1-http-hello-world/serverless.yml index 5c2ff68..ed9ee7b 100644 --- a/_instructor/core-concepts/1-http-hello-world/serverless.yml +++ b/_instructor/core-concepts/1-http-hello-world/serverless.yml @@ -3,7 +3,7 @@ service: my-first-service provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x functions: hello: diff --git a/_instructor/core-concepts/2-http-dynamic-content/handler.js b/_instructor/core-concepts/2-http-dynamic-content/handler.js index 5e5ea7b..0e4d47d 100644 --- a/_instructor/core-concepts/2-http-dynamic-content/handler.js +++ b/_instructor/core-concepts/2-http-dynamic-content/handler.js @@ -6,7 +6,7 @@ Finally remember to set the headers of the response as `'Content-Type': 'text/html'` to return HTML instead of the default `json` */ -module.exports.queryParamsExample = (event, context, callback) => { +export const queryParamsExample = (event, context) => { // WORKSHOP_START const response = { statusCode: 200, @@ -14,7 +14,7 @@ module.exports.queryParamsExample = (event, context, callback) => { message: 'Hi ⊂◉‿◉つ', }), } - return callback(null, response) + return response // WORKSHOP_END // FINAL_START let name @@ -24,13 +24,13 @@ module.exports.queryParamsExample = (event, context, callback) => { /* generate the hello paragraph */ const helloParagraph = greetPerson(name) - return callback(null, { + return { statusCode: 200, headers: { 'Content-Type': 'text/html', }, body: generateHtmlPage(helloParagraph), - }) + } // FINAL_END } @@ -42,7 +42,7 @@ module.exports.queryParamsExample = (event, context, callback) => { Finally, remember to set the headers of the response as `'Content-Type': 'text/html'` to return HTML instead of the default `json` */ -module.exports.pathParamsExample = (event, context, callback) => { +export const pathParamsExample = (event, context) => { // WORKSHOP_START const response = { statusCode: 200, @@ -50,7 +50,7 @@ module.exports.pathParamsExample = (event, context, callback) => { message: 'Hi ⊂◉‿◉つ', }), } - return callback(null, response) + return response // WORKSHOP_END // FINAL_START let name @@ -61,14 +61,14 @@ module.exports.pathParamsExample = (event, context, callback) => { /* generate the hello paragraph */ const helloParagraph = greetPerson(name) - // callback is sending HTML back - return callback(null, { + // return is sending HTML back + return { statusCode: 200, headers: { 'Content-Type': 'text/html', }, body: generateHtmlPage(helloParagraph), - }) + } // FINAL_END } diff --git a/_instructor/core-concepts/2-http-dynamic-content/serverless.yml b/_instructor/core-concepts/2-http-dynamic-content/serverless.yml index 42f2a84..469bad7 100644 --- a/_instructor/core-concepts/2-http-dynamic-content/serverless.yml +++ b/_instructor/core-concepts/2-http-dynamic-content/serverless.yml @@ -3,7 +3,7 @@ service: using-dynamic-value provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x functions: queryParamsExample: diff --git a/_instructor/core-concepts/3-http-post-with-cors/README.md b/_instructor/core-concepts/3-http-post-with-cors/README.md index 4a32d6c..53edb19 100644 --- a/_instructor/core-concepts/3-http-post-with-cors/README.md +++ b/_instructor/core-concepts/3-http-post-with-cors/README.md @@ -13,6 +13,7 @@ This lesson will walk through creating an http function triggered by a `POST` re - [Lesson Steps](#lesson-steps) - [Complete code](#complete-code) +- [Locking down cors to a specific domain](#locking-down-cors-to-a-specific-domain) ## Lesson Steps diff --git a/_instructor/core-concepts/3-http-post-with-cors/handler.js b/_instructor/core-concepts/3-http-post-with-cors/handler.js index 1515fb1..7f09fa7 100644 --- a/_instructor/core-concepts/3-http-post-with-cors/handler.js +++ b/_instructor/core-concepts/3-http-post-with-cors/handler.js @@ -6,7 +6,7 @@ For additional information, see the cors docs http://bit.ly/2FlFSWB */ // WORKSHOP_END -module.exports.functionWithCors = (event, context, callback) => { +export const functionWithCors = (event, context) => { const response = { statusCode: 200, // FINAL_START @@ -22,5 +22,5 @@ module.exports.functionWithCors = (event, context, callback) => { event: event, }), } - return callback(null, response) + return response } diff --git a/_instructor/core-concepts/3-http-post-with-cors/serverless.yml b/_instructor/core-concepts/3-http-post-with-cors/serverless.yml index b86ab81..bb6504b 100644 --- a/_instructor/core-concepts/3-http-post-with-cors/serverless.yml +++ b/_instructor/core-concepts/3-http-post-with-cors/serverless.yml @@ -3,7 +3,7 @@ service: using-http-cors-example provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x functions: hello: diff --git a/_instructor/core-concepts/4-using-env-vars/handler.js b/_instructor/core-concepts/4-using-env-vars/handler.js index 01a9d7b..a6da80c 100644 --- a/_instructor/core-concepts/4-using-env-vars/handler.js +++ b/_instructor/core-concepts/4-using-env-vars/handler.js @@ -8,7 +8,7 @@ Return the environment variable in the `foo` function response */ // WORKSHOP_END -module.exports.foo = (event, context, callback) => { +export const foo = (event, context) => { // FINAL_START console.log('process.env.MY_ENV_VAR', process.env.MY_ENV_VAR) /* MY_ENV_VAR_FOR_BAR will be undefined */ @@ -31,7 +31,7 @@ module.exports.foo = (event, context, callback) => { // FINAL_END }), } - return callback(null, response) + return response } // WORKSHOP_START @@ -42,7 +42,7 @@ module.exports.foo = (event, context, callback) => { Return the environment variable in the `bar` function response */ // WORKSHOP_END -module.exports.bar = (event, context, callback) => { +export const bar = (event, context) => { // FINAL_START /* both env variables will be accessible in bar */ console.log('process.env.MY_ENV_VAR', process.env.MY_ENV_VAR) @@ -66,5 +66,5 @@ module.exports.bar = (event, context, callback) => { // FINAL_END }), } - return callback(null, response) + return response } diff --git a/_instructor/core-concepts/4-using-env-vars/serverless.yml b/_instructor/core-concepts/4-using-env-vars/serverless.yml index 0bdbf07..3261b49 100644 --- a/_instructor/core-concepts/4-using-env-vars/serverless.yml +++ b/_instructor/core-concepts/4-using-env-vars/serverless.yml @@ -6,7 +6,7 @@ service: using-env-variables-example # WORKSHOP_END provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x # FINAL_START environment: MY_ENV_VAR: 'hello there' diff --git a/_instructor/core-concepts/5-using-serverless-variable-syntax/handler.js b/_instructor/core-concepts/5-using-serverless-variable-syntax/handler.js index 4a928f0..d16ec23 100644 --- a/_instructor/core-concepts/5-using-serverless-variable-syntax/handler.js +++ b/_instructor/core-concepts/5-using-serverless-variable-syntax/handler.js @@ -1,5 +1,5 @@ -module.exports.foo = (event, context, callback) => { +export const foo = (event, context) => { // FINAL_START console.log('process.env.MY_SECRET', process.env.MY_SECRET) // FINAL_END @@ -15,5 +15,5 @@ module.exports.foo = (event, context, callback) => { message: `my super secret thing ${process.env.MY_SECRET}` }), } - return callback(null, response) + return response } diff --git a/_instructor/core-concepts/5-using-serverless-variable-syntax/serverless.yml b/_instructor/core-concepts/5-using-serverless-variable-syntax/serverless.yml index f71d245..2838d64 100644 --- a/_instructor/core-concepts/5-using-serverless-variable-syntax/serverless.yml +++ b/_instructor/core-concepts/5-using-serverless-variable-syntax/serverless.yml @@ -6,7 +6,7 @@ service: serverless-variables-syntax-example # WORKSHOP_END provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x # WORKSHOP_START environment: MY_SECRET: xyz123 diff --git a/_instructor/core-concepts/6-using-addition-resources/handler.js b/_instructor/core-concepts/6-using-addition-resources/handler.js index fc44edb..fd1b468 100644 --- a/_instructor/core-concepts/6-using-addition-resources/handler.js +++ b/_instructor/core-concepts/6-using-addition-resources/handler.js @@ -1,23 +1,25 @@ /* Include the AWS sdk. * No need to add to package.json. It's included in lambda env */ -const AWS = require('aws-sdk') +import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; +import { DynamoDBDocumentClient, PutCommand, ScanCommand } from '@aws-sdk/lib-dynamodb'; // Connect to DynamoDB -const dynamoDb = new AWS.DynamoDB.DocumentClient() +const client = new DynamoDBClient({}); +const dynamoDb = DynamoDBDocumentClient.from(client); // Save item in DynamoDB table -module.exports.create = (event, context, callback) => { +export const create = async (event, context) => { const timestamp = new Date().getTime() const body = JSON.parse(event.body) if (!body || !body.email) { - return callback(null, { + return { statusCode: 401, headers: { 'Content-Type': 'text/plain' }, body: JSON.stringify({ error: 'no body found or email found' }) - }) + } } const params = { @@ -30,48 +32,46 @@ module.exports.create = (event, context, callback) => { } // write the todo to the database - dynamoDb.put(params, (error) => { - // handle potential errors - if (error) { - console.error(error) - return callback(null, { - statusCode: error.statusCode || 501, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t create the dynamo item.', - }) - } - + try { + await dynamoDb.send(new PutCommand(params)); // create a response const response = { statusCode: 200, body: JSON.stringify(params.Item), } - return callback(null, response) - }) + return response + } catch (error) { + // handle potential errors + console.error(error) + return { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t create the dynamo item.', + } + } } /* Scan a dynamoDB table and return items */ -module.exports.scan = (event, context, callback) => { +export const scan = async (event, context) => { const params = { TableName: process.env.MY_TABLE, } // fetch all todos from the database - dynamoDb.scan(params, (error, result) => { - // handle potential errors - if (error) { - console.error(error) - return callback(null, { - statusCode: error.statusCode || 501, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t fetch the todos.', - }) - } - + try { + const result = await dynamoDb.send(new ScanCommand(params)); // create a response const response = { statusCode: 200, body: JSON.stringify(result.Items), } - return callback(null, response) - }) + return response + } catch (error) { + // handle potential errors + console.error(error) + return { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t fetch the todos.', + } + } } diff --git a/_instructor/core-concepts/6-using-addition-resources/serverless.yml b/_instructor/core-concepts/6-using-addition-resources/serverless.yml index 5017cd3..76e65aa 100644 --- a/_instructor/core-concepts/6-using-addition-resources/serverless.yml +++ b/_instructor/core-concepts/6-using-addition-resources/serverless.yml @@ -3,7 +3,7 @@ service: event-driven-app provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x # WORKSHOP_START # Step 3. In this_file, add the database table name to the service's `environment` variables. The `aws-sdk` will need to know the table name in order to access the table # WORKSHOP_END diff --git a/_instructor/core-concepts/7-using-serverless-plugins/handler.js b/_instructor/core-concepts/7-using-serverless-plugins/handler.js index c355f23..2e6e0db 100644 --- a/_instructor/core-concepts/7-using-serverless-plugins/handler.js +++ b/_instructor/core-concepts/7-using-serverless-plugins/handler.js @@ -1,5 +1,5 @@ -module.exports.hello = (event, context, callback) => { +export const hello = (event, context) => { const response = { /* Status code required for default lambda integration */ statusCode: 200, @@ -8,5 +8,5 @@ module.exports.hello = (event, context, callback) => { }), } /** callback(error, response) */ - return callback(null, response) + return response } diff --git a/_instructor/core-concepts/7-using-serverless-plugins/package.json b/_instructor/core-concepts/7-using-serverless-plugins/package.json index 06a58b0..6ef1a12 100644 --- a/_instructor/core-concepts/7-using-serverless-plugins/package.json +++ b/_instructor/core-concepts/7-using-serverless-plugins/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "using-serverless-plugins", "main": "index.js", + "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/_instructor/core-concepts/7-using-serverless-plugins/serverless.yml b/_instructor/core-concepts/7-using-serverless-plugins/serverless.yml index 517cf37..607b28c 100644 --- a/_instructor/core-concepts/7-using-serverless-plugins/serverless.yml +++ b/_instructor/core-concepts/7-using-serverless-plugins/serverless.yml @@ -12,7 +12,7 @@ custom: provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x functions: hello: diff --git a/_instructor/core-concepts/8-using-multiple-stages/handler.js b/_instructor/core-concepts/8-using-multiple-stages/handler.js index c355f23..2e6e0db 100644 --- a/_instructor/core-concepts/8-using-multiple-stages/handler.js +++ b/_instructor/core-concepts/8-using-multiple-stages/handler.js @@ -1,5 +1,5 @@ -module.exports.hello = (event, context, callback) => { +export const hello = (event, context) => { const response = { /* Status code required for default lambda integration */ statusCode: 200, @@ -8,5 +8,5 @@ module.exports.hello = (event, context, callback) => { }), } /** callback(error, response) */ - return callback(null, response) + return response } diff --git a/_instructor/core-concepts/8-using-multiple-stages/package.json b/_instructor/core-concepts/8-using-multiple-stages/package.json index 6034242..5e0e359 100644 --- a/_instructor/core-concepts/8-using-multiple-stages/package.json +++ b/_instructor/core-concepts/8-using-multiple-stages/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "", "main": "index.js", + "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/_instructor/core-concepts/8-using-multiple-stages/serverless.yml b/_instructor/core-concepts/8-using-multiple-stages/serverless.yml index c306b0f..c7488b2 100644 --- a/_instructor/core-concepts/8-using-multiple-stages/serverless.yml +++ b/_instructor/core-concepts/8-using-multiple-stages/serverless.yml @@ -11,7 +11,7 @@ custom: provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x # WORKSHOP_START # Step 4. In this_file, set the stage key to the custom.stage value set in previous step # WORKSHOP_END diff --git a/_instructor/events/dynamodb-streams/README.md b/_instructor/events/dynamodb-streams/README.md index 04bd597..082c5c5 100644 --- a/_instructor/events/dynamodb-streams/README.md +++ b/_instructor/events/dynamodb-streams/README.md @@ -6,6 +6,7 @@ This lesson we will be adding a delete function to remove users from the databas - [Lesson Steps](#lesson-steps) - [DynamoDB Stream CloudFormation](#dynamodb-stream-cloudformation) - [Complete code](#complete-code) +- [Additional Resources](#additional-resources) ## Lesson Steps diff --git a/_instructor/events/dynamodb-streams/handler.js b/_instructor/events/dynamodb-streams/handler.js index ad17d9d..43f8ec9 100644 --- a/_instructor/events/dynamodb-streams/handler.js +++ b/_instructor/events/dynamodb-streams/handler.js @@ -1,23 +1,25 @@ /* Include the AWS sdk. * No need to add to package.json. It's included in lambda env */ -const AWS = require('aws-sdk') +import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; +import { DynamoDBDocumentClient, PutCommand, ScanCommand, DeleteCommand } from '@aws-sdk/lib-dynamodb'; // Connect to DynamoDB -const dynamoDb = new AWS.DynamoDB.DocumentClient() +const client = new DynamoDBClient({}); +const dynamoDb = DynamoDBDocumentClient.from(client); // Save item in DynamoDB table -module.exports.create = (event, context, callback) => { +export const create = async (event, context) => { const timestamp = new Date().getTime() const body = JSON.parse(event.body) if (!body || !body.email) { - return callback(null, { + return { statusCode: 401, headers: { 'Content-Type': 'text/plain' }, body: JSON.stringify({ error: 'no body found or email found' }) - }) + } } const params = { @@ -30,64 +32,62 @@ module.exports.create = (event, context, callback) => { } // write the todo to the database - dynamoDb.put(params, (error) => { - // handle potential errors - if (error) { - console.error(error) - return callback(null, { - statusCode: error.statusCode || 501, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t create the dynamo item.', - }) - } - + try { + await dynamoDb.send(new PutCommand(params)); // create a response const response = { statusCode: 200, body: JSON.stringify(params.Item), } - return callback(null, response) - }) + return response + } catch (error) { + // handle potential errors + console.error(error) + return { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t create the dynamo item.', + } + } } /* Scan a dynamoDB table and return items */ -module.exports.scan = (event, context, callback) => { +export const scan = async (event, context) => { const params = { TableName: process.env.MY_TABLE, } // fetch all todos from the database - dynamoDb.scan(params, (error, result) => { - // handle potential errors - if (error) { - console.error(error) - return callback(null, { - statusCode: error.statusCode || 501, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t fetch the todos.', - }) - } - + try { + const result = await dynamoDb.send(new ScanCommand(params)); // create a response const response = { statusCode: 200, body: JSON.stringify(result.Items), } - return callback(null, response) - }) + return response + } catch (error) { + // handle potential errors + console.error(error) + return { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t fetch the todos.', + } + } } -module.exports.delete = (event, context, callback) => { +export const delete = async (event, context) => { const body = JSON.parse(event.body) if (!body || !body.id) { - return callback(null, { + return { statusCode: 401, headers: { 'Content-Type': 'text/plain' }, body: JSON.stringify({ error: 'no body found or id found' }) - }) + } } // WORKSHOP_START /* Step 1. In this_file, implement the delete item function here via `dynamoDb.delete` method. @@ -105,17 +105,8 @@ module.exports.delete = (event, context, callback) => { } // delete the todo from the database - dynamoDb.delete(params, (error) => { - // handle potential errors - if (error) { - console.error(error) - return callback(null, { - statusCode: error.statusCode || 501, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t remove the todo item.', - }) - } - + try { + await dynamoDb.send(new DeleteCommand(params)); // create a response const response = { statusCode: 200, @@ -123,8 +114,16 @@ module.exports.delete = (event, context, callback) => { message: `user ${body.id} deleted` }), } - return callback(null, response) - }) + return response + } catch (error) { + // handle potential errors + console.error(error) + return { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t remove the todo item.', + } + } // FINAL_END } @@ -137,7 +136,7 @@ module.exports.delete = (event, context, callback) => { */ // WORKSHOP_END /* Function to handle items on the dynamoDB stream */ -module.exports.dynamoStreamHandler = (event, context, callback) => { +export const dynamoStreamHandler = (event, context) => { // FINAL_START event.Records.forEach((record) => { console.log(record.eventID) @@ -150,6 +149,6 @@ module.exports.dynamoStreamHandler = (event, context, callback) => { console.log('REMOVAL EVENT. DO REMOVAL STUFF') } }) - return callback(null, `Successfully processed ${event.Records.length} records.`); + return `Successfully processed ${event.Records.length} records.`; // FINAL_END } diff --git a/_instructor/events/dynamodb-streams/package.json b/_instructor/events/dynamodb-streams/package.json new file mode 100644 index 0000000..41bb0ba --- /dev/null +++ b/_instructor/events/dynamodb-streams/package.json @@ -0,0 +1,10 @@ +{ + "name": "dynamodb-streams-handler", + "version": "1.0.0", + "description": "DynamoDB streams handler using AWS SDK v3", + "type": "module", + "dependencies": { + "@aws-sdk/client-dynamodb": "^3.668.0", + "@aws-sdk/lib-dynamodb": "^3.668.0" + } +} \ No newline at end of file diff --git a/_instructor/events/dynamodb-streams/serverless.yml b/_instructor/events/dynamodb-streams/serverless.yml index 2047aa2..33326f2 100644 --- a/_instructor/events/dynamodb-streams/serverless.yml +++ b/_instructor/events/dynamodb-streams/serverless.yml @@ -3,7 +3,7 @@ service: event-driven-app provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x environment: MY_TABLE: { Ref: myDynamoTable } iamRoleStatements: diff --git a/_instructor/events/kinesis/kinesis-basic/handler.js b/_instructor/events/kinesis/kinesis-basic/handler.js index e68f254..66c267d 100644 --- a/_instructor/events/kinesis/kinesis-basic/handler.js +++ b/_instructor/events/kinesis/kinesis-basic/handler.js @@ -3,7 +3,7 @@ Step 6. the `processEvents` in this_file will handle the batch processing of kinesis events */ // WORKSHOP_END -module.exports.processEvents = (event, context, callback) => { +export const processEvents = (event, context) => { // Process kinesis event if (event.Records) { event.Records.forEach((record) => { @@ -23,5 +23,5 @@ module.exports.processEvents = (event, context, callback) => { console.log('NEGATIVE decoded record:', payload) }) } - return callback(null, `Successfully processed ${event.Records.length} records.`); + return `Successfully processed ${event.Records.length} records.`; } diff --git a/_instructor/events/kinesis/kinesis-basic/package.json b/_instructor/events/kinesis/kinesis-basic/package.json index 9afa69e..4a8b4a7 100644 --- a/_instructor/events/kinesis/kinesis-basic/package.json +++ b/_instructor/events/kinesis/kinesis-basic/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "", "main": "handler.js", + "type": "module", "scripts": { "simulate": "node ./scripts/putRecords.js --name=$npm_config_name --count=$npm_config_count --region=$npm_config_region", "test": "echo \"Error: no test specified\" && exit 1" diff --git a/_instructor/events/kinesis/kinesis-basic/scripts/putRecords.js b/_instructor/events/kinesis/kinesis-basic/scripts/putRecords.js index fefc163..45148da 100644 --- a/_instructor/events/kinesis/kinesis-basic/scripts/putRecords.js +++ b/_instructor/events/kinesis/kinesis-basic/scripts/putRecords.js @@ -1,5 +1,7 @@ -const AWS = require('aws-sdk') -const argv = require('yargs').argv +import { KinesisClient, PutRecordsCommand } from '@aws-sdk/client-kinesis'; +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; +const argv = yargs(hideBin(process.argv)).argv; const streamName = argv.name const numberOfEvents = argv.count || 20 @@ -10,8 +12,8 @@ if (!streamName) { return false } -function putRecords() { - const kinesis = new AWS.Kinesis({region: AWS_REGION}) +async function putRecords() { + const kinesis = new KinesisClient({region: AWS_REGION}) const params = { Records: Array.from(Array(parseInt(numberOfEvents))).map((_, i) => { const n = (Math.random() * 100).toFixed(2) * (Math.random() > 0.5 ? 1 : -1); @@ -28,7 +30,7 @@ function putRecords() { }), StreamName: streamName, }; - return kinesis.putRecords(params).promise(); + return await kinesis.send(new PutRecordsCommand(params)); } putRecords().then((res) => { diff --git a/_instructor/events/kinesis/kinesis-basic/serverless.yml b/_instructor/events/kinesis/kinesis-basic/serverless.yml index 1409cb9..66af900 100644 --- a/_instructor/events/kinesis/kinesis-basic/serverless.yml +++ b/_instructor/events/kinesis/kinesis-basic/serverless.yml @@ -14,7 +14,7 @@ custom: provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x stage: ${self:custom.stage} # WORKSHOP_START diff --git a/_instructor/events/s3/README.md b/_instructor/events/s3/README.md index a48f52a..81b6523 100644 --- a/_instructor/events/s3/README.md +++ b/_instructor/events/s3/README.md @@ -7,6 +7,7 @@ This lesson will walk through triggering a lambda function in response to an s3 - [S3 bucket CloudFormation template](#s3-bucket-cloudformation-template) - [Triggering events from existing buckets](#triggering-events-from-existing-buckets) - [Complete code](#complete-code) +- [Alternative methods](#alternative-methods) ## Lesson Steps diff --git a/_instructor/events/s3/package.json b/_instructor/events/s3/package.json index 9692eda..50c8747 100644 --- a/_instructor/events/s3/package.json +++ b/_instructor/events/s3/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "aws s3 image resizer", "main": "handler.js", + "type": "module", "scripts": { "deploy": "sls deploy" }, diff --git a/_instructor/events/s3/resize.js b/_instructor/events/s3/resize.js index e7439e9..ca362dc 100644 --- a/_instructor/events/s3/resize.js +++ b/_instructor/events/s3/resize.js @@ -1,16 +1,17 @@ // dependencies -const util = require('util') -const path = require('path') -const async = require('async') -const AWS = require('aws-sdk') -const gm = require('gm').subClass({ +import util from 'util'; +import path from 'path'; +import async from 'async'; +import { S3Client, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3'; +import gm from 'gm'; +const gmIM = gm.subClass({ imageMagick: true }) // get reference to S3 client -const s3 = new AWS.S3() +const s3 = new S3Client({}); -module.exports.resizeImage = (event, context, callback) => { +export const resizeImage = async (event, context) => { // Read options from the event. console.log("Reading options from event:\n", util.inspect(event, { depth: 5 @@ -23,12 +24,12 @@ module.exports.resizeImage = (event, context, callback) => { if (srcBucket === dstBucket) { console.log('Destination bucket must not match source bucket.') console.log('This would result in an infinite loop') - return callback(null, { + return { statusCode: 501, body: JSON.stringify({ error: 'Destination Matches source bucket' }), - }); + }; } // Image sizes and output folder paths @@ -79,10 +80,10 @@ module.exports.resizeImage = (event, context, callback) => { // Download the image from S3 into a buffer. // sadly it downloads the image several times, but we couldn't place it outside // the variable was not recognized - s3.getObject({ + s3.send(new GetObjectCommand({ Bucket: srcBucket, Key: srcKey - }, next) + })).then(result => next(null, result)).catch(next) console.timeEnd("downloadImage") }, function convert(response, next) { @@ -90,7 +91,7 @@ module.exports.resizeImage = (event, context, callback) => { console.time("convertImage") console.log(`Reponse content type: ${response.ContentType}`) console.log("Conversion") - gm(response.Body) + gmIM(response.Body) .antialias(true) .density(300) .toBuffer('JPG', (err, buffer) => { @@ -106,7 +107,7 @@ module.exports.resizeImage = (event, context, callback) => { console.time("processImage") // Transform the image buffer in memory. // gm(response.Body).size(function(err, size) { - gm(response).size(function(err, size) { + gmIM(response).size(function(err, size) { // console.log("buf content type " + buf.ContentType) if (err) { console.log(err) @@ -140,12 +141,12 @@ module.exports.resizeImage = (event, context, callback) => { console.log("upload: " + index) console.log(`uploadPath: /${keyPath}`) // Stream the transformed image to a different folder. - s3.putObject({ + s3.send(new PutObjectCommand({ Bucket: dstBucket, Key: keyPath, Body: data, ContentType: 'JPG' - }, next) + })).then(result => next(null, result)).catch(next) console.timeEnd("uploadImage") } ], function(err, result) { @@ -170,6 +171,6 @@ module.exports.resizeImage = (event, context, callback) => { imageProcessed: true }), } - return callback(null, response) + return response }) } diff --git a/_instructor/events/s3/save.js b/_instructor/events/s3/save.js index e490955..e28fa15 100644 --- a/_instructor/events/s3/save.js +++ b/_instructor/events/s3/save.js @@ -1,31 +1,30 @@ -const fetch = require('node-fetch') -const AWS = require('aws-sdk') -const s3 = new AWS.S3() +import fetch from 'node-fetch'; +import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'; +const s3 = new S3Client({}); const BUCKET_NAME = process.env.BUCKET_NAME -module.exports.saveImage = (event, context, callback) => { +export const saveImage = async (event, context) => { const key = event.key || JSON.parse(event.body).key const imageURL = event.image_url || JSON.parse(event.body).image_url - fetch(imageURL) - .then(response => response.buffer()) - .then(buffer => ( - s3.putObject({ - Bucket: BUCKET_NAME, - Key: key, - Body: buffer, - }).promise() - )) - .then(() => { - return callback(null, { - statusCode: 200, - body: JSON.stringify({ - success: true, - message: 'image saved to bucket' - }), - }) - }) - .catch((error) => { - return callback(error, null) - }) + try { + const response = await fetch(imageURL); + const buffer = await response.buffer(); + + await s3.send(new PutObjectCommand({ + Bucket: BUCKET_NAME, + Key: key, + Body: buffer, + })); + + return { + statusCode: 200, + body: JSON.stringify({ + success: true, + message: 'image saved to bucket' + }), + } + } catch (error) { + throw error + } } diff --git a/_instructor/events/s3/serverless.yml b/_instructor/events/s3/serverless.yml index 61ecbc9..031b86c 100644 --- a/_instructor/events/s3/serverless.yml +++ b/_instructor/events/s3/serverless.yml @@ -13,7 +13,7 @@ custom: provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x # WORKSHOP_START # Step 4. In this_file, Expose the `bucketName` to `environment` variables # # WORKSHOP_END diff --git a/_instructor/events/schedule/handler.js b/_instructor/events/schedule/handler.js index 5855b19..e6b1521 100644 --- a/_instructor/events/schedule/handler.js +++ b/_instructor/events/schedule/handler.js @@ -2,7 +2,7 @@ /* Step 2. In this_file, use the `cronFunction` function to do something interesting */ // WORKSHOP_END /* Cron function */ -module.exports.cronFunction = (event, context, callback) => { +export const cronFunction = (event, context, callback) => { // FINAL_START const time = new Date() console.log(`Function "${context.functionName}" ran at ${time}`) diff --git a/_instructor/events/schedule/serverless.yml b/_instructor/events/schedule/serverless.yml index 07c882b..cc69dc3 100644 --- a/_instructor/events/schedule/serverless.yml +++ b/_instructor/events/schedule/serverless.yml @@ -3,7 +3,7 @@ service: aws-scheduled-cron provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x functions: cron: diff --git a/_instructor/events/sns/handler.js b/_instructor/events/sns/handler.js index 17be91e..dda8f90 100644 --- a/_instructor/events/sns/handler.js +++ b/_instructor/events/sns/handler.js @@ -1,8 +1,8 @@ -const AWS = require('aws-sdk') -const sns = new AWS.SNS() +import { SNSClient, PublishCommand } from '@aws-sdk/client-sns'; +const sns = new SNSClient({}); const TOPIC_NAME = process.env.TOPIC_NAME -module.exports.eventProducer = (event, context, callback) => { +export const eventProducer = async (event, context) => { const functionArnCols = context.invokedFunctionArn.split(':') const region = functionArnCols[3] const accountId = functionArnCols[4] @@ -15,22 +15,22 @@ module.exports.eventProducer = (event, context, callback) => { TopicArn: `arn:aws:sns:${region}:${accountId}:${TOPIC_NAME}` } - sns.publish(params, (error, data) => { - if (error) { - return callback(error) - } - return callback(null, { + try { + const data = await sns.send(new PublishCommand(params)); + return { statusCode: 200, body: JSON.stringify({ message: `Message successfully published to SNS topic "${TOPIC_NAME}"` }), - }) - }); + } + } catch (error) { + throw error + } }; -module.exports.eventConsumer = (event, context, callback) => { +export const eventConsumer = (event, context) => { // print out the event information on the console (so that we can see it in the CloudWatch logs) console.log(`I'm triggered by "eventProducer" through the SNS topic "${TOPIC_NAME}"`); console.log(`event:\n${JSON.stringify(event, null, 2)}`); - callback(null, { event }); + return { event }; }; diff --git a/_instructor/events/sns/package.json b/_instructor/events/sns/package.json new file mode 100644 index 0000000..f2bc0d7 --- /dev/null +++ b/_instructor/events/sns/package.json @@ -0,0 +1,9 @@ +{ + "name": "sns-handler", + "version": "1.0.0", + "description": "SNS handler using AWS SDK v3", + "type": "module", + "dependencies": { + "@aws-sdk/client-sns": "^3.668.0" + } +} \ No newline at end of file diff --git a/_instructor/events/sns/serverless.yml b/_instructor/events/sns/serverless.yml index 3072317..9e591c7 100644 --- a/_instructor/events/sns/serverless.yml +++ b/_instructor/events/sns/serverless.yml @@ -6,7 +6,7 @@ custom: provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x environment: TOPIC_NAME: ${self:custom.topicName} iamRoleStatements: diff --git a/_instructor/events/sns/sns-advanced/handler.js b/_instructor/events/sns/sns-advanced/handler.js index 24d933f..aa09553 100644 --- a/_instructor/events/sns/sns-advanced/handler.js +++ b/_instructor/events/sns/sns-advanced/handler.js @@ -1,8 +1,8 @@ -const AWS = require('aws-sdk') -const sns = new AWS.SNS() +import { SNSClient, PublishCommand } from '@aws-sdk/client-sns'; +const sns = new SNSClient({}); const TOPIC_NAME = process.env.TOPIC_NAME -module.exports.eventProducer = (event, context, callback) => { +export const eventProducer = async (event, context) => { const functionArnCols = context.invokedFunctionArn.split(':') const region = functionArnCols[3] const accountId = functionArnCols[4] @@ -12,24 +12,24 @@ module.exports.eventProducer = (event, context, callback) => { TopicArn: `arn:aws:sns:${region}:${accountId}:${TOPIC_NAME}` } - sns.publish(params, (error, data) => { - if (error) { - return callback(error) - } - return callback(null, { + try { + const data = await sns.send(new PublishCommand(params)); + return { statusCode: 200, body: JSON.stringify({ message: `Message successfully published to SNS topic "${TOPIC_NAME}"` }), - }) - }) + } + } catch (error) { + throw error + } } -module.exports.eventConsumer = (event, context, callback) => { +export const eventConsumer = (event, context) => { // print out the event information on the console (so that we can see it in the CloudWatch logs) console.log(`I'm triggered by "eventProducer" through the SNS topic "${TOPIC_NAME}"`) console.log(`event:\n${JSON.stringify(event, null, 2)}`) - return callback(null, { + return { event: event - }) + } } diff --git a/_instructor/events/sns/sns-advanced/package.json b/_instructor/events/sns/sns-advanced/package.json index 0f744a6..1b88407 100644 --- a/_instructor/events/sns/sns-advanced/package.json +++ b/_instructor/events/sns/sns-advanced/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "", "main": "handler.js", + "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/_instructor/events/sns/sns-advanced/serverless.yml b/_instructor/events/sns/sns-advanced/serverless.yml index 10192c3..37cf5e8 100644 --- a/_instructor/events/sns/sns-advanced/serverless.yml +++ b/_instructor/events/sns/sns-advanced/serverless.yml @@ -31,7 +31,7 @@ custom: provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x # WORKSHOP_START # Step 5. In this_file, expose the `TOPIC_NAME` to `environment` variables using the `${self:custom.topicName}` reference. This is for the eventProducer to send message to our newly created topic # # WORKSHOP_END diff --git a/_instructor/events/sns/sns-basic/handler.js b/_instructor/events/sns/sns-basic/handler.js index 24d933f..aa09553 100644 --- a/_instructor/events/sns/sns-basic/handler.js +++ b/_instructor/events/sns/sns-basic/handler.js @@ -1,8 +1,8 @@ -const AWS = require('aws-sdk') -const sns = new AWS.SNS() +import { SNSClient, PublishCommand } from '@aws-sdk/client-sns'; +const sns = new SNSClient({}); const TOPIC_NAME = process.env.TOPIC_NAME -module.exports.eventProducer = (event, context, callback) => { +export const eventProducer = async (event, context) => { const functionArnCols = context.invokedFunctionArn.split(':') const region = functionArnCols[3] const accountId = functionArnCols[4] @@ -12,24 +12,24 @@ module.exports.eventProducer = (event, context, callback) => { TopicArn: `arn:aws:sns:${region}:${accountId}:${TOPIC_NAME}` } - sns.publish(params, (error, data) => { - if (error) { - return callback(error) - } - return callback(null, { + try { + const data = await sns.send(new PublishCommand(params)); + return { statusCode: 200, body: JSON.stringify({ message: `Message successfully published to SNS topic "${TOPIC_NAME}"` }), - }) - }) + } + } catch (error) { + throw error + } } -module.exports.eventConsumer = (event, context, callback) => { +export const eventConsumer = (event, context) => { // print out the event information on the console (so that we can see it in the CloudWatch logs) console.log(`I'm triggered by "eventProducer" through the SNS topic "${TOPIC_NAME}"`) console.log(`event:\n${JSON.stringify(event, null, 2)}`) - return callback(null, { + return { event: event - }) + } } diff --git a/_instructor/events/sns/sns-basic/serverless.yml b/_instructor/events/sns/sns-basic/serverless.yml index 60979a4..e807a0f 100644 --- a/_instructor/events/sns/sns-basic/serverless.yml +++ b/_instructor/events/sns/sns-basic/serverless.yml @@ -10,7 +10,7 @@ custom: provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x # WORKSHOP_START # Step 3. In this_file, Expose the `TOPIC_NAME` to `environment` variables using the `${self:custom.topicName}` reference # # WORKSHOP_END diff --git a/_instructor/events/step-functions/handler.js b/_instructor/events/step-functions/handler.js index a233681..4bb0695 100644 --- a/_instructor/events/step-functions/handler.js +++ b/_instructor/events/step-functions/handler.js @@ -1,7 +1,7 @@ /* run step function */ -const AWS = require('aws-sdk') +import { SFNClient, StartExecutionCommand } from '@aws-sdk/client-sfn'; const STATE_MACHINE_ARN = process.env.STATE_MACHINE_ARN -const stepfunctions = new AWS.StepFunctions() +const stepfunctions = new SFNClient({}); // WORKSHOP_START /* Step 6. In this_file the `startStateMachine` will handle the creation of the new step function. @@ -11,7 +11,7 @@ const stepfunctions = new AWS.StepFunctions() See docs for more details http://amzn.to/2zP0OPW */ // WORKSHOP_END -module.exports.startStateMachine = (event, context, callback) => { +export const startStateMachine = async (event, context) => { const body = JSON.parse(event.body) const taskName = body.taskName const startAt = body.startAt @@ -32,11 +32,8 @@ module.exports.startStateMachine = (event, context, callback) => { }) } // start step function - stepfunctions.startExecution(params, (err, data) => { - if (err) { - console.log(err, err.stack) // an error occurred - return callback(err) - } + try { + const data = await stepfunctions.send(new StartExecutionCommand(params)); console.log(data) // successful response console.log(data.executionArn) // needed for cancels console.log(data.startDate) @@ -47,13 +44,16 @@ module.exports.startStateMachine = (event, context, callback) => { params: params }), }; - return callback(null, response) - }) + return response + } catch (err) { + console.log(err, err.stack) // an error occurred + throw err + } // FINAL_END } -module.exports.sendEmail = (event, context, callback) => { +export const sendEmail = (event, context) => { const time = new Date() console.log(`send email triggered at ${time}`) @@ -65,5 +65,5 @@ module.exports.sendEmail = (event, context, callback) => { }), }; - return callback(null, response); + return response; }; diff --git a/_instructor/events/step-functions/package.json b/_instructor/events/step-functions/package.json index bebaaab..b64bd38 100644 --- a/_instructor/events/step-functions/package.json +++ b/_instructor/events/step-functions/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "", "main": "handler.js", + "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/_instructor/events/step-functions/serverless.yml b/_instructor/events/step-functions/serverless.yml index 1ccd4f0..6c5fcd9 100644 --- a/_instructor/events/step-functions/serverless.yml +++ b/_instructor/events/step-functions/serverless.yml @@ -10,7 +10,7 @@ plugins: provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x stage: ${self:custom.stage} # WORKSHOP_START # Step 4. In this_file, reference the Output value of the state machine. ${self:resources.Outputs.MyStateMachine.Value} in the `environment` variables # diff --git a/lessons-code-complete/core-concepts/1-http-hello-world/README.md b/lessons-code-complete/core-concepts/1-http-hello-world/README.md index f31302e..6581c1f 100644 --- a/lessons-code-complete/core-concepts/1-http-hello-world/README.md +++ b/lessons-code-complete/core-concepts/1-http-hello-world/README.md @@ -4,6 +4,7 @@ This lesson will walk through creating a basic function triggered by a http GET - [Lesson Steps](#lesson-steps) - [Complete code](#complete-code) +- [Additional Resources](#additional-resources) ## Lesson Steps @@ -80,3 +81,7 @@ This lesson will walk through creating a basic function triggered by a http GET If you need help or get stuck refer to the completed code of this lesson [View Complete Code](https://github.com/DavidWells/serverless-workshop/tree/master/lessons-code-complete/core-concepts/1-http-hello-world) + +## Additional Resources + +- [API gateway deepdive video](https://www.twitch.tv/videos/408651893?t=00h03m34s) diff --git a/lessons-code-complete/core-concepts/1-http-hello-world/handler.js b/lessons-code-complete/core-concepts/1-http-hello-world/handler.js index 87b47c4..759776c 100644 --- a/lessons-code-complete/core-concepts/1-http-hello-world/handler.js +++ b/lessons-code-complete/core-concepts/1-http-hello-world/handler.js @@ -1,5 +1,5 @@ -module.exports.hello = (event, context, callback) => { +export const hello = (event, context) => { const response = { /* Status code required for default lambda integration */ statusCode: 200, @@ -8,6 +8,5 @@ module.exports.hello = (event, context, callback) => { event: event, }), } - /** callback(error, response) */ - return callback(null, response) + return response } diff --git a/lessons-code-complete/core-concepts/1-http-hello-world/serverless.yml b/lessons-code-complete/core-concepts/1-http-hello-world/serverless.yml index d6db1c0..c7feb4b 100644 --- a/lessons-code-complete/core-concepts/1-http-hello-world/serverless.yml +++ b/lessons-code-complete/core-concepts/1-http-hello-world/serverless.yml @@ -3,7 +3,7 @@ service: my-first-service provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x functions: hello: diff --git a/lessons-code-complete/core-concepts/2-http-dynamic-content/handler.js b/lessons-code-complete/core-concepts/2-http-dynamic-content/handler.js index f238d85..89ff16d 100644 --- a/lessons-code-complete/core-concepts/2-http-dynamic-content/handler.js +++ b/lessons-code-complete/core-concepts/2-http-dynamic-content/handler.js @@ -6,7 +6,7 @@ Finally remember to set the headers of the response as `'Content-Type': 'text/html'` to return HTML instead of the default `json` */ -module.exports.queryParamsExample = (event, context, callback) => { +export const queryParamsExample = (event, context) => { let name if (event.queryStringParameters && event.queryStringParameters.name) { name = event.queryStringParameters.name @@ -14,13 +14,13 @@ module.exports.queryParamsExample = (event, context, callback) => { /* generate the hello paragraph */ const helloParagraph = greetPerson(name) - return callback(null, { + return { statusCode: 200, headers: { 'Content-Type': 'text/html', }, body: generateHtmlPage(helloParagraph), - }) + } } /* Step 5. In this_file, use the `pathParamsExample` function to return html in the callback. @@ -31,7 +31,7 @@ module.exports.queryParamsExample = (event, context, callback) => { Finally, remember to set the headers of the response as `'Content-Type': 'text/html'` to return HTML instead of the default `json` */ -module.exports.pathParamsExample = (event, context, callback) => { +export const pathParamsExample = (event, context) => { let name if (event.pathParameters && event.pathParameters.name) { name = event.pathParameters.name @@ -40,14 +40,14 @@ module.exports.pathParamsExample = (event, context, callback) => { /* generate the hello paragraph */ const helloParagraph = greetPerson(name) - // callback is sending HTML back - return callback(null, { + // return is sending HTML back + return { statusCode: 200, headers: { 'Content-Type': 'text/html', }, body: generateHtmlPage(helloParagraph), - }) + } } /* Utility function for rendering HTML */ diff --git a/lessons-code-complete/core-concepts/2-http-dynamic-content/serverless.yml b/lessons-code-complete/core-concepts/2-http-dynamic-content/serverless.yml index de5459e..737cf93 100644 --- a/lessons-code-complete/core-concepts/2-http-dynamic-content/serverless.yml +++ b/lessons-code-complete/core-concepts/2-http-dynamic-content/serverless.yml @@ -3,7 +3,7 @@ service: using-dynamic-value provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x functions: queryParamsExample: diff --git a/lessons-code-complete/core-concepts/3-http-post-with-cors/README.md b/lessons-code-complete/core-concepts/3-http-post-with-cors/README.md index e45e867..3111deb 100644 --- a/lessons-code-complete/core-concepts/3-http-post-with-cors/README.md +++ b/lessons-code-complete/core-concepts/3-http-post-with-cors/README.md @@ -12,6 +12,7 @@ This lesson will walk through creating an http function triggered by a `POST` re - [Lesson Steps](#lesson-steps) - [Complete code](#complete-code) +- [Locking down cors to a specific domain](#locking-down-cors-to-a-specific-domain) ## Lesson Steps @@ -44,3 +45,25 @@ This lesson will walk through creating an http function triggered by a `POST` re If you need help or get stuck refer to the completed code of this lesson [View Complete Code](https://github.com/DavidWells/serverless-workshop/tree/master/lessons-code-complete/core-concepts/3-http-post-with-cors) + +## Locking down cors to a specific domain + +```yml +functions: + hello: + handler: handler.hello + events: + - http: + path: hello + method: get + cors: + origin: 'api.myorigin.com' + headers: + - Content-Type + - X-Amz-Date + - Authorization + - X-Api-Key + - X-Amz-Security-Token + - X-Amz-User-Agent + allowCredentials: true +``` diff --git a/lessons-code-complete/core-concepts/3-http-post-with-cors/handler.js b/lessons-code-complete/core-concepts/3-http-post-with-cors/handler.js index 4343b2c..2ec2681 100644 --- a/lessons-code-complete/core-concepts/3-http-post-with-cors/handler.js +++ b/lessons-code-complete/core-concepts/3-http-post-with-cors/handler.js @@ -1,4 +1,4 @@ -module.exports.functionWithCors = (event, context, callback) => { +export const functionWithCors = (event, context) => { const response = { statusCode: 200, headers: { @@ -12,5 +12,5 @@ module.exports.functionWithCors = (event, context, callback) => { event: event, }), } - return callback(null, response) + return response } diff --git a/lessons-code-complete/core-concepts/3-http-post-with-cors/serverless.yml b/lessons-code-complete/core-concepts/3-http-post-with-cors/serverless.yml index e2339b9..c45e88d 100644 --- a/lessons-code-complete/core-concepts/3-http-post-with-cors/serverless.yml +++ b/lessons-code-complete/core-concepts/3-http-post-with-cors/serverless.yml @@ -3,7 +3,7 @@ service: using-http-cors-example provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x functions: hello: diff --git a/lessons-code-complete/core-concepts/4-using-env-vars/handler.js b/lessons-code-complete/core-concepts/4-using-env-vars/handler.js index e108893..985981f 100644 --- a/lessons-code-complete/core-concepts/4-using-env-vars/handler.js +++ b/lessons-code-complete/core-concepts/4-using-env-vars/handler.js @@ -1,4 +1,4 @@ -module.exports.foo = (event, context, callback) => { +export const foo = (event, context) => { console.log('process.env.MY_ENV_VAR', process.env.MY_ENV_VAR) /* MY_ENV_VAR_FOR_BAR will be undefined */ console.log('process.env.MY_ENV_VAR_FOR_BAR', process.env.MY_ENV_VAR_FOR_BAR) @@ -14,10 +14,10 @@ module.exports.foo = (event, context, callback) => { message: `hello ${process.env.MY_ENV_VAR}` }), } - return callback(null, response) + return response } -module.exports.bar = (event, context, callback) => { +export const bar = (event, context) => { /* both env variables will be accessible in bar */ console.log('process.env.MY_ENV_VAR', process.env.MY_ENV_VAR) console.log('process.env.MY_ENV_VAR_FOR_BAR', process.env.MY_ENV_VAR_FOR_BAR) @@ -34,5 +34,5 @@ module.exports.bar = (event, context, callback) => { message: `hello ${process.env.MY_ENV_VAR_FOR_BAR}` }), } - return callback(null, response) + return response } diff --git a/lessons-code-complete/core-concepts/4-using-env-vars/serverless.yml b/lessons-code-complete/core-concepts/4-using-env-vars/serverless.yml index 3fb5473..fef62c3 100644 --- a/lessons-code-complete/core-concepts/4-using-env-vars/serverless.yml +++ b/lessons-code-complete/core-concepts/4-using-env-vars/serverless.yml @@ -3,7 +3,7 @@ service: using-env-variables-example provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x environment: MY_ENV_VAR: 'hello there' diff --git a/lessons-code-complete/core-concepts/5-using-serverless-variable-syntax/handler.js b/lessons-code-complete/core-concepts/5-using-serverless-variable-syntax/handler.js index baaf452..dc52888 100644 --- a/lessons-code-complete/core-concepts/5-using-serverless-variable-syntax/handler.js +++ b/lessons-code-complete/core-concepts/5-using-serverless-variable-syntax/handler.js @@ -1,5 +1,5 @@ -module.exports.foo = (event, context, callback) => { +export const foo = (event, context) => { console.log('process.env.MY_SECRET', process.env.MY_SECRET) const response = { statusCode: 200, @@ -13,5 +13,5 @@ module.exports.foo = (event, context, callback) => { message: `my super secret thing ${process.env.MY_SECRET}` }), } - return callback(null, response) + return response } diff --git a/lessons-code-complete/core-concepts/5-using-serverless-variable-syntax/serverless.yml b/lessons-code-complete/core-concepts/5-using-serverless-variable-syntax/serverless.yml index cf86e1a..fad7c40 100644 --- a/lessons-code-complete/core-concepts/5-using-serverless-variable-syntax/serverless.yml +++ b/lessons-code-complete/core-concepts/5-using-serverless-variable-syntax/serverless.yml @@ -3,7 +3,7 @@ service: serverless-variables-syntax-example provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x environment: MY_SECRET: ${file(./secrets.json):VAR_FROM_FILE} diff --git a/lessons-code-complete/core-concepts/6-using-addition-resources/handler.js b/lessons-code-complete/core-concepts/6-using-addition-resources/handler.js index fc44edb..fd1b468 100644 --- a/lessons-code-complete/core-concepts/6-using-addition-resources/handler.js +++ b/lessons-code-complete/core-concepts/6-using-addition-resources/handler.js @@ -1,23 +1,25 @@ /* Include the AWS sdk. * No need to add to package.json. It's included in lambda env */ -const AWS = require('aws-sdk') +import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; +import { DynamoDBDocumentClient, PutCommand, ScanCommand } from '@aws-sdk/lib-dynamodb'; // Connect to DynamoDB -const dynamoDb = new AWS.DynamoDB.DocumentClient() +const client = new DynamoDBClient({}); +const dynamoDb = DynamoDBDocumentClient.from(client); // Save item in DynamoDB table -module.exports.create = (event, context, callback) => { +export const create = async (event, context) => { const timestamp = new Date().getTime() const body = JSON.parse(event.body) if (!body || !body.email) { - return callback(null, { + return { statusCode: 401, headers: { 'Content-Type': 'text/plain' }, body: JSON.stringify({ error: 'no body found or email found' }) - }) + } } const params = { @@ -30,48 +32,46 @@ module.exports.create = (event, context, callback) => { } // write the todo to the database - dynamoDb.put(params, (error) => { - // handle potential errors - if (error) { - console.error(error) - return callback(null, { - statusCode: error.statusCode || 501, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t create the dynamo item.', - }) - } - + try { + await dynamoDb.send(new PutCommand(params)); // create a response const response = { statusCode: 200, body: JSON.stringify(params.Item), } - return callback(null, response) - }) + return response + } catch (error) { + // handle potential errors + console.error(error) + return { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t create the dynamo item.', + } + } } /* Scan a dynamoDB table and return items */ -module.exports.scan = (event, context, callback) => { +export const scan = async (event, context) => { const params = { TableName: process.env.MY_TABLE, } // fetch all todos from the database - dynamoDb.scan(params, (error, result) => { - // handle potential errors - if (error) { - console.error(error) - return callback(null, { - statusCode: error.statusCode || 501, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t fetch the todos.', - }) - } - + try { + const result = await dynamoDb.send(new ScanCommand(params)); // create a response const response = { statusCode: 200, body: JSON.stringify(result.Items), } - return callback(null, response) - }) + return response + } catch (error) { + // handle potential errors + console.error(error) + return { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t fetch the todos.', + } + } } diff --git a/lessons-code-complete/core-concepts/6-using-addition-resources/serverless.yml b/lessons-code-complete/core-concepts/6-using-addition-resources/serverless.yml index 8aae635..565d9f8 100644 --- a/lessons-code-complete/core-concepts/6-using-addition-resources/serverless.yml +++ b/lessons-code-complete/core-concepts/6-using-addition-resources/serverless.yml @@ -3,7 +3,7 @@ service: event-driven-app provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x environment: # Here we reference the table name defined below. This is a cloud formation ref MY_TABLE: { Ref: myDynamoTable } diff --git a/lessons-code-complete/core-concepts/7-using-serverless-plugins/handler.js b/lessons-code-complete/core-concepts/7-using-serverless-plugins/handler.js index c355f23..2e6e0db 100644 --- a/lessons-code-complete/core-concepts/7-using-serverless-plugins/handler.js +++ b/lessons-code-complete/core-concepts/7-using-serverless-plugins/handler.js @@ -1,5 +1,5 @@ -module.exports.hello = (event, context, callback) => { +export const hello = (event, context) => { const response = { /* Status code required for default lambda integration */ statusCode: 200, @@ -8,5 +8,5 @@ module.exports.hello = (event, context, callback) => { }), } /** callback(error, response) */ - return callback(null, response) + return response } diff --git a/lessons-code-complete/core-concepts/7-using-serverless-plugins/package.json b/lessons-code-complete/core-concepts/7-using-serverless-plugins/package.json index 06a58b0..6ef1a12 100644 --- a/lessons-code-complete/core-concepts/7-using-serverless-plugins/package.json +++ b/lessons-code-complete/core-concepts/7-using-serverless-plugins/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "using-serverless-plugins", "main": "index.js", + "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/lessons-code-complete/core-concepts/7-using-serverless-plugins/serverless.yml b/lessons-code-complete/core-concepts/7-using-serverless-plugins/serverless.yml index d203cc4..5b85c4b 100644 --- a/lessons-code-complete/core-concepts/7-using-serverless-plugins/serverless.yml +++ b/lessons-code-complete/core-concepts/7-using-serverless-plugins/serverless.yml @@ -10,7 +10,7 @@ custom: provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x functions: hello: diff --git a/lessons-code-complete/core-concepts/8-using-multiple-stages/handler.js b/lessons-code-complete/core-concepts/8-using-multiple-stages/handler.js index c355f23..2e6e0db 100644 --- a/lessons-code-complete/core-concepts/8-using-multiple-stages/handler.js +++ b/lessons-code-complete/core-concepts/8-using-multiple-stages/handler.js @@ -1,5 +1,5 @@ -module.exports.hello = (event, context, callback) => { +export const hello = (event, context) => { const response = { /* Status code required for default lambda integration */ statusCode: 200, @@ -8,5 +8,5 @@ module.exports.hello = (event, context, callback) => { }), } /** callback(error, response) */ - return callback(null, response) + return response } diff --git a/lessons-code-complete/core-concepts/8-using-multiple-stages/package.json b/lessons-code-complete/core-concepts/8-using-multiple-stages/package.json index 6034242..5e0e359 100644 --- a/lessons-code-complete/core-concepts/8-using-multiple-stages/package.json +++ b/lessons-code-complete/core-concepts/8-using-multiple-stages/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "", "main": "index.js", + "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/lessons-code-complete/core-concepts/8-using-multiple-stages/serverless.yml b/lessons-code-complete/core-concepts/8-using-multiple-stages/serverless.yml index c309f13..1c5c2d9 100644 --- a/lessons-code-complete/core-concepts/8-using-multiple-stages/serverless.yml +++ b/lessons-code-complete/core-concepts/8-using-multiple-stages/serverless.yml @@ -6,7 +6,7 @@ custom: provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x stage: ${self:custom.stage} environment: MY_STAGE_SPECIFIC_VAR: ${file(./config.${self:custom.stage}.json):projectName} diff --git a/lessons-code-complete/events/dynamodb-streams/README.md b/lessons-code-complete/events/dynamodb-streams/README.md index 6b7e6bc..118306a 100644 --- a/lessons-code-complete/events/dynamodb-streams/README.md +++ b/lessons-code-complete/events/dynamodb-streams/README.md @@ -5,6 +5,7 @@ This lesson we will be adding a delete function to remove users from the databas - [Lesson Steps](#lesson-steps) - [DynamoDB Stream CloudFormation](#dynamodb-stream-cloudformation) - [Complete code](#complete-code) +- [Additional Resources](#additional-resources) ## Lesson Steps @@ -160,3 +161,7 @@ For additional CloudFormation information check out. https://acloud.guru/learn/a If you need help or get stuck refer to the completed code of this lesson [View Complete Code](https://github.com/DavidWells/serverless-workshop/tree/master/lessons-code-complete/events/dynamodb-streams) + +## Additional Resources + +- [Modeling Relational Data in DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-modeling-nosql-B.html) diff --git a/lessons-code-complete/events/dynamodb-streams/handler.js b/lessons-code-complete/events/dynamodb-streams/handler.js index e9e63ae..f37c14f 100644 --- a/lessons-code-complete/events/dynamodb-streams/handler.js +++ b/lessons-code-complete/events/dynamodb-streams/handler.js @@ -1,23 +1,25 @@ /* Include the AWS sdk. * No need to add to package.json. It's included in lambda env */ -const AWS = require('aws-sdk') +import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; +import { DynamoDBDocumentClient, PutCommand, ScanCommand, DeleteCommand } from '@aws-sdk/lib-dynamodb'; // Connect to DynamoDB -const dynamoDb = new AWS.DynamoDB.DocumentClient() +const client = new DynamoDBClient({}); +const dynamoDb = DynamoDBDocumentClient.from(client); // Save item in DynamoDB table -module.exports.create = (event, context, callback) => { +export const create = async (event, context) => { const timestamp = new Date().getTime() const body = JSON.parse(event.body) if (!body || !body.email) { - return callback(null, { + return { statusCode: 401, headers: { 'Content-Type': 'text/plain' }, body: JSON.stringify({ error: 'no body found or email found' }) - }) + } } const params = { @@ -30,64 +32,62 @@ module.exports.create = (event, context, callback) => { } // write the todo to the database - dynamoDb.put(params, (error) => { - // handle potential errors - if (error) { - console.error(error) - return callback(null, { - statusCode: error.statusCode || 501, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t create the dynamo item.', - }) - } - + try { + await dynamoDb.send(new PutCommand(params)); // create a response const response = { statusCode: 200, body: JSON.stringify(params.Item), } - return callback(null, response) - }) + return response + } catch (error) { + // handle potential errors + console.error(error) + return { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t create the dynamo item.', + } + } } /* Scan a dynamoDB table and return items */ -module.exports.scan = (event, context, callback) => { +export const scan = async (event, context) => { const params = { TableName: process.env.MY_TABLE, } // fetch all todos from the database - dynamoDb.scan(params, (error, result) => { - // handle potential errors - if (error) { - console.error(error) - return callback(null, { - statusCode: error.statusCode || 501, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t fetch the todos.', - }) - } - + try { + const result = await dynamoDb.send(new ScanCommand(params)); // create a response const response = { statusCode: 200, body: JSON.stringify(result.Items), } - return callback(null, response) - }) + return response + } catch (error) { + // handle potential errors + console.error(error) + return { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t fetch the todos.', + } + } } -module.exports.delete = (event, context, callback) => { +export const delete = async (event, context) => { const body = JSON.parse(event.body) if (!body || !body.id) { - return callback(null, { + return { statusCode: 401, headers: { 'Content-Type': 'text/plain' }, body: JSON.stringify({ error: 'no body found or id found' }) - }) + } } const params = { @@ -98,17 +98,8 @@ module.exports.delete = (event, context, callback) => { } // delete the todo from the database - dynamoDb.delete(params, (error) => { - // handle potential errors - if (error) { - console.error(error) - return callback(null, { - statusCode: error.statusCode || 501, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t remove the todo item.', - }) - } - + try { + await dynamoDb.send(new DeleteCommand(params)); // create a response const response = { statusCode: 200, @@ -116,12 +107,20 @@ module.exports.delete = (event, context, callback) => { message: `user ${body.id} deleted` }), } - return callback(null, response) - }) + return response + } catch (error) { + // handle potential errors + console.error(error) + return { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t remove the todo item.', + } + } } /* Function to handle items on the dynamoDB stream */ -module.exports.dynamoStreamHandler = (event, context, callback) => { +export const dynamoStreamHandler = (event, context) => { event.Records.forEach((record) => { console.log(record.eventID) console.log(record.eventName) @@ -133,5 +132,5 @@ module.exports.dynamoStreamHandler = (event, context, callback) => { console.log('REMOVAL EVENT. DO REMOVAL STUFF') } }) - return callback(null, `Successfully processed ${event.Records.length} records.`); + return `Successfully processed ${event.Records.length} records.`; } diff --git a/lessons-code-complete/events/dynamodb-streams/package.json b/lessons-code-complete/events/dynamodb-streams/package.json new file mode 100644 index 0000000..41bb0ba --- /dev/null +++ b/lessons-code-complete/events/dynamodb-streams/package.json @@ -0,0 +1,10 @@ +{ + "name": "dynamodb-streams-handler", + "version": "1.0.0", + "description": "DynamoDB streams handler using AWS SDK v3", + "type": "module", + "dependencies": { + "@aws-sdk/client-dynamodb": "^3.668.0", + "@aws-sdk/lib-dynamodb": "^3.668.0" + } +} \ No newline at end of file diff --git a/lessons-code-complete/events/dynamodb-streams/serverless.yml b/lessons-code-complete/events/dynamodb-streams/serverless.yml index d9f9871..7be7b4f 100644 --- a/lessons-code-complete/events/dynamodb-streams/serverless.yml +++ b/lessons-code-complete/events/dynamodb-streams/serverless.yml @@ -3,7 +3,7 @@ service: event-driven-app provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x environment: MY_TABLE: { Ref: myDynamoTable } iamRoleStatements: diff --git a/lessons-code-complete/events/kinesis/kinesis-basic/handler.js b/lessons-code-complete/events/kinesis/kinesis-basic/handler.js index 6908eaf..dfd8c32 100644 --- a/lessons-code-complete/events/kinesis/kinesis-basic/handler.js +++ b/lessons-code-complete/events/kinesis/kinesis-basic/handler.js @@ -1,4 +1,4 @@ -module.exports.processEvents = (event, context, callback) => { +export const processEvents = (event, context) => { // Process kinesis event if (event.Records) { event.Records.forEach((record) => { @@ -18,5 +18,5 @@ module.exports.processEvents = (event, context, callback) => { console.log('NEGATIVE decoded record:', payload) }) } - return callback(null, `Successfully processed ${event.Records.length} records.`); + return `Successfully processed ${event.Records.length} records.`; } diff --git a/lessons-code-complete/events/kinesis/kinesis-basic/package.json b/lessons-code-complete/events/kinesis/kinesis-basic/package.json index 1c8d050..72d7020 100644 --- a/lessons-code-complete/events/kinesis/kinesis-basic/package.json +++ b/lessons-code-complete/events/kinesis/kinesis-basic/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "", "main": "handler.js", + "type": "module", "scripts": { "simulate": "node ./scripts/putRecords.js --name=$npm_config_name --count=$npm_config_count --region=$npm_config_region", "test": "echo \"Error: no test specified\" && exit 1" diff --git a/lessons-code-complete/events/kinesis/kinesis-basic/scripts/putRecords.js b/lessons-code-complete/events/kinesis/kinesis-basic/scripts/putRecords.js index fefc163..45148da 100644 --- a/lessons-code-complete/events/kinesis/kinesis-basic/scripts/putRecords.js +++ b/lessons-code-complete/events/kinesis/kinesis-basic/scripts/putRecords.js @@ -1,5 +1,7 @@ -const AWS = require('aws-sdk') -const argv = require('yargs').argv +import { KinesisClient, PutRecordsCommand } from '@aws-sdk/client-kinesis'; +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; +const argv = yargs(hideBin(process.argv)).argv; const streamName = argv.name const numberOfEvents = argv.count || 20 @@ -10,8 +12,8 @@ if (!streamName) { return false } -function putRecords() { - const kinesis = new AWS.Kinesis({region: AWS_REGION}) +async function putRecords() { + const kinesis = new KinesisClient({region: AWS_REGION}) const params = { Records: Array.from(Array(parseInt(numberOfEvents))).map((_, i) => { const n = (Math.random() * 100).toFixed(2) * (Math.random() > 0.5 ? 1 : -1); @@ -28,7 +30,7 @@ function putRecords() { }), StreamName: streamName, }; - return kinesis.putRecords(params).promise(); + return await kinesis.send(new PutRecordsCommand(params)); } putRecords().then((res) => { diff --git a/lessons-code-complete/events/kinesis/kinesis-basic/serverless.yml b/lessons-code-complete/events/kinesis/kinesis-basic/serverless.yml index f7bca6b..c890902 100644 --- a/lessons-code-complete/events/kinesis/kinesis-basic/serverless.yml +++ b/lessons-code-complete/events/kinesis/kinesis-basic/serverless.yml @@ -7,7 +7,7 @@ custom: provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x stage: ${self:custom.stage} # `processEvents` is trigged by the kinesis stream diff --git a/lessons-code-complete/events/s3/README.md b/lessons-code-complete/events/s3/README.md index a73c82b..29c5128 100644 --- a/lessons-code-complete/events/s3/README.md +++ b/lessons-code-complete/events/s3/README.md @@ -6,6 +6,7 @@ This lesson will walk through triggering a lambda function in response to an s3 - [S3 bucket CloudFormation template](#s3-bucket-cloudformation-template) - [Triggering events from existing buckets](#triggering-events-from-existing-buckets) - [Complete code](#complete-code) +- [Alternative methods](#alternative-methods) ## Lesson Steps @@ -111,3 +112,23 @@ You will need to install the [serverless-external-s3-event plugin](https://githu If you need help or get stuck refer to the completed code of this lesson [View Complete Code](https://github.com/DavidWells/serverless-workshop/tree/master/lessons-code-complete/events/s3) + +## Alternative methods + +You can wire up s3 notifications via cloudformation as well. See [docs](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-notificationconfig.html) + +Example cloudformation: + +```yml +resources: + Resources: + Bucket: + Type: AWS::S3::Bucket + ... + Properties: + NotificationConfiguration: + TopicConfigurations: + - Event: s3:ObjectCreated:Put + Topic: + Ref: BucketTopic +``` diff --git a/lessons-code-complete/events/s3/package.json b/lessons-code-complete/events/s3/package.json index d81d183..a29f7b9 100644 --- a/lessons-code-complete/events/s3/package.json +++ b/lessons-code-complete/events/s3/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "aws s3 image resizer", "main": "handler.js", + "type": "module", "scripts": { "deploy": "sls deploy" }, diff --git a/lessons-code-complete/events/s3/resize.js b/lessons-code-complete/events/s3/resize.js index e7439e9..ca362dc 100644 --- a/lessons-code-complete/events/s3/resize.js +++ b/lessons-code-complete/events/s3/resize.js @@ -1,16 +1,17 @@ // dependencies -const util = require('util') -const path = require('path') -const async = require('async') -const AWS = require('aws-sdk') -const gm = require('gm').subClass({ +import util from 'util'; +import path from 'path'; +import async from 'async'; +import { S3Client, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3'; +import gm from 'gm'; +const gmIM = gm.subClass({ imageMagick: true }) // get reference to S3 client -const s3 = new AWS.S3() +const s3 = new S3Client({}); -module.exports.resizeImage = (event, context, callback) => { +export const resizeImage = async (event, context) => { // Read options from the event. console.log("Reading options from event:\n", util.inspect(event, { depth: 5 @@ -23,12 +24,12 @@ module.exports.resizeImage = (event, context, callback) => { if (srcBucket === dstBucket) { console.log('Destination bucket must not match source bucket.') console.log('This would result in an infinite loop') - return callback(null, { + return { statusCode: 501, body: JSON.stringify({ error: 'Destination Matches source bucket' }), - }); + }; } // Image sizes and output folder paths @@ -79,10 +80,10 @@ module.exports.resizeImage = (event, context, callback) => { // Download the image from S3 into a buffer. // sadly it downloads the image several times, but we couldn't place it outside // the variable was not recognized - s3.getObject({ + s3.send(new GetObjectCommand({ Bucket: srcBucket, Key: srcKey - }, next) + })).then(result => next(null, result)).catch(next) console.timeEnd("downloadImage") }, function convert(response, next) { @@ -90,7 +91,7 @@ module.exports.resizeImage = (event, context, callback) => { console.time("convertImage") console.log(`Reponse content type: ${response.ContentType}`) console.log("Conversion") - gm(response.Body) + gmIM(response.Body) .antialias(true) .density(300) .toBuffer('JPG', (err, buffer) => { @@ -106,7 +107,7 @@ module.exports.resizeImage = (event, context, callback) => { console.time("processImage") // Transform the image buffer in memory. // gm(response.Body).size(function(err, size) { - gm(response).size(function(err, size) { + gmIM(response).size(function(err, size) { // console.log("buf content type " + buf.ContentType) if (err) { console.log(err) @@ -140,12 +141,12 @@ module.exports.resizeImage = (event, context, callback) => { console.log("upload: " + index) console.log(`uploadPath: /${keyPath}`) // Stream the transformed image to a different folder. - s3.putObject({ + s3.send(new PutObjectCommand({ Bucket: dstBucket, Key: keyPath, Body: data, ContentType: 'JPG' - }, next) + })).then(result => next(null, result)).catch(next) console.timeEnd("uploadImage") } ], function(err, result) { @@ -170,6 +171,6 @@ module.exports.resizeImage = (event, context, callback) => { imageProcessed: true }), } - return callback(null, response) + return response }) } diff --git a/lessons-code-complete/events/s3/save.js b/lessons-code-complete/events/s3/save.js index e490955..e28fa15 100644 --- a/lessons-code-complete/events/s3/save.js +++ b/lessons-code-complete/events/s3/save.js @@ -1,31 +1,30 @@ -const fetch = require('node-fetch') -const AWS = require('aws-sdk') -const s3 = new AWS.S3() +import fetch from 'node-fetch'; +import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'; +const s3 = new S3Client({}); const BUCKET_NAME = process.env.BUCKET_NAME -module.exports.saveImage = (event, context, callback) => { +export const saveImage = async (event, context) => { const key = event.key || JSON.parse(event.body).key const imageURL = event.image_url || JSON.parse(event.body).image_url - fetch(imageURL) - .then(response => response.buffer()) - .then(buffer => ( - s3.putObject({ - Bucket: BUCKET_NAME, - Key: key, - Body: buffer, - }).promise() - )) - .then(() => { - return callback(null, { - statusCode: 200, - body: JSON.stringify({ - success: true, - message: 'image saved to bucket' - }), - }) - }) - .catch((error) => { - return callback(error, null) - }) + try { + const response = await fetch(imageURL); + const buffer = await response.buffer(); + + await s3.send(new PutObjectCommand({ + Bucket: BUCKET_NAME, + Key: key, + Body: buffer, + })); + + return { + statusCode: 200, + body: JSON.stringify({ + success: true, + message: 'image saved to bucket' + }), + } + } catch (error) { + throw error + } } diff --git a/lessons-code-complete/events/s3/serverless.yml b/lessons-code-complete/events/s3/serverless.yml index 8931a2a..efa22fb 100644 --- a/lessons-code-complete/events/s3/serverless.yml +++ b/lessons-code-complete/events/s3/serverless.yml @@ -7,7 +7,7 @@ custom: provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x environment: BUCKET_NAME: ${self:custom.bucketName} iamRoleStatements: diff --git a/lessons-code-complete/events/schedule/handler.js b/lessons-code-complete/events/schedule/handler.js index 4fc94b5..7355410 100644 --- a/lessons-code-complete/events/schedule/handler.js +++ b/lessons-code-complete/events/schedule/handler.js @@ -1,5 +1,5 @@ /* Cron function */ -module.exports.cronFunction = (event, context, callback) => { +export const cronFunction = (event, context, callback) => { const time = new Date() console.log(`Function "${context.functionName}" ran at ${time}`) } diff --git a/lessons-code-complete/events/schedule/serverless.yml b/lessons-code-complete/events/schedule/serverless.yml index 3066096..28fc020 100644 --- a/lessons-code-complete/events/schedule/serverless.yml +++ b/lessons-code-complete/events/schedule/serverless.yml @@ -3,7 +3,7 @@ service: aws-scheduled-cron provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x functions: cron: diff --git a/lessons-code-complete/events/sns/README.md b/lessons-code-complete/events/sns/README.md index b8c4e44..f5c145e 100644 --- a/lessons-code-complete/events/sns/README.md +++ b/lessons-code-complete/events/sns/README.md @@ -16,6 +16,10 @@ Amazon Simple Notification Service (Amazon SNS) is a web service that coordinate - [Basic Example]('./basic') - [Advanced Example]('./advanced') +## Other Examples + +- [Lambda (publisher) => SNS => Lambda (consumer)](https://github.com/didil/serverless-lambda-sns-example) + ## Troubleshooting - `event.sns.arn.indexOf is not a function` means there is a `yaml` indentation error diff --git a/lessons-code-complete/events/sns/handler.js b/lessons-code-complete/events/sns/handler.js index 17be91e..dda8f90 100644 --- a/lessons-code-complete/events/sns/handler.js +++ b/lessons-code-complete/events/sns/handler.js @@ -1,8 +1,8 @@ -const AWS = require('aws-sdk') -const sns = new AWS.SNS() +import { SNSClient, PublishCommand } from '@aws-sdk/client-sns'; +const sns = new SNSClient({}); const TOPIC_NAME = process.env.TOPIC_NAME -module.exports.eventProducer = (event, context, callback) => { +export const eventProducer = async (event, context) => { const functionArnCols = context.invokedFunctionArn.split(':') const region = functionArnCols[3] const accountId = functionArnCols[4] @@ -15,22 +15,22 @@ module.exports.eventProducer = (event, context, callback) => { TopicArn: `arn:aws:sns:${region}:${accountId}:${TOPIC_NAME}` } - sns.publish(params, (error, data) => { - if (error) { - return callback(error) - } - return callback(null, { + try { + const data = await sns.send(new PublishCommand(params)); + return { statusCode: 200, body: JSON.stringify({ message: `Message successfully published to SNS topic "${TOPIC_NAME}"` }), - }) - }); + } + } catch (error) { + throw error + } }; -module.exports.eventConsumer = (event, context, callback) => { +export const eventConsumer = (event, context) => { // print out the event information on the console (so that we can see it in the CloudWatch logs) console.log(`I'm triggered by "eventProducer" through the SNS topic "${TOPIC_NAME}"`); console.log(`event:\n${JSON.stringify(event, null, 2)}`); - callback(null, { event }); + return { event }; }; diff --git a/lessons-code-complete/events/sns/package.json b/lessons-code-complete/events/sns/package.json new file mode 100644 index 0000000..f2bc0d7 --- /dev/null +++ b/lessons-code-complete/events/sns/package.json @@ -0,0 +1,9 @@ +{ + "name": "sns-handler", + "version": "1.0.0", + "description": "SNS handler using AWS SDK v3", + "type": "module", + "dependencies": { + "@aws-sdk/client-sns": "^3.668.0" + } +} \ No newline at end of file diff --git a/lessons-code-complete/events/sns/serverless.yml b/lessons-code-complete/events/sns/serverless.yml index 3072317..9e591c7 100644 --- a/lessons-code-complete/events/sns/serverless.yml +++ b/lessons-code-complete/events/sns/serverless.yml @@ -6,7 +6,7 @@ custom: provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x environment: TOPIC_NAME: ${self:custom.topicName} iamRoleStatements: diff --git a/lessons-code-complete/events/sns/sns-advanced/handler.js b/lessons-code-complete/events/sns/sns-advanced/handler.js index 24d933f..aa09553 100644 --- a/lessons-code-complete/events/sns/sns-advanced/handler.js +++ b/lessons-code-complete/events/sns/sns-advanced/handler.js @@ -1,8 +1,8 @@ -const AWS = require('aws-sdk') -const sns = new AWS.SNS() +import { SNSClient, PublishCommand } from '@aws-sdk/client-sns'; +const sns = new SNSClient({}); const TOPIC_NAME = process.env.TOPIC_NAME -module.exports.eventProducer = (event, context, callback) => { +export const eventProducer = async (event, context) => { const functionArnCols = context.invokedFunctionArn.split(':') const region = functionArnCols[3] const accountId = functionArnCols[4] @@ -12,24 +12,24 @@ module.exports.eventProducer = (event, context, callback) => { TopicArn: `arn:aws:sns:${region}:${accountId}:${TOPIC_NAME}` } - sns.publish(params, (error, data) => { - if (error) { - return callback(error) - } - return callback(null, { + try { + const data = await sns.send(new PublishCommand(params)); + return { statusCode: 200, body: JSON.stringify({ message: `Message successfully published to SNS topic "${TOPIC_NAME}"` }), - }) - }) + } + } catch (error) { + throw error + } } -module.exports.eventConsumer = (event, context, callback) => { +export const eventConsumer = (event, context) => { // print out the event information on the console (so that we can see it in the CloudWatch logs) console.log(`I'm triggered by "eventProducer" through the SNS topic "${TOPIC_NAME}"`) console.log(`event:\n${JSON.stringify(event, null, 2)}`) - return callback(null, { + return { event: event - }) + } } diff --git a/lessons-code-complete/events/sns/sns-advanced/package.json b/lessons-code-complete/events/sns/sns-advanced/package.json index 0f744a6..1b88407 100644 --- a/lessons-code-complete/events/sns/sns-advanced/package.json +++ b/lessons-code-complete/events/sns/sns-advanced/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "", "main": "handler.js", + "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/lessons-code-complete/events/sns/sns-advanced/serverless.yml b/lessons-code-complete/events/sns/sns-advanced/serverless.yml index dd4b849..bf5edf4 100644 --- a/lessons-code-complete/events/sns/sns-advanced/serverless.yml +++ b/lessons-code-complete/events/sns/sns-advanced/serverless.yml @@ -21,7 +21,7 @@ custom: provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x environment: TOPIC_NAME: ${self:custom.topicName} iamRoleStatements: diff --git a/lessons-code-complete/events/sns/sns-basic/handler.js b/lessons-code-complete/events/sns/sns-basic/handler.js index 24d933f..aa09553 100644 --- a/lessons-code-complete/events/sns/sns-basic/handler.js +++ b/lessons-code-complete/events/sns/sns-basic/handler.js @@ -1,8 +1,8 @@ -const AWS = require('aws-sdk') -const sns = new AWS.SNS() +import { SNSClient, PublishCommand } from '@aws-sdk/client-sns'; +const sns = new SNSClient({}); const TOPIC_NAME = process.env.TOPIC_NAME -module.exports.eventProducer = (event, context, callback) => { +export const eventProducer = async (event, context) => { const functionArnCols = context.invokedFunctionArn.split(':') const region = functionArnCols[3] const accountId = functionArnCols[4] @@ -12,24 +12,24 @@ module.exports.eventProducer = (event, context, callback) => { TopicArn: `arn:aws:sns:${region}:${accountId}:${TOPIC_NAME}` } - sns.publish(params, (error, data) => { - if (error) { - return callback(error) - } - return callback(null, { + try { + const data = await sns.send(new PublishCommand(params)); + return { statusCode: 200, body: JSON.stringify({ message: `Message successfully published to SNS topic "${TOPIC_NAME}"` }), - }) - }) + } + } catch (error) { + throw error + } } -module.exports.eventConsumer = (event, context, callback) => { +export const eventConsumer = (event, context) => { // print out the event information on the console (so that we can see it in the CloudWatch logs) console.log(`I'm triggered by "eventProducer" through the SNS topic "${TOPIC_NAME}"`) console.log(`event:\n${JSON.stringify(event, null, 2)}`) - return callback(null, { + return { event: event - }) + } } diff --git a/lessons-code-complete/events/sns/sns-basic/serverless.yml b/lessons-code-complete/events/sns/sns-basic/serverless.yml index fabde95..080fec3 100644 --- a/lessons-code-complete/events/sns/sns-basic/serverless.yml +++ b/lessons-code-complete/events/sns/sns-basic/serverless.yml @@ -5,7 +5,7 @@ custom: provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x environment: TOPIC_NAME: ${self:custom.topicName} iamRoleStatements: diff --git a/lessons-code-complete/events/step-functions/README.md b/lessons-code-complete/events/step-functions/README.md index ee694a7..c7d6a99 100644 --- a/lessons-code-complete/events/step-functions/README.md +++ b/lessons-code-complete/events/step-functions/README.md @@ -112,6 +112,7 @@ This example will walk us through using [AWS step functions](https://aws.amazon. - [Trigger Step functions with AWS Lambda (Video)](https://www.youtube.com/watch?v=u-ALakoQ8kM) - [Passing data between steps in Step Functions (Video)](https://www.youtube.com/watch?v=5RXSTTOiPzk) - [AWS re:Invent 2016: Serverless Apps with AWS Step Functions (Video)](https://www.youtube.com/watch?v=75MRve4nv8s) +- [Wait state in AWS Step Functions](http://vgaltes.com/post/step-functions-wait-state/) ## Complete code diff --git a/lessons-code-complete/events/step-functions/handler.js b/lessons-code-complete/events/step-functions/handler.js index 9ef4573..cdda9be 100644 --- a/lessons-code-complete/events/step-functions/handler.js +++ b/lessons-code-complete/events/step-functions/handler.js @@ -1,9 +1,9 @@ /* run step function */ -const AWS = require('aws-sdk') +import { SFNClient, StartExecutionCommand } from '@aws-sdk/client-sfn'; const STATE_MACHINE_ARN = process.env.STATE_MACHINE_ARN -const stepfunctions = new AWS.StepFunctions() +const stepfunctions = new SFNClient({}); -module.exports.startStateMachine = (event, context, callback) => { +export const startStateMachine = async (event, context) => { const body = JSON.parse(event.body) const taskName = body.taskName const startAt = body.startAt @@ -20,11 +20,8 @@ module.exports.startStateMachine = (event, context, callback) => { }) } // start step function - stepfunctions.startExecution(params, (err, data) => { - if (err) { - console.log(err, err.stack) // an error occurred - return callback(err) - } + try { + const data = await stepfunctions.send(new StartExecutionCommand(params)); console.log(data) // successful response console.log(data.executionArn) // needed for cancels console.log(data.startDate) @@ -35,12 +32,15 @@ module.exports.startStateMachine = (event, context, callback) => { params: params }), }; - return callback(null, response) - }) + return response + } catch (err) { + console.log(err, err.stack) // an error occurred + throw err + } } -module.exports.sendEmail = (event, context, callback) => { +export const sendEmail = (event, context) => { const time = new Date() console.log(`send email triggered at ${time}`) @@ -52,5 +52,5 @@ module.exports.sendEmail = (event, context, callback) => { }), }; - return callback(null, response); + return response; }; diff --git a/lessons-code-complete/events/step-functions/package.json b/lessons-code-complete/events/step-functions/package.json index bebaaab..b64bd38 100644 --- a/lessons-code-complete/events/step-functions/package.json +++ b/lessons-code-complete/events/step-functions/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "", "main": "handler.js", + "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/lessons-code-complete/events/step-functions/serverless.yml b/lessons-code-complete/events/step-functions/serverless.yml index 73d59d6..5987fa6 100644 --- a/lessons-code-complete/events/step-functions/serverless.yml +++ b/lessons-code-complete/events/step-functions/serverless.yml @@ -10,7 +10,7 @@ plugins: provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x stage: ${self:custom.stage} environment: STATE_MACHINE_ARN: ${self:resources.Outputs.MyStateMachine.Value} diff --git a/lessons/core-concepts/1-http-hello-world/README.md b/lessons/core-concepts/1-http-hello-world/README.md index f31302e..6581c1f 100644 --- a/lessons/core-concepts/1-http-hello-world/README.md +++ b/lessons/core-concepts/1-http-hello-world/README.md @@ -4,6 +4,7 @@ This lesson will walk through creating a basic function triggered by a http GET - [Lesson Steps](#lesson-steps) - [Complete code](#complete-code) +- [Additional Resources](#additional-resources) ## Lesson Steps @@ -80,3 +81,7 @@ This lesson will walk through creating a basic function triggered by a http GET If you need help or get stuck refer to the completed code of this lesson [View Complete Code](https://github.com/DavidWells/serverless-workshop/tree/master/lessons-code-complete/core-concepts/1-http-hello-world) + +## Additional Resources + +- [API gateway deepdive video](https://www.twitch.tv/videos/408651893?t=00h03m34s) diff --git a/lessons/core-concepts/1-http-hello-world/handler.js b/lessons/core-concepts/1-http-hello-world/handler.js index 3db1110..837c56e 100644 --- a/lessons/core-concepts/1-http-hello-world/handler.js +++ b/lessons/core-concepts/1-http-hello-world/handler.js @@ -1,5 +1,5 @@ -module.exports.hello = (event, context, callback) => { +export const hello = (event, context) => { /* Step 1. In this_file, Create a `200` response code and return the `event` data in the response body. The response needs a `statusCode` and a `body` object returned. Remember to `JSON.stringify` the body. @@ -7,5 +7,5 @@ module.exports.hello = (event, context, callback) => { For more details, see the http event docs link http://bit.ly/2mkgV4P */ const response = {} - return callback(null, response); + return response; } diff --git a/lessons/core-concepts/1-http-hello-world/serverless.yml b/lessons/core-concepts/1-http-hello-world/serverless.yml index 137c946..d263895 100644 --- a/lessons/core-concepts/1-http-hello-world/serverless.yml +++ b/lessons/core-concepts/1-http-hello-world/serverless.yml @@ -3,7 +3,7 @@ service: my-first-service provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x functions: hello: diff --git a/lessons/core-concepts/2-http-dynamic-content/handler.js b/lessons/core-concepts/2-http-dynamic-content/handler.js index 25e8c04..e87bcff 100644 --- a/lessons/core-concepts/2-http-dynamic-content/handler.js +++ b/lessons/core-concepts/2-http-dynamic-content/handler.js @@ -6,14 +6,14 @@ Finally remember to set the headers of the response as `'Content-Type': 'text/html'` to return HTML instead of the default `json` */ -module.exports.queryParamsExample = (event, context, callback) => { +export const queryParamsExample = (event, context) => { const response = { statusCode: 200, body: JSON.stringify({ message: 'Hi ⊂◉‿◉つ', }), } - return callback(null, response) + return response } /* Step 5. In this_file, use the `pathParamsExample` function to return html in the callback. @@ -24,14 +24,14 @@ module.exports.queryParamsExample = (event, context, callback) => { Finally, remember to set the headers of the response as `'Content-Type': 'text/html'` to return HTML instead of the default `json` */ -module.exports.pathParamsExample = (event, context, callback) => { +export const pathParamsExample = (event, context) => { const response = { statusCode: 200, body: JSON.stringify({ message: 'Hi ⊂◉‿◉つ', }), } - return callback(null, response) + return response } /* Utility function for rendering HTML */ diff --git a/lessons/core-concepts/2-http-dynamic-content/serverless.yml b/lessons/core-concepts/2-http-dynamic-content/serverless.yml index fddc716..3280640 100644 --- a/lessons/core-concepts/2-http-dynamic-content/serverless.yml +++ b/lessons/core-concepts/2-http-dynamic-content/serverless.yml @@ -3,7 +3,7 @@ service: using-dynamic-value provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x functions: queryParamsExample: diff --git a/lessons/core-concepts/3-http-post-with-cors/README.md b/lessons/core-concepts/3-http-post-with-cors/README.md index e45e867..3111deb 100644 --- a/lessons/core-concepts/3-http-post-with-cors/README.md +++ b/lessons/core-concepts/3-http-post-with-cors/README.md @@ -12,6 +12,7 @@ This lesson will walk through creating an http function triggered by a `POST` re - [Lesson Steps](#lesson-steps) - [Complete code](#complete-code) +- [Locking down cors to a specific domain](#locking-down-cors-to-a-specific-domain) ## Lesson Steps @@ -44,3 +45,25 @@ This lesson will walk through creating an http function triggered by a `POST` re If you need help or get stuck refer to the completed code of this lesson [View Complete Code](https://github.com/DavidWells/serverless-workshop/tree/master/lessons-code-complete/core-concepts/3-http-post-with-cors) + +## Locking down cors to a specific domain + +```yml +functions: + hello: + handler: handler.hello + events: + - http: + path: hello + method: get + cors: + origin: 'api.myorigin.com' + headers: + - Content-Type + - X-Amz-Date + - Authorization + - X-Api-Key + - X-Amz-Security-Token + - X-Amz-User-Agent + allowCredentials: true +``` diff --git a/lessons/core-concepts/3-http-post-with-cors/handler.js b/lessons/core-concepts/3-http-post-with-cors/handler.js index ec15f84..5d9c174 100644 --- a/lessons/core-concepts/3-http-post-with-cors/handler.js +++ b/lessons/core-concepts/3-http-post-with-cors/handler.js @@ -4,7 +4,7 @@ For additional information, see the cors docs http://bit.ly/2FlFSWB */ -module.exports.functionWithCors = (event, context, callback) => { +export const functionWithCors = (event, context) => { const response = { statusCode: 200, body: JSON.stringify({ @@ -12,5 +12,5 @@ module.exports.functionWithCors = (event, context, callback) => { event: event, }), } - return callback(null, response) + return response } diff --git a/lessons/core-concepts/3-http-post-with-cors/serverless.yml b/lessons/core-concepts/3-http-post-with-cors/serverless.yml index 169534d..f8cad41 100644 --- a/lessons/core-concepts/3-http-post-with-cors/serverless.yml +++ b/lessons/core-concepts/3-http-post-with-cors/serverless.yml @@ -3,7 +3,7 @@ service: using-http-cors-example provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x functions: hello: diff --git a/lessons/core-concepts/4-using-env-vars/handler.js b/lessons/core-concepts/4-using-env-vars/handler.js index a25be8a..4d06d4b 100644 --- a/lessons/core-concepts/4-using-env-vars/handler.js +++ b/lessons/core-concepts/4-using-env-vars/handler.js @@ -6,7 +6,7 @@ Return the environment variable in the `foo` function response */ -module.exports.foo = (event, context, callback) => { +export const foo = (event, context) => { const response = { statusCode: 200, body: JSON.stringify({ @@ -19,7 +19,7 @@ module.exports.foo = (event, context, callback) => { message: 'return env variable here' }), } - return callback(null, response) + return response } /* Step 4. In this_file, access the newly created scoped `bar` environment variables @@ -28,7 +28,7 @@ module.exports.foo = (event, context, callback) => { Return the environment variable in the `bar` function response */ -module.exports.bar = (event, context, callback) => { +export const bar = (event, context) => { const response = { statusCode: 200, @@ -42,5 +42,5 @@ module.exports.bar = (event, context, callback) => { message: 'return env variable here' }), } - return callback(null, response) + return response } diff --git a/lessons/core-concepts/4-using-env-vars/serverless.yml b/lessons/core-concepts/4-using-env-vars/serverless.yml index f1faed0..59acbae 100644 --- a/lessons/core-concepts/4-using-env-vars/serverless.yml +++ b/lessons/core-concepts/4-using-env-vars/serverless.yml @@ -4,7 +4,7 @@ service: using-env-variables-example # Step 1. In this_file, add an environment key & value to the `provider` section. This will allow all functions in the service to access the value. http://bit.ly/2yVp4CR # provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x functions: foo: diff --git a/lessons/core-concepts/5-using-serverless-variable-syntax/handler.js b/lessons/core-concepts/5-using-serverless-variable-syntax/handler.js index 8750707..19ab336 100644 --- a/lessons/core-concepts/5-using-serverless-variable-syntax/handler.js +++ b/lessons/core-concepts/5-using-serverless-variable-syntax/handler.js @@ -1,5 +1,5 @@ -module.exports.foo = (event, context, callback) => { +export const foo = (event, context) => { const response = { statusCode: 200, body: JSON.stringify({ @@ -12,5 +12,5 @@ module.exports.foo = (event, context, callback) => { message: `my super secret thing ${process.env.MY_SECRET}` }), } - return callback(null, response) + return response } diff --git a/lessons/core-concepts/5-using-serverless-variable-syntax/serverless.yml b/lessons/core-concepts/5-using-serverless-variable-syntax/serverless.yml index 65717ec..3f8d00b 100644 --- a/lessons/core-concepts/5-using-serverless-variable-syntax/serverless.yml +++ b/lessons/core-concepts/5-using-serverless-variable-syntax/serverless.yml @@ -4,7 +4,7 @@ service: serverless-variables-syntax-example # Step 2. In this_file, replace the hardcoded value for the for `MY_SECRET` environment variable with a serverless variable using the file reference syntax `${file(path):key}`. Reference the `VAR_FROM_FILE` value from `secrets.json` # provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x environment: MY_SECRET: xyz123 diff --git a/lessons/core-concepts/6-using-addition-resources/handler.js b/lessons/core-concepts/6-using-addition-resources/handler.js index fc44edb..fd1b468 100644 --- a/lessons/core-concepts/6-using-addition-resources/handler.js +++ b/lessons/core-concepts/6-using-addition-resources/handler.js @@ -1,23 +1,25 @@ /* Include the AWS sdk. * No need to add to package.json. It's included in lambda env */ -const AWS = require('aws-sdk') +import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; +import { DynamoDBDocumentClient, PutCommand, ScanCommand } from '@aws-sdk/lib-dynamodb'; // Connect to DynamoDB -const dynamoDb = new AWS.DynamoDB.DocumentClient() +const client = new DynamoDBClient({}); +const dynamoDb = DynamoDBDocumentClient.from(client); // Save item in DynamoDB table -module.exports.create = (event, context, callback) => { +export const create = async (event, context) => { const timestamp = new Date().getTime() const body = JSON.parse(event.body) if (!body || !body.email) { - return callback(null, { + return { statusCode: 401, headers: { 'Content-Type': 'text/plain' }, body: JSON.stringify({ error: 'no body found or email found' }) - }) + } } const params = { @@ -30,48 +32,46 @@ module.exports.create = (event, context, callback) => { } // write the todo to the database - dynamoDb.put(params, (error) => { - // handle potential errors - if (error) { - console.error(error) - return callback(null, { - statusCode: error.statusCode || 501, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t create the dynamo item.', - }) - } - + try { + await dynamoDb.send(new PutCommand(params)); // create a response const response = { statusCode: 200, body: JSON.stringify(params.Item), } - return callback(null, response) - }) + return response + } catch (error) { + // handle potential errors + console.error(error) + return { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t create the dynamo item.', + } + } } /* Scan a dynamoDB table and return items */ -module.exports.scan = (event, context, callback) => { +export const scan = async (event, context) => { const params = { TableName: process.env.MY_TABLE, } // fetch all todos from the database - dynamoDb.scan(params, (error, result) => { - // handle potential errors - if (error) { - console.error(error) - return callback(null, { - statusCode: error.statusCode || 501, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t fetch the todos.', - }) - } - + try { + const result = await dynamoDb.send(new ScanCommand(params)); // create a response const response = { statusCode: 200, body: JSON.stringify(result.Items), } - return callback(null, response) - }) + return response + } catch (error) { + // handle potential errors + console.error(error) + return { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t fetch the todos.', + } + } } diff --git a/lessons/core-concepts/6-using-addition-resources/serverless.yml b/lessons/core-concepts/6-using-addition-resources/serverless.yml index 094019d..58fa7cc 100644 --- a/lessons/core-concepts/6-using-addition-resources/serverless.yml +++ b/lessons/core-concepts/6-using-addition-resources/serverless.yml @@ -3,7 +3,7 @@ service: event-driven-app provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x # Step 3. In this_file, add the database table name to the service's `environment` variables. The `aws-sdk` will need to know the table name in order to access the table # Step 2. In this_file, add a new IAM role the lambda function needs in order to access the newly created dynamoDB table. diff --git a/lessons/core-concepts/7-using-serverless-plugins/handler.js b/lessons/core-concepts/7-using-serverless-plugins/handler.js index c355f23..2e6e0db 100644 --- a/lessons/core-concepts/7-using-serverless-plugins/handler.js +++ b/lessons/core-concepts/7-using-serverless-plugins/handler.js @@ -1,5 +1,5 @@ -module.exports.hello = (event, context, callback) => { +export const hello = (event, context) => { const response = { /* Status code required for default lambda integration */ statusCode: 200, @@ -8,5 +8,5 @@ module.exports.hello = (event, context, callback) => { }), } /** callback(error, response) */ - return callback(null, response) + return response } diff --git a/lessons/core-concepts/7-using-serverless-plugins/package.json b/lessons/core-concepts/7-using-serverless-plugins/package.json index 06a58b0..6ef1a12 100644 --- a/lessons/core-concepts/7-using-serverless-plugins/package.json +++ b/lessons/core-concepts/7-using-serverless-plugins/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "using-serverless-plugins", "main": "index.js", + "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/lessons/core-concepts/7-using-serverless-plugins/serverless.yml b/lessons/core-concepts/7-using-serverless-plugins/serverless.yml index df8f1aa..8633cfe 100644 --- a/lessons/core-concepts/7-using-serverless-plugins/serverless.yml +++ b/lessons/core-concepts/7-using-serverless-plugins/serverless.yml @@ -4,7 +4,7 @@ service: using-serverless-plugins provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x functions: hello: diff --git a/lessons/core-concepts/8-using-multiple-stages/handler.js b/lessons/core-concepts/8-using-multiple-stages/handler.js index c355f23..2e6e0db 100644 --- a/lessons/core-concepts/8-using-multiple-stages/handler.js +++ b/lessons/core-concepts/8-using-multiple-stages/handler.js @@ -1,5 +1,5 @@ -module.exports.hello = (event, context, callback) => { +export const hello = (event, context) => { const response = { /* Status code required for default lambda integration */ statusCode: 200, @@ -8,5 +8,5 @@ module.exports.hello = (event, context, callback) => { }), } /** callback(error, response) */ - return callback(null, response) + return response } diff --git a/lessons/core-concepts/8-using-multiple-stages/package.json b/lessons/core-concepts/8-using-multiple-stages/package.json index 6034242..5e0e359 100644 --- a/lessons/core-concepts/8-using-multiple-stages/package.json +++ b/lessons/core-concepts/8-using-multiple-stages/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "", "main": "index.js", + "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/lessons/core-concepts/8-using-multiple-stages/serverless.yml b/lessons/core-concepts/8-using-multiple-stages/serverless.yml index 34bfa35..a8a63b7 100644 --- a/lessons/core-concepts/8-using-multiple-stages/serverless.yml +++ b/lessons/core-concepts/8-using-multiple-stages/serverless.yml @@ -5,7 +5,7 @@ service: using-multiple-stages provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x # Step 4. In this_file, set the stage key to the custom.stage value set in previous step # Step 5. In this_file, set an environment variable that uses the serverless file variable syntax to grab a file name with the current stage in it. Example `config.dev.json`. Hint this uses nested variables http://bit.ly/2AHvkKO diff --git a/lessons/events/dynamodb-streams/README.md b/lessons/events/dynamodb-streams/README.md index 6b7e6bc..118306a 100644 --- a/lessons/events/dynamodb-streams/README.md +++ b/lessons/events/dynamodb-streams/README.md @@ -5,6 +5,7 @@ This lesson we will be adding a delete function to remove users from the databas - [Lesson Steps](#lesson-steps) - [DynamoDB Stream CloudFormation](#dynamodb-stream-cloudformation) - [Complete code](#complete-code) +- [Additional Resources](#additional-resources) ## Lesson Steps @@ -160,3 +161,7 @@ For additional CloudFormation information check out. https://acloud.guru/learn/a If you need help or get stuck refer to the completed code of this lesson [View Complete Code](https://github.com/DavidWells/serverless-workshop/tree/master/lessons-code-complete/events/dynamodb-streams) + +## Additional Resources + +- [Modeling Relational Data in DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-modeling-nosql-B.html) diff --git a/lessons/events/dynamodb-streams/handler.js b/lessons/events/dynamodb-streams/handler.js index 34db937..d375d5a 100644 --- a/lessons/events/dynamodb-streams/handler.js +++ b/lessons/events/dynamodb-streams/handler.js @@ -1,23 +1,25 @@ /* Include the AWS sdk. * No need to add to package.json. It's included in lambda env */ -const AWS = require('aws-sdk') +import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; +import { DynamoDBDocumentClient, PutCommand, ScanCommand, DeleteCommand } from '@aws-sdk/lib-dynamodb'; // Connect to DynamoDB -const dynamoDb = new AWS.DynamoDB.DocumentClient() +const client = new DynamoDBClient({}); +const dynamoDb = DynamoDBDocumentClient.from(client); // Save item in DynamoDB table -module.exports.create = (event, context, callback) => { +export const create = async (event, context) => { const timestamp = new Date().getTime() const body = JSON.parse(event.body) if (!body || !body.email) { - return callback(null, { + return { statusCode: 401, headers: { 'Content-Type': 'text/plain' }, body: JSON.stringify({ error: 'no body found or email found' }) - }) + } } const params = { @@ -30,64 +32,62 @@ module.exports.create = (event, context, callback) => { } // write the todo to the database - dynamoDb.put(params, (error) => { - // handle potential errors - if (error) { - console.error(error) - return callback(null, { - statusCode: error.statusCode || 501, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t create the dynamo item.', - }) - } - + try { + await dynamoDb.send(new PutCommand(params)); // create a response const response = { statusCode: 200, body: JSON.stringify(params.Item), } - return callback(null, response) - }) + return response + } catch (error) { + // handle potential errors + console.error(error) + return { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t create the dynamo item.', + } + } } /* Scan a dynamoDB table and return items */ -module.exports.scan = (event, context, callback) => { +export const scan = async (event, context) => { const params = { TableName: process.env.MY_TABLE, } // fetch all todos from the database - dynamoDb.scan(params, (error, result) => { - // handle potential errors - if (error) { - console.error(error) - return callback(null, { - statusCode: error.statusCode || 501, - headers: { 'Content-Type': 'text/plain' }, - body: 'Couldn\'t fetch the todos.', - }) - } - + try { + const result = await dynamoDb.send(new ScanCommand(params)); // create a response const response = { statusCode: 200, body: JSON.stringify(result.Items), } - return callback(null, response) - }) + return response + } catch (error) { + // handle potential errors + console.error(error) + return { + statusCode: error.statusCode || 501, + headers: { 'Content-Type': 'text/plain' }, + body: 'Couldn\'t fetch the todos.', + } + } } -module.exports.delete = (event, context, callback) => { +export const delete = async (event, context) => { const body = JSON.parse(event.body) if (!body || !body.id) { - return callback(null, { + return { statusCode: 401, headers: { 'Content-Type': 'text/plain' }, body: JSON.stringify({ error: 'no body found or id found' }) - }) + } } /* Step 1. In this_file, implement the delete item function here via `dynamoDb.delete` method. @@ -103,5 +103,5 @@ module.exports.delete = (event, context, callback) => { See the completed code if in `lessons-code-complete` directory */ /* Function to handle items on the dynamoDB stream */ -module.exports.dynamoStreamHandler = (event, context, callback) => { +export const dynamoStreamHandler = (event, context) => { } diff --git a/lessons/events/dynamodb-streams/package.json b/lessons/events/dynamodb-streams/package.json new file mode 100644 index 0000000..41bb0ba --- /dev/null +++ b/lessons/events/dynamodb-streams/package.json @@ -0,0 +1,10 @@ +{ + "name": "dynamodb-streams-handler", + "version": "1.0.0", + "description": "DynamoDB streams handler using AWS SDK v3", + "type": "module", + "dependencies": { + "@aws-sdk/client-dynamodb": "^3.668.0", + "@aws-sdk/lib-dynamodb": "^3.668.0" + } +} \ No newline at end of file diff --git a/lessons/events/dynamodb-streams/serverless.yml b/lessons/events/dynamodb-streams/serverless.yml index dc326f7..6bd1c95 100644 --- a/lessons/events/dynamodb-streams/serverless.yml +++ b/lessons/events/dynamodb-streams/serverless.yml @@ -3,7 +3,7 @@ service: event-driven-app provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x environment: MY_TABLE: { Ref: myDynamoTable } iamRoleStatements: diff --git a/lessons/events/kinesis/kinesis-basic/handler.js b/lessons/events/kinesis/kinesis-basic/handler.js index 04fa3fe..e24cae8 100644 --- a/lessons/events/kinesis/kinesis-basic/handler.js +++ b/lessons/events/kinesis/kinesis-basic/handler.js @@ -1,7 +1,7 @@ /* Step 6. the `processEvents` in this_file will handle the batch processing of kinesis events */ -module.exports.processEvents = (event, context, callback) => { +export const processEvents = (event, context) => { // Process kinesis event if (event.Records) { event.Records.forEach((record) => { @@ -21,5 +21,5 @@ module.exports.processEvents = (event, context, callback) => { console.log('NEGATIVE decoded record:', payload) }) } - return callback(null, `Successfully processed ${event.Records.length} records.`); + return `Successfully processed ${event.Records.length} records.`; } diff --git a/lessons/events/kinesis/kinesis-basic/package.json b/lessons/events/kinesis/kinesis-basic/package.json index 70f2daa..f0bfccc 100644 --- a/lessons/events/kinesis/kinesis-basic/package.json +++ b/lessons/events/kinesis/kinesis-basic/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "", "main": "handler.js", + "type": "module", "scripts": { "simulate": "node ./scripts/putRecords.js --name=$npm_config_name --count=$npm_config_count --region=$npm_config_region", "test": "echo \"Error: no test specified\" && exit 1" diff --git a/lessons/events/kinesis/kinesis-basic/scripts/putRecords.js b/lessons/events/kinesis/kinesis-basic/scripts/putRecords.js index fefc163..45148da 100644 --- a/lessons/events/kinesis/kinesis-basic/scripts/putRecords.js +++ b/lessons/events/kinesis/kinesis-basic/scripts/putRecords.js @@ -1,5 +1,7 @@ -const AWS = require('aws-sdk') -const argv = require('yargs').argv +import { KinesisClient, PutRecordsCommand } from '@aws-sdk/client-kinesis'; +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; +const argv = yargs(hideBin(process.argv)).argv; const streamName = argv.name const numberOfEvents = argv.count || 20 @@ -10,8 +12,8 @@ if (!streamName) { return false } -function putRecords() { - const kinesis = new AWS.Kinesis({region: AWS_REGION}) +async function putRecords() { + const kinesis = new KinesisClient({region: AWS_REGION}) const params = { Records: Array.from(Array(parseInt(numberOfEvents))).map((_, i) => { const n = (Math.random() * 100).toFixed(2) * (Math.random() > 0.5 ? 1 : -1); @@ -28,7 +30,7 @@ function putRecords() { }), StreamName: streamName, }; - return kinesis.putRecords(params).promise(); + return await kinesis.send(new PutRecordsCommand(params)); } putRecords().then((res) => { diff --git a/lessons/events/kinesis/kinesis-basic/serverless.yml b/lessons/events/kinesis/kinesis-basic/serverless.yml index bc5c463..192b055 100644 --- a/lessons/events/kinesis/kinesis-basic/serverless.yml +++ b/lessons/events/kinesis/kinesis-basic/serverless.yml @@ -7,7 +7,7 @@ service: kinesis-basic-example provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x stage: ${self:custom.stage} # Step 6. In this_file, make a new `function` block and connect to the `processEvents` function. The function `events` will be trigged by the kinesis `arn`. http://bit.ly/2htzI8r # diff --git a/lessons/events/s3/README.md b/lessons/events/s3/README.md index a73c82b..29c5128 100644 --- a/lessons/events/s3/README.md +++ b/lessons/events/s3/README.md @@ -6,6 +6,7 @@ This lesson will walk through triggering a lambda function in response to an s3 - [S3 bucket CloudFormation template](#s3-bucket-cloudformation-template) - [Triggering events from existing buckets](#triggering-events-from-existing-buckets) - [Complete code](#complete-code) +- [Alternative methods](#alternative-methods) ## Lesson Steps @@ -111,3 +112,23 @@ You will need to install the [serverless-external-s3-event plugin](https://githu If you need help or get stuck refer to the completed code of this lesson [View Complete Code](https://github.com/DavidWells/serverless-workshop/tree/master/lessons-code-complete/events/s3) + +## Alternative methods + +You can wire up s3 notifications via cloudformation as well. See [docs](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-notificationconfig.html) + +Example cloudformation: + +```yml +resources: + Resources: + Bucket: + Type: AWS::S3::Bucket + ... + Properties: + NotificationConfiguration: + TopicConfigurations: + - Event: s3:ObjectCreated:Put + Topic: + Ref: BucketTopic +``` diff --git a/lessons/events/s3/package.json b/lessons/events/s3/package.json index 9457313..9e0b0d7 100644 --- a/lessons/events/s3/package.json +++ b/lessons/events/s3/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "aws s3 image resizer", "main": "handler.js", + "type": "module", "scripts": { "deploy": "sls deploy" }, diff --git a/lessons/events/s3/resize.js b/lessons/events/s3/resize.js index e7439e9..ca362dc 100644 --- a/lessons/events/s3/resize.js +++ b/lessons/events/s3/resize.js @@ -1,16 +1,17 @@ // dependencies -const util = require('util') -const path = require('path') -const async = require('async') -const AWS = require('aws-sdk') -const gm = require('gm').subClass({ +import util from 'util'; +import path from 'path'; +import async from 'async'; +import { S3Client, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3'; +import gm from 'gm'; +const gmIM = gm.subClass({ imageMagick: true }) // get reference to S3 client -const s3 = new AWS.S3() +const s3 = new S3Client({}); -module.exports.resizeImage = (event, context, callback) => { +export const resizeImage = async (event, context) => { // Read options from the event. console.log("Reading options from event:\n", util.inspect(event, { depth: 5 @@ -23,12 +24,12 @@ module.exports.resizeImage = (event, context, callback) => { if (srcBucket === dstBucket) { console.log('Destination bucket must not match source bucket.') console.log('This would result in an infinite loop') - return callback(null, { + return { statusCode: 501, body: JSON.stringify({ error: 'Destination Matches source bucket' }), - }); + }; } // Image sizes and output folder paths @@ -79,10 +80,10 @@ module.exports.resizeImage = (event, context, callback) => { // Download the image from S3 into a buffer. // sadly it downloads the image several times, but we couldn't place it outside // the variable was not recognized - s3.getObject({ + s3.send(new GetObjectCommand({ Bucket: srcBucket, Key: srcKey - }, next) + })).then(result => next(null, result)).catch(next) console.timeEnd("downloadImage") }, function convert(response, next) { @@ -90,7 +91,7 @@ module.exports.resizeImage = (event, context, callback) => { console.time("convertImage") console.log(`Reponse content type: ${response.ContentType}`) console.log("Conversion") - gm(response.Body) + gmIM(response.Body) .antialias(true) .density(300) .toBuffer('JPG', (err, buffer) => { @@ -106,7 +107,7 @@ module.exports.resizeImage = (event, context, callback) => { console.time("processImage") // Transform the image buffer in memory. // gm(response.Body).size(function(err, size) { - gm(response).size(function(err, size) { + gmIM(response).size(function(err, size) { // console.log("buf content type " + buf.ContentType) if (err) { console.log(err) @@ -140,12 +141,12 @@ module.exports.resizeImage = (event, context, callback) => { console.log("upload: " + index) console.log(`uploadPath: /${keyPath}`) // Stream the transformed image to a different folder. - s3.putObject({ + s3.send(new PutObjectCommand({ Bucket: dstBucket, Key: keyPath, Body: data, ContentType: 'JPG' - }, next) + })).then(result => next(null, result)).catch(next) console.timeEnd("uploadImage") } ], function(err, result) { @@ -170,6 +171,6 @@ module.exports.resizeImage = (event, context, callback) => { imageProcessed: true }), } - return callback(null, response) + return response }) } diff --git a/lessons/events/s3/save.js b/lessons/events/s3/save.js index e490955..e28fa15 100644 --- a/lessons/events/s3/save.js +++ b/lessons/events/s3/save.js @@ -1,31 +1,30 @@ -const fetch = require('node-fetch') -const AWS = require('aws-sdk') -const s3 = new AWS.S3() +import fetch from 'node-fetch'; +import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'; +const s3 = new S3Client({}); const BUCKET_NAME = process.env.BUCKET_NAME -module.exports.saveImage = (event, context, callback) => { +export const saveImage = async (event, context) => { const key = event.key || JSON.parse(event.body).key const imageURL = event.image_url || JSON.parse(event.body).image_url - fetch(imageURL) - .then(response => response.buffer()) - .then(buffer => ( - s3.putObject({ - Bucket: BUCKET_NAME, - Key: key, - Body: buffer, - }).promise() - )) - .then(() => { - return callback(null, { - statusCode: 200, - body: JSON.stringify({ - success: true, - message: 'image saved to bucket' - }), - }) - }) - .catch((error) => { - return callback(error, null) - }) + try { + const response = await fetch(imageURL); + const buffer = await response.buffer(); + + await s3.send(new PutObjectCommand({ + Bucket: BUCKET_NAME, + Key: key, + Body: buffer, + })); + + return { + statusCode: 200, + body: JSON.stringify({ + success: true, + message: 'image saved to bucket' + }), + } + } catch (error) { + throw error + } } diff --git a/lessons/events/s3/serverless.yml b/lessons/events/s3/serverless.yml index 59a0639..0d00df8 100644 --- a/lessons/events/s3/serverless.yml +++ b/lessons/events/s3/serverless.yml @@ -6,7 +6,7 @@ service: aws-s3-event-example provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x # Step 4. In this_file, Expose the `bucketName` to `environment` variables # # Step 5. In this_file, Create the IAM role `iamRoleStatements` needed to `s3:GetObject` & `s3:PutObject` # Narrow the scope of the permission to the two buckets in this service diff --git a/lessons/events/schedule/handler.js b/lessons/events/schedule/handler.js index d645a36..5f926d3 100644 --- a/lessons/events/schedule/handler.js +++ b/lessons/events/schedule/handler.js @@ -1,4 +1,4 @@ /* Step 2. In this_file, use the `cronFunction` function to do something interesting */ /* Cron function */ -module.exports.cronFunction = (event, context, callback) => { +export const cronFunction = (event, context, callback) => { } diff --git a/lessons/events/schedule/serverless.yml b/lessons/events/schedule/serverless.yml index bc9b8d1..629411a 100644 --- a/lessons/events/schedule/serverless.yml +++ b/lessons/events/schedule/serverless.yml @@ -3,7 +3,7 @@ service: aws-scheduled-cron provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x functions: cron: diff --git a/lessons/events/sns/README.md b/lessons/events/sns/README.md index b8c4e44..f5c145e 100644 --- a/lessons/events/sns/README.md +++ b/lessons/events/sns/README.md @@ -16,6 +16,10 @@ Amazon Simple Notification Service (Amazon SNS) is a web service that coordinate - [Basic Example]('./basic') - [Advanced Example]('./advanced') +## Other Examples + +- [Lambda (publisher) => SNS => Lambda (consumer)](https://github.com/didil/serverless-lambda-sns-example) + ## Troubleshooting - `event.sns.arn.indexOf is not a function` means there is a `yaml` indentation error diff --git a/lessons/events/sns/handler.js b/lessons/events/sns/handler.js index 17be91e..dda8f90 100644 --- a/lessons/events/sns/handler.js +++ b/lessons/events/sns/handler.js @@ -1,8 +1,8 @@ -const AWS = require('aws-sdk') -const sns = new AWS.SNS() +import { SNSClient, PublishCommand } from '@aws-sdk/client-sns'; +const sns = new SNSClient({}); const TOPIC_NAME = process.env.TOPIC_NAME -module.exports.eventProducer = (event, context, callback) => { +export const eventProducer = async (event, context) => { const functionArnCols = context.invokedFunctionArn.split(':') const region = functionArnCols[3] const accountId = functionArnCols[4] @@ -15,22 +15,22 @@ module.exports.eventProducer = (event, context, callback) => { TopicArn: `arn:aws:sns:${region}:${accountId}:${TOPIC_NAME}` } - sns.publish(params, (error, data) => { - if (error) { - return callback(error) - } - return callback(null, { + try { + const data = await sns.send(new PublishCommand(params)); + return { statusCode: 200, body: JSON.stringify({ message: `Message successfully published to SNS topic "${TOPIC_NAME}"` }), - }) - }); + } + } catch (error) { + throw error + } }; -module.exports.eventConsumer = (event, context, callback) => { +export const eventConsumer = (event, context) => { // print out the event information on the console (so that we can see it in the CloudWatch logs) console.log(`I'm triggered by "eventProducer" through the SNS topic "${TOPIC_NAME}"`); console.log(`event:\n${JSON.stringify(event, null, 2)}`); - callback(null, { event }); + return { event }; }; diff --git a/lessons/events/sns/package.json b/lessons/events/sns/package.json new file mode 100644 index 0000000..f2bc0d7 --- /dev/null +++ b/lessons/events/sns/package.json @@ -0,0 +1,9 @@ +{ + "name": "sns-handler", + "version": "1.0.0", + "description": "SNS handler using AWS SDK v3", + "type": "module", + "dependencies": { + "@aws-sdk/client-sns": "^3.668.0" + } +} \ No newline at end of file diff --git a/lessons/events/sns/serverless.yml b/lessons/events/sns/serverless.yml index 3072317..9e591c7 100644 --- a/lessons/events/sns/serverless.yml +++ b/lessons/events/sns/serverless.yml @@ -6,7 +6,7 @@ custom: provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x environment: TOPIC_NAME: ${self:custom.topicName} iamRoleStatements: diff --git a/lessons/events/sns/sns-advanced/handler.js b/lessons/events/sns/sns-advanced/handler.js index 24d933f..aa09553 100644 --- a/lessons/events/sns/sns-advanced/handler.js +++ b/lessons/events/sns/sns-advanced/handler.js @@ -1,8 +1,8 @@ -const AWS = require('aws-sdk') -const sns = new AWS.SNS() +import { SNSClient, PublishCommand } from '@aws-sdk/client-sns'; +const sns = new SNSClient({}); const TOPIC_NAME = process.env.TOPIC_NAME -module.exports.eventProducer = (event, context, callback) => { +export const eventProducer = async (event, context) => { const functionArnCols = context.invokedFunctionArn.split(':') const region = functionArnCols[3] const accountId = functionArnCols[4] @@ -12,24 +12,24 @@ module.exports.eventProducer = (event, context, callback) => { TopicArn: `arn:aws:sns:${region}:${accountId}:${TOPIC_NAME}` } - sns.publish(params, (error, data) => { - if (error) { - return callback(error) - } - return callback(null, { + try { + const data = await sns.send(new PublishCommand(params)); + return { statusCode: 200, body: JSON.stringify({ message: `Message successfully published to SNS topic "${TOPIC_NAME}"` }), - }) - }) + } + } catch (error) { + throw error + } } -module.exports.eventConsumer = (event, context, callback) => { +export const eventConsumer = (event, context) => { // print out the event information on the console (so that we can see it in the CloudWatch logs) console.log(`I'm triggered by "eventProducer" through the SNS topic "${TOPIC_NAME}"`) console.log(`event:\n${JSON.stringify(event, null, 2)}`) - return callback(null, { + return { event: event - }) + } } diff --git a/lessons/events/sns/sns-advanced/package.json b/lessons/events/sns/sns-advanced/package.json index 0f744a6..1b88407 100644 --- a/lessons/events/sns/sns-advanced/package.json +++ b/lessons/events/sns/sns-advanced/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "", "main": "handler.js", + "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/lessons/events/sns/sns-advanced/serverless.yml b/lessons/events/sns/sns-advanced/serverless.yml index 83db08b..646a982 100644 --- a/lessons/events/sns/sns-advanced/serverless.yml +++ b/lessons/events/sns/sns-advanced/serverless.yml @@ -6,7 +6,7 @@ service: aws-sns-advanced provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x # Step 5. In this_file, expose the `TOPIC_NAME` to `environment` variables using the `${self:custom.topicName}` reference. This is for the eventProducer to send message to our newly created topic # # Step 6. In this_file, Create the IAM role `iamRoleStatements` needed to `sns:Publish`. This allows for our `eventProducer` function to publish messages to our SNS topic. diff --git a/lessons/events/sns/sns-basic/handler.js b/lessons/events/sns/sns-basic/handler.js index 24d933f..aa09553 100644 --- a/lessons/events/sns/sns-basic/handler.js +++ b/lessons/events/sns/sns-basic/handler.js @@ -1,8 +1,8 @@ -const AWS = require('aws-sdk') -const sns = new AWS.SNS() +import { SNSClient, PublishCommand } from '@aws-sdk/client-sns'; +const sns = new SNSClient({}); const TOPIC_NAME = process.env.TOPIC_NAME -module.exports.eventProducer = (event, context, callback) => { +export const eventProducer = async (event, context) => { const functionArnCols = context.invokedFunctionArn.split(':') const region = functionArnCols[3] const accountId = functionArnCols[4] @@ -12,24 +12,24 @@ module.exports.eventProducer = (event, context, callback) => { TopicArn: `arn:aws:sns:${region}:${accountId}:${TOPIC_NAME}` } - sns.publish(params, (error, data) => { - if (error) { - return callback(error) - } - return callback(null, { + try { + const data = await sns.send(new PublishCommand(params)); + return { statusCode: 200, body: JSON.stringify({ message: `Message successfully published to SNS topic "${TOPIC_NAME}"` }), - }) - }) + } + } catch (error) { + throw error + } } -module.exports.eventConsumer = (event, context, callback) => { +export const eventConsumer = (event, context) => { // print out the event information on the console (so that we can see it in the CloudWatch logs) console.log(`I'm triggered by "eventProducer" through the SNS topic "${TOPIC_NAME}"`) console.log(`event:\n${JSON.stringify(event, null, 2)}`) - return callback(null, { + return { event: event - }) + } } diff --git a/lessons/events/sns/sns-basic/serverless.yml b/lessons/events/sns/sns-basic/serverless.yml index 6a6a438..7a1cc43 100644 --- a/lessons/events/sns/sns-basic/serverless.yml +++ b/lessons/events/sns/sns-basic/serverless.yml @@ -4,6 +4,6 @@ service: aws-sns-basic provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x # Step 3. In this_file, Expose the `TOPIC_NAME` to `environment` variables using the `${self:custom.topicName}` reference # # Step 4. In this_file, Create the IAM role `iamRoleStatements` needed to `sns:Publish`. This allows for our `eventProducer` function to publish messages to our SNS topic. diff --git a/lessons/events/step-functions/README.md b/lessons/events/step-functions/README.md index ee694a7..c7d6a99 100644 --- a/lessons/events/step-functions/README.md +++ b/lessons/events/step-functions/README.md @@ -112,6 +112,7 @@ This example will walk us through using [AWS step functions](https://aws.amazon. - [Trigger Step functions with AWS Lambda (Video)](https://www.youtube.com/watch?v=u-ALakoQ8kM) - [Passing data between steps in Step Functions (Video)](https://www.youtube.com/watch?v=5RXSTTOiPzk) - [AWS re:Invent 2016: Serverless Apps with AWS Step Functions (Video)](https://www.youtube.com/watch?v=75MRve4nv8s) +- [Wait state in AWS Step Functions](http://vgaltes.com/post/step-functions-wait-state/) ## Complete code diff --git a/lessons/events/step-functions/handler.js b/lessons/events/step-functions/handler.js index 792576e..15f2324 100644 --- a/lessons/events/step-functions/handler.js +++ b/lessons/events/step-functions/handler.js @@ -1,7 +1,7 @@ /* run step function */ -const AWS = require('aws-sdk') +import { SFNClient, StartExecutionCommand } from '@aws-sdk/client-sfn'; const STATE_MACHINE_ARN = process.env.STATE_MACHINE_ARN -const stepfunctions = new AWS.StepFunctions() +const stepfunctions = new SFNClient({}); /* Step 6. In this_file the `startStateMachine` will handle the creation of the new step function. @@ -9,7 +9,7 @@ const stepfunctions = new AWS.StepFunctions() See docs for more details http://amzn.to/2zP0OPW */ -module.exports.startStateMachine = (event, context, callback) => { +export const startStateMachine = async (event, context) => { const body = JSON.parse(event.body) const taskName = body.taskName const startAt = body.startAt @@ -18,7 +18,7 @@ module.exports.startStateMachine = (event, context, callback) => { } -module.exports.sendEmail = (event, context, callback) => { +export const sendEmail = (event, context) => { const time = new Date() console.log(`send email triggered at ${time}`) @@ -30,5 +30,5 @@ module.exports.sendEmail = (event, context, callback) => { }), }; - return callback(null, response); + return response; }; diff --git a/lessons/events/step-functions/package.json b/lessons/events/step-functions/package.json index bebaaab..b64bd38 100644 --- a/lessons/events/step-functions/package.json +++ b/lessons/events/step-functions/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "", "main": "handler.js", + "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/lessons/events/step-functions/serverless.yml b/lessons/events/step-functions/serverless.yml index a4681ca..0e05abb 100644 --- a/lessons/events/step-functions/serverless.yml +++ b/lessons/events/step-functions/serverless.yml @@ -10,7 +10,7 @@ plugins: provider: name: aws - runtime: nodejs12.x + runtime: nodejs22.x stage: ${self:custom.stage} # Step 4. In this_file, reference the Output value of the state machine. ${self:resources.Outputs.MyStateMachine.Value} in the `environment` variables # # Step 5. In this_file, Attach the need `iamRoleStatements` to Allow access to step functions `states:*` # diff --git a/package.json b/package.json index 245c0da..13b366c 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,6 @@ "license": "MIT", "devDependencies": { "markdown-magic": "^0.1.21", - "split-guide-yml": "0.0.3" + "split-guide-yml": "0.0.4" } }