diff --git a/aws-node-sqs-serverless-offline-typescript/.gitignore b/aws-node-sqs-serverless-offline-typescript/.gitignore new file mode 100644 index 000000000..cd5b57b40 --- /dev/null +++ b/aws-node-sqs-serverless-offline-typescript/.gitignore @@ -0,0 +1,107 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and *not* Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# ebuild generated files +.esbuild \ No newline at end of file diff --git a/aws-node-sqs-serverless-offline-typescript/.prettierrc.json b/aws-node-sqs-serverless-offline-typescript/.prettierrc.json new file mode 100644 index 000000000..e8f6b0d3c --- /dev/null +++ b/aws-node-sqs-serverless-offline-typescript/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "semi": true, + "trailingComma": "all", + "singleQuote": true, + "jsxSingleQuote": true, + "printWidth": 120, + "tabWidth": 2 +} \ No newline at end of file diff --git a/aws-node-sqs-serverless-offline-typescript/LICENSE b/aws-node-sqs-serverless-offline-typescript/LICENSE new file mode 100644 index 000000000..3229797c6 --- /dev/null +++ b/aws-node-sqs-serverless-offline-typescript/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 A. Sayef Reyadh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/aws-node-sqs-serverless-offline-typescript/README.md b/aws-node-sqs-serverless-offline-typescript/README.md new file mode 100644 index 000000000..b75344d06 --- /dev/null +++ b/aws-node-sqs-serverless-offline-typescript/README.md @@ -0,0 +1,39 @@ +# serverless-offline-sqs-example-typescript + +This is a sample example project written in Typescript to run AWS SQS with Serverless Offline + +## Setup + +Run this command to initialize a new project in a new working directory. + +``` +npm install +``` + +## Usage + +**Start and Run the Project Offline** + +First you need to install docker in your OS. After installation, run the following commands in two cmd. + +``` +npm run start-elastic-mq +``` + +``` +npm start +``` + +**Invoke the function** + +Make a get request to the following endpoint `http://localhost:8080/` + +``` +curl http://localhost:8080/ +``` + +## Reference + +- https://www.npmjs.com/package/serverless-offline +- https://www.npmjs.com/package/serverless-offline-sqs +- https://github.com/softwaremill/elasticmq diff --git a/aws-node-sqs-serverless-offline-typescript/package.json b/aws-node-sqs-serverless-offline-typescript/package.json new file mode 100644 index 000000000..209c14d6b --- /dev/null +++ b/aws-node-sqs-serverless-offline-typescript/package.json @@ -0,0 +1,28 @@ +{ + "name": "serverless-offline-sqs-example-typescript", + "description": "This is a sample example project written in Typescript to run AWS SQS with Serverless Offline", + "author": "A. Sayef Reyadh", + "version": "0.1.0", + "scripts": { + "start": "sls offline start", + "start-elastic-mq": "docker run --name Offline-SQS --rm -it -p 9324:9324 -p 9325:9325 softwaremill/elasticmq-native" + }, + "dependencies": { + "aws-lambda": "^1.0.7", + "aws-sdk": "^2.1055.0", + "axios": "^0.21.4" + }, + "devDependencies": { + "@types/aws-lambda": "^8.10.92", + "@types/node": "^18.8.4", + "@types/uuid": "^8.3.4", + "esbuild": "^0.14.48", + "serverless": "^3.1.1", + "serverless-esbuild": "^1.31.0", + "serverless-offline": "^8.8.0", + "serverless-offline-sqs": "^6.0.0", + "ts-node": "^10.8.0", + "tsconfig-paths": "^4.0.0", + "typescript": "^4.7.2" + } +} \ No newline at end of file diff --git a/aws-node-sqs-serverless-offline-typescript/serverless.yml b/aws-node-sqs-serverless-offline-typescript/serverless.yml new file mode 100644 index 000000000..3620b16ea --- /dev/null +++ b/aws-node-sqs-serverless-offline-typescript/serverless.yml @@ -0,0 +1,57 @@ +service: serverless-offline-sqs-example-typescript +frameworkVersion: '3' + +provider: + name: aws + runtime: nodejs16.x + region: eu-west-1 + environment: + FIFO_QUEUE_ARN: ${self:custom.myFifoQueueARN} + SQS_OFFLINE_ENDPOINT: ${self:custom.serverless-offline-sqs.endpoint} + +functions: + publisher: + handler: ./src/lambda/publisher.handlePublish + events: + - httpApi: + path: / + method: get + consumer: + handler: ./src/sqs/consumer.handleConsume + events: + - sqs: ${self:custom.myFifoQueueARN} + +resources: + Resources: + myFifoQueue: + Type: 'AWS::SQS::Queue' + Properties: + QueueName: ${self:service}-${sls:stage}-myFifoQueue.fifo + ContentBasedDeduplication: false + FifoQueue: true + +custom: + serverless-offline: + httpPort: 8080 + esbuild: + minify: false + sourcemap: linked + watch: + pattern: ['src/**/*.ts'] + ignore: ['node_modules/**/*'] + serverless-offline-sqs: + autoCreate: true + apiVersion: '2012-11-05' + endpoint: http://0.0.0.0:9324 + region: ${self:provider.region} + accessKeyId: root + secretAccessKey: root + skipCacheInvalidation: false + + myFifoQueueARN: arn:aws:sqs:${aws:region}:${aws:accountId}:${self:service}-${sls:stage}-myFifoQueue.fifo + +plugins: + - serverless-esbuild + # Run elasticMQ server `npm run start-elastic-mq` before enabling serverless-offline-sqs. + - serverless-offline-sqs + - serverless-offline diff --git a/aws-node-sqs-serverless-offline-typescript/src/lambda/publisher.ts b/aws-node-sqs-serverless-offline-typescript/src/lambda/publisher.ts new file mode 100644 index 000000000..b2a56ef95 --- /dev/null +++ b/aws-node-sqs-serverless-offline-typescript/src/lambda/publisher.ts @@ -0,0 +1,53 @@ +import { APIGatewayEvent, ProxyResult } from 'aws-lambda'; +import SQS from 'aws-sdk/clients/sqs'; +import { v1 } from 'uuid'; +import { generateSQSQueueUrlFromArn, getOfflineSqsQueueUrl, isLocalHost } from './utils'; + +export const handlePublish = async (event: APIGatewayEvent): Promise => { + console.log('Publishing message to SQS queue from Lambda'); + const sqsQueueUrl = generateSQSQueueUrlFromArn(process.env.FIFO_QUEUE_ARN); + + if (!sqsQueueUrl) { + return { + statusCode: 404, + body: JSON.stringify('Queue not found'), + }; + } + + const id = v1(); + const sqs = new SQS(); + const url = isLocalHost(event) ? getOfflineSqsQueueUrl(sqsQueueUrl) : sqsQueueUrl; + + try { + const result = await sqs + .sendMessage({ + QueueUrl: url, + MessageBody: JSON.stringify({ + id, + message: 'Hello from Lambda!', + }), + MessageDeduplicationId: id, + MessageGroupId: `Test-Group-${id}`, + MessageAttributes: { + id: { + DataType: 'String', + StringValue: id, + }, + }, + }) + .promise(); + + console.info(`SQS publish result from Lambda: ${JSON.stringify(result, null, 2)}`); + } catch (error) { + console.error('Error:', error); + return { + statusCode: 500, + body: JSON.stringify({ message: 'Error publishing message to SQS' }), + }; + } + + return { + statusCode: 200, + body: JSON.stringify('Published message to SQS queue'), + }; +}; diff --git a/aws-node-sqs-serverless-offline-typescript/src/lambda/utils.ts b/aws-node-sqs-serverless-offline-typescript/src/lambda/utils.ts new file mode 100644 index 000000000..5b9cf6a79 --- /dev/null +++ b/aws-node-sqs-serverless-offline-typescript/src/lambda/utils.ts @@ -0,0 +1,18 @@ +import { APIGatewayEvent } from 'aws-lambda'; +import SQS from 'aws-sdk/clients/sqs'; + +export const isLocalHost = (event: APIGatewayEvent): boolean => { + const isLocalHost = event.headers?.host?.includes('localhost'); + return isLocalHost ?? false; +}; + +export const generateSQSQueueUrlFromArn = (arn: string | undefined): string => { + if (!arn) return ''; + const [_, __, ___, region, accountId, queueName] = arn.split(':'); + return `https://sqs.${region}.amazonaws.com/${accountId}/${queueName}`; +}; + +export const getOfflineSqsQueueUrl = (sqsQueueUrl: string) => { + const url = new URL(sqsQueueUrl); + return `${process.env.SQS_OFFLINE_ENDPOINT}${url.pathname}`; +}; diff --git a/aws-node-sqs-serverless-offline-typescript/src/sqs/consumer.ts b/aws-node-sqs-serverless-offline-typescript/src/sqs/consumer.ts new file mode 100644 index 000000000..a43d55816 --- /dev/null +++ b/aws-node-sqs-serverless-offline-typescript/src/sqs/consumer.ts @@ -0,0 +1,5 @@ +import { SQSEvent } from 'aws-lambda'; + +export const handleConsume = async (event: SQSEvent) => { + console.log('SQS Consumer Event Log:', JSON.stringify(event, null, 2)); +}; diff --git a/aws-node-sqs-serverless-offline-typescript/tsconfig.json b/aws-node-sqs-serverless-offline-typescript/tsconfig.json new file mode 100644 index 000000000..4f5fda9ef --- /dev/null +++ b/aws-node-sqs-serverless-offline-typescript/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "sourceMap": true, + "target": "es2021", + "experimentalDecorators": true, + "module": "commonjs", + "allowSyntheticDefaultImports": true, + "preserveConstEnums": true, + "strictNullChecks": true, + "esModuleInterop": true, + "allowJs": true, + "strict": true, + "useUnknownInCatchVariables": false, + "lib": ["esnext", "dom"], + "outDir": ".build", + "moduleResolution": "node", + "types": ["node"] + } +}