Repository: serverless/examples Branch: v4 Commit: 631c0739a793 Files: 1049 Total size: 1.3 MB Directory structure: gitextract_k325cu19/ ├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .github/ │ ├── pull_request_template.md │ └── workflows/ │ └── trigger-examples-docs.yml ├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── aws-dotnet-rest-api-with-dynamodb/ │ ├── .gitignore │ ├── DotNetServerless.sln │ ├── README.MD │ ├── src/ │ │ ├── DotNetServerless.Application/ │ │ │ ├── DotNetServerless.Application.csproj │ │ │ ├── Entities/ │ │ │ │ └── Item.cs │ │ │ ├── Handlers/ │ │ │ │ ├── CreateItemHandler.cs │ │ │ │ ├── GetItemHandler.cs │ │ │ │ └── UpdateItemHandler.cs │ │ │ ├── Infrastructure/ │ │ │ │ ├── AWSClientFactory.cs │ │ │ │ ├── Configs/ │ │ │ │ │ ├── AwsBasicConfiguration.cs │ │ │ │ │ └── DynamoDbConfiguration.cs │ │ │ │ └── Repositories/ │ │ │ │ ├── IItemRepository.cs │ │ │ │ └── ItemDynamoRepository.cs │ │ │ ├── Requests/ │ │ │ │ ├── CreateItemRequest.cs │ │ │ │ ├── GetItemRequest.cs │ │ │ │ └── UpdateItemRequest.cs │ │ │ └── Responses/ │ │ │ └── ItemResponse.cs │ │ └── DotNetServerless.Lambda/ │ │ ├── DotNetServerless.Lambda.csproj │ │ ├── Extensions/ │ │ │ └── ServicesExtensions.cs │ │ ├── Functions/ │ │ │ ├── CreateItemFunction.cs │ │ │ ├── GetItemFunction.cs │ │ │ └── UpdateItemFunction.cs │ │ ├── Program.cs │ │ ├── Startup.cs │ │ ├── package.json │ │ └── serverless.yml │ └── tests/ │ └── DotNetServerless.Tests/ │ ├── DotNetServerless.Tests.csproj │ └── Functions/ │ ├── CreateItemFunctionTests.cs │ ├── GetItemFunctionTests.cs │ └── UpdateItemFunctionTests.cs ├── aws-ffmpeg-layer/ │ ├── .gitignore │ ├── README.md │ ├── build.sh │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── aws-golang-auth-examples/ │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── functions/ │ │ ├── auth/ │ │ │ ├── auth_suite_test.go │ │ │ ├── main.go │ │ │ └── main_test.go │ │ ├── auth2/ │ │ │ ├── auth_suite_test.go │ │ │ ├── main.go │ │ │ └── main_test.go │ │ ├── hello-world/ │ │ │ ├── hello_world_suite_test.go │ │ │ ├── main.go │ │ │ └── main_test.go │ │ └── hello-world2/ │ │ ├── hello_world_suite_test.go │ │ ├── main.go │ │ └── main_test.go │ ├── go.mod │ ├── go.sum │ ├── package.json │ └── serverless.yml ├── aws-golang-dynamo-stream-to-elasticsearch/ │ ├── Gopkg.toml │ ├── Makefile │ ├── README.md │ ├── cmd/ │ │ ├── aws-golang-dynamo-stream-to-elasticsearch/ │ │ │ └── main.go │ │ └── seed-dynamo/ │ │ └── main.go │ ├── dstream/ │ │ ├── details.go │ │ └── update.go │ ├── package.json │ └── serverless.yml ├── aws-golang-googlemap/ │ ├── Gopkg.toml │ ├── Makefile │ ├── README.md │ ├── geomap/ │ │ └── geomap.go │ ├── getgeodetail/ │ │ └── main.go │ ├── getgeolocation/ │ │ └── main.go │ ├── getnearbylocation/ │ │ └── main.go │ ├── getsearchlocation/ │ │ └── main.go │ └── serverless.yml ├── aws-golang-http-get-post/ │ ├── Gopkg.toml │ ├── Makefile │ ├── README.md │ ├── getFolder/ │ │ ├── getExample.go │ │ ├── getQueryExample.go │ │ ├── go.mod │ │ └── go.sum │ ├── package.json │ ├── postFolder/ │ │ ├── go.mod │ │ ├── go.sum │ │ └── postExample.go │ └── serverless.yml ├── aws-golang-rest-api-with-dynamodb/ │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── package.json │ ├── serverless.yml │ └── todos/ │ ├── create.go │ ├── delete.go │ ├── get.go │ ├── list.go │ └── update.go ├── aws-golang-s3-file-replicator/ │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── gomod.sh │ ├── serverless.yml │ └── src/ │ └── main.go ├── aws-golang-simple-http-endpoint/ │ ├── Gopkg.toml │ ├── Makefile │ ├── README.md │ ├── hello/ │ │ └── main.go │ ├── package.json │ ├── serverless.yml │ └── world/ │ └── main.go ├── aws-golang-stream-kinesis-to-elasticsearch/ │ ├── Gopkg.toml │ ├── Makefile │ ├── README.md │ ├── elastic/ │ │ └── elastic.go │ ├── main.go │ ├── package.json │ └── serverless.yml ├── aws-java-simple-http-endpoint/ │ ├── .gitignore │ ├── README.md │ ├── build.gradle │ ├── gradle/ │ │ └── wrapper/ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── pom.xml │ ├── serverless.yml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── serverless/ │ │ ├── ApiGatewayResponse.java │ │ ├── Handler.java │ │ └── Response.java │ └── resources/ │ └── log4j.properties ├── aws-multiple-runtime/ │ ├── README.md │ ├── api/ │ │ ├── .gitignore │ │ └── handler.js │ ├── serverless.yml │ └── web/ │ └── handler.py ├── aws-node/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ └── serverless.yml ├── aws-node-alexa-skill/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── aws-node-auth0-cognito-custom-authorizers-api/ │ ├── .gitignore │ ├── README.md │ ├── auth.js │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── aws-node-auth0-custom-authorizers-api/ │ ├── .gitignore │ ├── README.md │ ├── frontend/ │ │ ├── app.css │ │ ├── app.js │ │ └── index.html │ ├── handler.js │ ├── package.json │ ├── public_key-example │ ├── secrets.example.json │ └── serverless.yml ├── aws-node-cdk-extension/ │ ├── README.md │ ├── bin/ │ │ └── example.js │ ├── cdk.json │ ├── lib/ │ │ └── example-stack.js │ ├── package.json │ └── serverless.yml ├── aws-node-dynamic-image-resizer/ │ ├── Dockerfile │ ├── README.md │ ├── config/ │ │ ├── pull-secret.sh │ │ └── push-secret.sh │ ├── deploy.sh │ ├── docker-compose.yml │ ├── package.json │ ├── secrets/ │ │ ├── secrets.env │ │ └── secrets.json │ ├── serverless.yml │ ├── src/ │ │ ├── handlers/ │ │ │ └── resizer/ │ │ │ ├── index.js │ │ │ ├── resizeHandler.js │ │ │ └── s3Handler.js │ │ └── lib/ │ │ └── BaseHandler.js │ └── webpack.config.js ├── aws-node-dynamodb-backup/ │ ├── README.md │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── aws-node-env-variables/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── aws-node-env-variables-encrypted-in-a-file/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── package.json │ ├── secrets.dev.yml.encrypted │ ├── secrets.prod.yml.encrypted │ └── serverless.yml ├── aws-node-express-api/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── aws-node-express-dynamodb-api/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── aws-node-fetch-file-and-store-in-s3/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── aws-node-fullstack/ │ ├── README.md │ ├── backend/ │ │ ├── .gitignore │ │ ├── index.js │ │ ├── package.json │ │ └── serverless.yml │ └── frontend/ │ ├── .gitignore │ ├── package.json │ ├── public/ │ │ ├── index.html │ │ └── manifest.json │ └── src/ │ ├── DemoApp.css │ ├── DemoApp.js │ ├── index.css │ └── index.js ├── aws-node-function-compiled-with-babel/ │ ├── .gitignore │ ├── README.md │ ├── createResponse.js │ ├── event.json │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── aws-node-github-check/ │ ├── .babelrc │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── libs/ │ │ ├── github-service.js │ │ └── response-lib.js │ ├── package.json │ ├── serverless.yaml │ └── webpack.config.js ├── aws-node-github-webhook-listener/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── aws-node-graphql-and-rds/ │ ├── README.md │ ├── handler.js │ ├── package.json │ ├── resolver/ │ │ ├── Common/ │ │ │ ├── aurora.js │ │ │ ├── mysql.js │ │ │ └── postgresql.js │ │ ├── Mutation/ │ │ │ ├── aurora_createUser.js │ │ │ ├── mysql_createUser.js │ │ │ └── postgresql_createUser.js │ │ └── Query/ │ │ ├── aurora_getUser.js │ │ ├── mysql_getUser.js │ │ └── postgresql_getUser.js │ ├── resource/ │ │ ├── AuroraRDSCluster.yml │ │ ├── AuroraRDSClusterParameter.yml │ │ ├── AuroraRDSInstance.yml │ │ ├── AuroraRDSInstanceParameter.yml │ │ ├── LambdaRole.yml │ │ ├── MySqlRDSInstance.yml │ │ ├── PostgreSqlRDSInstance.yml │ │ ├── RoutePublic.yml │ │ ├── RouteTableAssociationSubnetA.yml │ │ ├── RouteTableAssociationSubnetB.yml │ │ ├── RouteTableAssociationSubnetC.yml │ │ ├── RouteTablePublic.yml │ │ ├── ServerlessInternetGateway.yml │ │ ├── ServerlessSecurityGroup.yml │ │ ├── ServerlessSubnetA.yml │ │ ├── ServerlessSubnetB.yml │ │ ├── ServerlessSubnetC.yml │ │ ├── ServerlessSubnetGroup.yml │ │ ├── ServerlessVPC.yml │ │ └── ServerlessVPCGA.yml │ ├── schema.gql │ ├── secrets.json │ └── serverless.yml ├── aws-node-graphql-api-with-dynamodb/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── aws-node-heroku-postgres/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── index.js │ ├── package.json │ └── serverless.yml ├── aws-node-http-api/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ └── serverless.yml ├── aws-node-http-api-dynamodb/ │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── serverless.yml │ └── todos/ │ ├── create.js │ ├── delete.js │ ├── get.js │ ├── list.js │ └── update.js ├── aws-node-http-api-dynamodb-local/ │ ├── .gitignore │ ├── README.md │ ├── dynamodb/ │ │ ├── Dockerfile │ │ └── docker-compose.yml │ ├── offline/ │ │ └── migrations/ │ │ └── todos.json │ ├── package.json │ ├── serverless.yml │ └── todos/ │ ├── create.js │ ├── delete.js │ ├── dynamodb.js │ ├── get.js │ ├── list.js │ └── update.js ├── aws-node-http-api-mongodb/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── model/ │ │ └── User.js │ ├── package.json │ └── serverless.yml ├── aws-node-http-api-typescript/ │ ├── .gitignore │ ├── README.md │ ├── handler.ts │ ├── package.json │ ├── serverless.template.yml │ └── serverless.yml ├── aws-node-http-api-typescript-dynamodb/ │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── serverless.yml │ ├── todos/ │ │ ├── create.ts │ │ ├── get.ts │ │ ├── list.ts │ │ └── update.ts │ ├── tsconfig.json │ └── tslint.json ├── aws-node-iot-event/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── aws-node-mongodb-atlas/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── index.js │ ├── package.json │ └── serverless.yml ├── aws-node-oauth-dropbox-api/ │ ├── .gitignore │ ├── README.md │ ├── config/ │ │ ├── default.yml │ │ ├── default_test.yml │ │ ├── stage.yml │ │ └── stage_test.yml │ ├── dropbox/ │ │ └── handler.js │ ├── package.json │ ├── serverless.yml │ └── test/ │ └── test.js ├── aws-node-puppeteer/ │ ├── .gitignore │ ├── README.md │ ├── chrome-script.js │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── aws-node-recursive-function/ │ ├── .gitignore │ ├── README.md │ ├── event.json │ ├── handler.js │ └── serverless.yml ├── aws-node-rekognition-analysis-s3-image/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── lib/ │ │ └── imageAnalyser.js │ ├── package.json │ ├── post.json │ └── serverless.yml ├── aws-node-rest-api/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── serverless.template.yml │ └── serverless.yml ├── aws-node-rest-api-mongodb/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── model/ │ │ └── User.js │ ├── package.json │ └── serverless.yml ├── aws-node-rest-api-typescript/ │ ├── .editorconfig │ ├── .gitignore │ ├── .nycrc.json │ ├── README.md │ ├── app/ │ │ ├── controller/ │ │ │ └── books.ts │ │ ├── handler.ts │ │ ├── model/ │ │ │ ├── books.ts │ │ │ ├── dto/ │ │ │ │ └── createBookDTO.ts │ │ │ ├── index.ts │ │ │ ├── mongoose-db.ts │ │ │ └── vo/ │ │ │ └── responseVo.ts │ │ ├── service/ │ │ │ └── books.ts │ │ └── utils/ │ │ └── message.ts │ ├── package.json │ ├── serverless.template.yml │ ├── serverless.yml │ ├── tests/ │ │ ├── books.mock.ts │ │ └── books.test.ts │ ├── tsconfig.json │ └── tslint.json ├── aws-node-rest-api-typescript-simple/ │ ├── .gitignore │ ├── README.md │ ├── handler.ts │ ├── package.json │ └── serverless.yml ├── aws-node-rest-api-with-dynamodb/ │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── serverless.yml │ └── todos/ │ ├── create.js │ ├── delete.js │ ├── get.js │ ├── list.js │ └── update.js ├── aws-node-rest-api-with-dynamodb-and-offline/ │ ├── .gitignore │ ├── README.md │ ├── dynamodb/ │ │ ├── Dockerfile │ │ └── docker-compose.yml │ ├── offline/ │ │ └── migrations/ │ │ └── todos.json │ ├── package.json │ ├── serverless.yml │ └── todos/ │ ├── create.js │ ├── delete.js │ ├── dynamodb.js │ ├── get.js │ ├── list.js │ └── update.js ├── aws-node-s3-file-replicator/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── aws-node-scheduled-cron/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ └── serverless.yml ├── aws-node-scheduled-weather/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── lib/ │ │ ├── email.js │ │ └── forecast.js │ ├── package.json │ └── serverless.yml ├── aws-node-serve-dynamic-html-via-http-endpoint/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── aws-node-serverless-gong/ │ ├── README.md │ ├── handler.js │ ├── node │ ├── package.json │ ├── serverless-examples@0.0.0 │ └── serverless.yml ├── aws-node-ses-receive-email-body/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── aws-node-ses-receive-email-header/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── aws-node-shared-gateway/ │ ├── README.md │ ├── ci-decomission.sh │ ├── ci-deploy.sh │ ├── gateway/ │ │ ├── .gitignore │ │ └── serverless.yml │ ├── package.json │ ├── products/ │ │ ├── .gitignore │ │ ├── handler.js │ │ └── serverless.yml │ ├── transactions/ │ │ ├── .gitignore │ │ ├── handler.js │ │ └── serverless.yml │ └── users/ │ ├── .gitignore │ ├── handler.js │ └── serverless.yml ├── aws-node-signed-uploads/ │ ├── .babelrc │ ├── .eslintrc │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── serverless.yml │ ├── src/ │ │ └── upload.js │ └── webpack.config.js ├── aws-node-simple-http-endpoint/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── aws-node-simple-transcribe-s3/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── aws-node-single-page-app-via-cloudfront/ │ ├── .gitignore │ ├── README.md │ ├── app/ │ │ ├── app.js │ │ └── index.html │ ├── package.json │ ├── serverless-single-page-app-plugin/ │ │ ├── index.js │ │ └── package.json │ └── serverless.yml ├── aws-node-sqs-worker/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── package.json │ ├── serverless.template.yml │ └── serverless.yml ├── aws-node-stripe-integration/ │ ├── .gitignore │ ├── README.md │ ├── config/ │ │ └── default.yaml │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── aws-node-telegram-echo-bot/ │ ├── README.md │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── aws-node-text-analysis-via-sns-post-processing/ │ ├── .gitignore │ ├── README.md │ ├── addNote.js │ ├── analyzeNote.js │ ├── config.js │ ├── package.json │ └── serverless.yml ├── aws-node-twilio-send-text-message/ │ ├── .gitignore │ ├── .npmignore │ ├── README.md │ ├── event.json │ ├── frontend/ │ │ ├── app.css │ │ └── index.html │ ├── handler.js │ ├── messenger.js │ ├── messenger.test.js │ ├── package.json │ └── serverless.yml ├── aws-node-twitter-joke-bot/ │ ├── README.md │ ├── handler.js │ ├── helpers/ │ │ ├── jokes.js │ │ └── twitter.js │ ├── package.json │ └── serverless.yml ├── aws-node-typescript-apollo-lambda/ │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── serverless.yml │ ├── src/ │ │ ├── graphql/ │ │ │ ├── apolloServer.ts │ │ │ ├── resolvers/ │ │ │ │ ├── mutations/ │ │ │ │ │ ├── dummyMutation.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── queries/ │ │ │ │ │ ├── dummyQuery.ts │ │ │ │ │ └── index.ts │ │ │ │ └── typings.ts │ │ │ └── type-defs/ │ │ │ ├── index.ts │ │ │ ├── inputs/ │ │ │ │ └── DummyInput.ts │ │ │ ├── objects/ │ │ │ │ └── DummyObject.ts │ │ │ └── root/ │ │ │ ├── Mutation.ts │ │ │ └── Query.ts │ │ └── handler.ts │ ├── tsconfig.json │ └── tslint.json ├── aws-node-typescript-kinesis/ │ ├── .gitignore │ ├── README.md │ ├── handler.ts │ ├── kinesis/ │ │ ├── consumer.ts │ │ └── producer.ts │ ├── package.json │ ├── serverless.yml │ ├── tsconfig.json │ ├── tslint.json │ └── webpack.config.js ├── aws-node-typescript-nest/ │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── bench.js │ ├── nest-cli.json │ ├── nodemon-debug.json │ ├── nodemon.json │ ├── package.json │ ├── serverless.yml │ ├── src/ │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ └── main.ts │ ├── test/ │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ ├── tsconfig.json │ ├── tsconfig.spec.json │ └── tslint.json ├── aws-node-typescript-rest-api-with-dynamodb/ │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── serverless.yml │ ├── todos/ │ │ ├── create.ts │ │ ├── get.ts │ │ ├── list.ts │ │ └── update.ts │ ├── tsconfig.json │ └── tslint.json ├── aws-node-typescript-sqs-standard/ │ ├── .gitignore │ ├── README.md │ ├── handler.ts │ ├── package.json │ ├── serverless.yml │ ├── sqs/ │ │ ├── receiver.ts │ │ └── sender.ts │ ├── tsconfig.json │ ├── tslint.json │ └── webpack.config.js ├── aws-node-upload-to-s3-and-postprocess/ │ ├── .gitignore │ ├── README.md │ ├── frontend/ │ │ └── index.template.html │ ├── generate-form.js │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── aws-node-vue-nuxt-ssr/ │ ├── .gitignore │ ├── README.md │ ├── binaryMimeTypes.js │ ├── client/ │ │ ├── components/ │ │ │ └── navbar.vue │ │ ├── layouts/ │ │ │ └── default.vue │ │ └── pages/ │ │ ├── dogs/ │ │ │ ├── _breed.vue │ │ │ └── index.vue │ │ └── index.vue │ ├── index.js │ ├── nuxt.config.js │ ├── nuxt.js │ ├── package.json │ ├── secrets.json │ └── serverless.yml ├── aws-node-websockets-authorizers/ │ ├── README.md │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── aws-python/ │ ├── .gitignore │ ├── README.md │ ├── handler.py │ └── serverless.yml ├── aws-python-alexa-skill/ │ ├── .gitignore │ ├── README.md │ ├── handler.py │ ├── package.json │ └── serverless.yml ├── aws-python-auth0-custom-authorizers-api/ │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── frontend/ │ │ ├── app.css │ │ ├── app.js │ │ └── index.html │ ├── lambda_handlers.py │ ├── package.json │ ├── public_key-example │ ├── requirements.txt │ ├── secrets.example.json │ └── serverless.yml ├── aws-python-flask-api/ │ ├── README.md │ ├── app.py │ ├── package.json │ ├── requirements.txt │ └── serverless.yml ├── aws-python-flask-dynamodb-api/ │ ├── README.md │ ├── app.py │ ├── package.json │ ├── requirements.txt │ └── serverless.yml ├── aws-python-http-api/ │ ├── .gitignore │ ├── README.md │ ├── handler.py │ └── serverless.yml ├── aws-python-http-api-with-dynamodb/ │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── serverless.yml │ └── todos/ │ ├── __init__.py │ ├── create.py │ ├── decimalencoder.py │ ├── delete.py │ ├── get.py │ ├── list.py │ └── update.py ├── aws-python-http-api-with-pynamodb/ │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── requirements.txt │ ├── serverless.yml │ └── todos/ │ ├── __init__.py │ ├── create.py │ ├── delete.py │ ├── get.py │ ├── list.py │ ├── todo_model.py │ └── update.py ├── aws-python-line-echo-bot/ │ ├── README.md │ ├── handler.py │ ├── package.json │ ├── requirements.txt │ ├── serverless.yml │ └── setup.cfg ├── aws-python-pynamodb-s3-sigurl/ │ ├── .gitignore │ ├── README.md │ ├── asset/ │ │ ├── __init__.py │ │ ├── asset_model.py │ │ ├── bucket.py │ │ ├── create.py │ │ ├── delete.py │ │ ├── get.py │ │ ├── list.py │ │ └── update.py │ ├── log_cfg.py │ ├── package.json │ ├── requirements.txt │ └── serverless.yml ├── aws-python-rest-api/ │ ├── .gitignore │ ├── README.md │ ├── handler.py │ ├── serverless.template.yml │ └── serverless.yml ├── aws-python-rest-api-with-dynamodb/ │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── serverless.yml │ └── todos/ │ ├── __init__.py │ ├── create.py │ ├── decimalencoder.py │ ├── delete.py │ ├── get.py │ ├── list.py │ └── update.py ├── aws-python-rest-api-with-faunadb/ │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── requirements.txt │ ├── serverless.yml │ └── todos/ │ ├── __init__.py │ ├── create.py │ ├── delete.py │ ├── get.py │ ├── list.py │ ├── makeresult.py │ ├── schema.py │ └── update.py ├── aws-python-rest-api-with-pymongo/ │ ├── README.md │ ├── item/ │ │ ├── __init__.py │ │ ├── create.py │ │ ├── delete.py │ │ ├── get.py │ │ └── list.py │ ├── package.json │ ├── requirements.txt │ └── serverless.yml ├── aws-python-rest-api-with-pynamodb/ │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── requirements.txt │ ├── serverless.yml │ └── todos/ │ ├── __init__.py │ ├── create.py │ ├── delete.py │ ├── get.py │ ├── list.py │ ├── todo_model.py │ └── update.py ├── aws-python-scheduled-cron/ │ ├── .gitignore │ ├── README.md │ ├── handler.py │ └── serverless.yml ├── aws-python-simple-http-endpoint/ │ ├── .gitignore │ ├── README.md │ ├── handler.py │ ├── package.json │ └── serverless.yml ├── aws-python-sqs-worker/ │ ├── .gitignore │ ├── README.md │ ├── handler.py │ ├── package.json │ ├── serverless.template.yml │ └── serverless.yml ├── aws-python-telegram-bot/ │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── handler.py │ ├── package.json │ ├── requirements.txt │ └── serverless.yml ├── aws-ruby-cron-with-dynamodb/ │ ├── Gemfile │ ├── README.md │ ├── package.json │ ├── serverless.yml │ └── src/ │ ├── common/ │ │ ├── adapters/ │ │ │ └── dynamo_db_adapter.rb │ │ └── services/ │ │ └── create_meal_order_service.rb │ └── handlers/ │ └── create_meal_order/ │ └── handler.rb ├── aws-ruby-line-bot/ │ ├── .gitignore │ ├── Gemfile │ ├── README.md │ ├── handler.rb │ ├── package.json │ └── serverless.yml ├── aws-ruby-simple-http-endpoint/ │ ├── .gitignore │ ├── README.md │ ├── handler.rb │ ├── package.json │ └── serverless.yml ├── aws-ruby-sinatra-dynamodb-api/ │ ├── .gitignore │ ├── .ruby-version │ ├── Gemfile │ ├── README.md │ ├── api.rb │ ├── config.ru │ ├── package.json │ └── serverless.yml ├── aws-ruby-sqs-with-dynamodb/ │ ├── Gemfile │ ├── README.md │ ├── package.json │ ├── serverless.yml │ └── src/ │ ├── common/ │ │ ├── adapters/ │ │ │ ├── dynamo_db_adapter.rb │ │ │ └── sqs_adapter.rb │ │ ├── helpers/ │ │ │ └── requests_helper.rb │ │ ├── schemas/ │ │ │ └── lottery_coupon_schema.rb │ │ ├── serializers/ │ │ │ └── error_serializer.rb │ │ ├── services/ │ │ │ ├── create_lottery_coupon_service.rb │ │ │ └── create_sqs_message_service.rb │ │ └── validators/ │ │ └── lottery_coupon_validator.rb │ ├── handlers/ │ │ └── lottery/ │ │ ├── handler.rb │ │ └── worker.rb │ └── package.json ├── aws-ruby-step-functions/ │ ├── Gemfile │ ├── README.md │ ├── package.json │ ├── serverless.yml │ └── src/ │ ├── common/ │ │ ├── adapters/ │ │ │ └── dynamo_db_adapter.rb │ │ └── services/ │ │ ├── reserve_parking_service.rb │ │ └── ticket_service.rb │ └── handlers/ │ ├── buy_ticket/ │ │ └── handler.rb │ ├── check_weather/ │ │ └── handler.rb │ ├── release_parking_space/ │ │ └── handler.rb │ ├── reserve_parking_lot_space/ │ │ └── handler.rb │ └── return_ticket/ │ └── handler.rb ├── aws-ruby-step-functions-express/ │ ├── Gemfile │ ├── README.md │ ├── package.json │ ├── serverless.yml │ └── src/ │ ├── common/ │ │ ├── adapters/ │ │ │ └── ses_adapter.rb │ │ └── services/ │ │ └── send_email_service.rb │ └── handlers/ │ └── send_email/ │ └── handler.rb ├── aws-ruby-step-functions-with-callback/ │ ├── Gemfile │ ├── README.md │ ├── package.json │ ├── serverless.yml │ └── src/ │ ├── common/ │ │ ├── adapters/ │ │ │ └── step_functions_adapter.rb │ │ └── services/ │ │ ├── detection_service.rb │ │ ├── redaction_service.rb │ │ └── send_task_token_service.rb │ └── handlers/ │ ├── check_comment/ │ │ └── handler.rb │ └── redact_comment/ │ └── handler.rb ├── aws-rust-simple-http-endpoint/ │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── package.json │ ├── serverless.yml │ └── test/ │ ├── Cargo.toml │ └── src/ │ └── main.rs ├── azure-node-line-bot/ │ ├── README.md │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── azure-node-simple-http-endpoint/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── azure-node-telegram-bot/ │ ├── README.md │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── azure-node-typescript-servicebus-trigger-endpoint/ │ ├── .eslintignore │ ├── .eslintrc │ ├── .prettierrc │ ├── README.md │ ├── host.json │ ├── local.settings.json │ ├── package.json │ ├── serverless.yml │ ├── src/ │ │ ├── config/ │ │ │ └── loggerConfig.ts │ │ ├── controller/ │ │ │ ├── messageSenderController.ts │ │ │ └── triggerFunctionController.ts │ │ ├── model/ │ │ │ └── sampleModel.ts │ │ └── service/ │ │ └── serviceBusMessageSender.ts │ ├── tsconfig.json │ └── webpack.config.js ├── check-if-readme-is-up-to-date.sh ├── compose-multiframework/ │ ├── README.md │ ├── cloudformation/ │ │ └── template.yml │ ├── sam/ │ │ ├── handler.js │ │ ├── samconfig.toml │ │ └── template.yml │ ├── serverless-compose.yml │ └── traditional/ │ ├── handler.js │ └── serverless.yml ├── examples.json ├── generate-readme.js ├── google-golang-simple-http-endpoint/ │ ├── .gcloudignore │ ├── README.md │ ├── hello.go │ ├── package.json │ └── serverless.yml ├── google-node-simple-http-endpoint/ │ ├── README.md │ ├── index.js │ ├── package.json │ └── serverless.yml ├── google-node-typescript-http-endpoint/ │ ├── README.md │ ├── package.json │ ├── serverless.yml │ ├── src/ │ │ └── App.ts │ └── tsconfig.json ├── google-python-simple-http-endpoint/ │ ├── README.md │ ├── main.py │ ├── package.json │ ├── requirements.txt │ └── serverless.yml ├── google-ruby-simple-http-endpoint/ │ ├── Gemfile │ ├── README.md │ ├── app.rb │ ├── package.json │ └── serverless.yml ├── kubeless-python-simple-function/ │ ├── README.md │ ├── handler.py │ ├── package.json │ └── serverless.yml ├── kubeless-python-simple-scheduled-function/ │ ├── README.md │ ├── handler.py │ ├── package.json │ └── serverless.yml ├── openwhisk-go-simple/ │ ├── .gitignore │ ├── README.md │ ├── handler.go │ ├── package.json │ └── serverless.yml ├── openwhisk-node-and-docker-chaining-functions/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── openwhisk-node-chaining-functions/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── package.json │ ├── serverless.yml │ └── utils.js ├── openwhisk-node-scheduled-cron/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── openwhisk-node-simple/ │ ├── .gitignore │ ├── README.md │ ├── delay.js │ ├── hello_world.js │ ├── left_pad.js │ ├── package.json │ └── serverless.yml ├── openwhisk-node-simple-http-endpoint/ │ ├── .gitignore │ ├── README.md │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── openwhisk-php-simple/ │ ├── .gitignore │ ├── README.md │ ├── handler.php │ ├── package.json │ └── serverless.yml ├── openwhisk-python-scheduled-cron/ │ ├── .gitignore │ ├── README.md │ ├── handler.py │ ├── package.json │ └── serverless.yml ├── openwhisk-python-simple/ │ ├── .gitignore │ ├── README.md │ ├── handler.py │ ├── package.json │ └── serverless.yml ├── openwhisk-python-simple-http-endpoint/ │ ├── .gitignore │ ├── README.md │ ├── handler.py │ ├── package.json │ └── serverless.yml ├── openwhisk-ruby-simple/ │ ├── .gitignore │ ├── README.md │ ├── handler.rb │ ├── package.json │ └── serverless.yml ├── openwhisk-rust-simple-http-endpoint/ │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── package.json │ ├── serverless.yml │ └── test/ │ ├── Cargo.toml │ └── src/ │ └── main.rs ├── openwhisk-swift-precompiled-binaries/ │ ├── Package.swift │ ├── README.md │ ├── Sources/ │ │ ├── hello/ │ │ │ └── main.swift │ │ └── welcome/ │ │ └── main.swift │ ├── package.json │ └── serverless.yml ├── openwhisk-swift-scheduled-cron/ │ ├── .gitignore │ ├── README.md │ ├── handler.swift │ ├── package.json │ └── serverless.yml ├── openwhisk-swift-simple/ │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── ping.swift │ └── serverless.yml ├── openwhisk-swift-simple-http-endpoint/ │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── ping.swift │ └── serverless.yml ├── package.json ├── twilio-node-forward-call/ │ ├── .gitignore │ ├── README.md │ ├── forward-call.js │ ├── package.json │ └── serverless.yml └── validate.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # EditorConfig helps developers define and maintain consistent # coding styles between different editors and IDEs # editorconfig.org root = true [*] end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 2 [*.{diff,md}] trim_trailing_whitespace = false ================================================ FILE: .eslintignore ================================================ node_modules ================================================ FILE: .eslintrc.json ================================================ { "root": true, "extends": "airbnb-base", "env": { "node": true }, "rules": { "strict": "off", "no-console": "off", "import/no-unresolved": "off" } } ================================================ FILE: .github/pull_request_template.md ================================================ ================================================ FILE: .github/workflows/trigger-examples-docs.yml ================================================ name: Trigger Sync Examples on: push: branches: - v4 paths: - "examples.json" workflow_dispatch: inputs: stage: description: "Deployment stage" required: true default: "dev" type: choice options: - dev - prod jobs: trigger: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - name: Trigger Sync Docs Workflow uses: actions/github-script@v6 with: github-token: ${{ secrets.CI_BOT_GITHUB_TOKEN }} script: | console.log(`Triggering sync-examples workflow for stage: ${{ github.event.inputs.stage || 'dev' }}`); await github.rest.actions.createWorkflowDispatch({ owner: 'serverlessinc', repo: 'growth', workflow_id: 'sync-examples.yml', // Replace with the actual workflow file name in the target repo ref: 'main', inputs: { stage: '${{ github.event.inputs.stage || 'dev' }}' } }); ================================================ FILE: .gitignore ================================================ .serverless *.log npm-debug.log pids *.pid *.seed dist lib-cov coverage .grunt .lock-wscript node_modules .idea .DS_Store .tmp .env env.js env.yml env.json admin.env tmp *.pyc *.swp *.swo vendor ./bin/Debug/netcoreapp2.1/ ./bin/release/netcoreapp2.1/ ================================================ FILE: .travis.yml ================================================ language: node_js sudo: false node_js: - 6 install: - npm install script: - npm run docs - npm run validate - ./check-if-readme-is-up-to-date.sh ================================================ FILE: LICENSE.txt ================================================ The MIT License (MIT) Copyright (c) 2017 Serverless, Inc. http://www.serverless.com The following license applies to all parts of this software except as documented below: ==== 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. ==== All files located in the node_modules and external directories are externally maintained libraries used by this software which have their own licenses; we recommend you read them, as their terms may differ from the terms above. ================================================ FILE: README.md ================================================
Website  •  Documentation  •  X / Twitter  •  Community Slack  •  Forum


![examples-hero](https://github.com/user-attachments/assets/83d9a859-a801-4abd-b812-c4498bb032c3) # Serverless Examples A collection of ready-to-deploy [Serverless Framework](https://github.com/serverless/serverless) services. ## Table of Contents
Click to expand - [Getting Started](#getting-started) - [Examples](#examples) - [Community Examples](#community-examples) - [Contributing](#contributing) - [Adding example code](#adding-example-code) - [Adding a community example](#adding-a-community-example)
## Getting Started If you are new to serverless, we recommend getting started with by creating an HTTP API Endpoint in [NodeJS](https://github.com/serverless/examples/tree/master/aws-node-simple-http-endpoint), [Python](https://github.com/serverless/examples/tree/master/aws-python-simple-http-endpoint), [Java](https://github.com/serverless/examples/tree/master/aws-java-simple-http-endpoint), or [Golang](https://github.com/serverless/examples/tree/master/aws-golang-simple-http-endpoint). ## Examples Each example contains a `README.md` with an explanation about the service and it's use cases. **Have an example?** Submit a PR or [open an issue](https://github.com/serverless/examples/issues). ⚡️ To install any of these you can run: ```bash serverless install -u https://github.com/serverless/examples/tree/master/folder-name -n my-project ``` | Example | Runtime | | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------ | | [Dot Net REST API with DynamoDB](https://github.com/serverless/examples/tree/master/)
Setup a REST API w/ DynamoDB using Dot Net Core 2.1 | unknown | | [AWS FFmepg Layer](https://github.com/serverless/examples/tree/master/)
AWS FFmepg Layer & a service using it to create GIFs | unknown | | [AWS Golang Auth](https://github.com/serverless/examples/tree/master/)
This example shows you how to setup auth in front of a AWS Lambda function | unknown | | [DynamoDB Stream To Elasticsearch](https://github.com/serverless/examples/tree/master/)
Stream data from DynamoDB to Elasticsearch | unknown | | [Google map api](https://github.com/serverless/examples/tree/master/)
Serverless example using golang to hit google map api | unknown | | [HTTP GET and POST](https://github.com/serverless/examples/tree/master/)
Boilerplate code for Golang with GET and POST example | unknown | | [Aws golang rest api with dynamodb](https://github.com/serverless/examples/tree/master/)
Boilerplate code for Golang CRUD Operations | unknown | | [AWS S3 Bucket Replicator in Golang](https://github.com/serverless/examples/tree/master/)
Boilerplate code for Golang with S3 object create event and replicator example | unknown | | [TODO](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup a simple HTTP endpoint in Go. | unknown | | [TODO](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to stream kinesis information into elasticsearch in a golang runtime | unknown | | [AWS Simple HTTP Endpoint example in Java](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup a simple HTTP GET endpoint using Java. Once you ping it, it will reply with the current time. | unknown | | [TODO](https://github.com/serverless/examples/tree/master/)
This example demonstrates how you can run multiple runtimes in AWS Lambda. | unknown | | [AWS Serverless Alexa Skill example in NodeJS](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup your own Alexa skill using AWS Lambdas. | unknown | | [API Gateway Authorizer Function for Auth0 or AWS Cognito using RS256 JSON Web Key Sets tokens.](https://github.com/serverless/examples/tree/master/)
Authorize your API Gateway with either Auth0 or Cognito JWKS RS256 tokens. | unknown | | [AWS API Gateway Custom Authorizer Function with Auth0 example in NodeJS](https://github.com/serverless/examples/tree/master/)
This is an example of how to protect API endpoints with Auth0, JSON Web Tokens (jwt) and a custom authorizer lambda function. | unknown | | [Dynamic Image Resizing API](https://github.com/serverless/examples/tree/master/)
This example shows you how to setup a dynamic image resizer API | unknown | | [TODO](https://github.com/serverless/examples/tree/master/)
This examples shows your how to create a backup of your DynamoDB table to S3. | unknown | | [AWS Storing Encrypted Secrets example in NodeJS](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to store secrets like API keys encrypted in your repository while providing them as environment variables to your AWS Lambda functions. | unknown | | [AWS Serverless Environment Variables Usage example in NodeJS](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to use environment variables for AWS Lambdas. | unknown | | [Node Express API on AWS](https://github.com/serverless/examples/tree/master/)
This template demonstrates how to develop and deploy a simple Node Express API running on AWS Lambda using the Serverless Framework. | unknown | | [Node Express API service backed by DynamoDB on AWS](https://github.com/serverless/examples/tree/master/)
This template demonstrates how to develop and deploy a simple Node Express API service backed by DynamoDB running on AWS Lambda using the Serverless Framework. | unknown | | [AWS Fetch image from URL and upload to S3 example in NodeJS](https://github.com/serverless/examples/tree/master/)
This example display how to fetch an image from remote source (URL) and then upload this image to a S3 bucket. | unknown | | [Serverless Email Sign Up Form](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to deploy a Fullstack serverless application | unknown | | [AWS Function compiled with Babel example in NodeJS](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to compile your JavaScript code with Babel. In order to do so the 'serverless-babel-plugin' is leveraged. | unknown | | [Serverless Github Check](https://github.com/serverless/examples/tree/master/)
The idea is to validate that all Pull Requests are related to a specific trello card. | unknown | | [AWS Serverless Github Webhook Listener example in NodeJS](https://github.com/serverless/examples/tree/master/)
This service will listen to github webhooks fired by a given repository. | unknown | | [A Simple Serverless GraphQL API for MySQL, Postgres and Aurora](https://github.com/serverless/examples/tree/master/)
This is an example project that uses 3 RDS databases to illustrate the differences between using each of them | unknown | | [GraphQL query endpoint in NodeJS on AWS with DynamoDB](https://github.com/serverless/examples/tree/master/)
A single-module GraphQL endpoint with query and mutation functionality. | unknown | | [Node.js AWS Lambda connecting to Heroku Postgres](https://github.com/serverless/examples/tree/master/)
Shows how to connect AWS Lambda to Heroku Postgres. Uses an api:release Heroku webhook and the Heroku API to handle automatic Heroku Postgres credential rotation. | unknown | | [AWS Serverless IoT Event example in NodeJS](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup a AWS IoT Rule to send events to a Lambda function. | unknown | | [Node.js AWS Lambda connecting to MongoDB Atlas](https://github.com/serverless/examples/tree/master/)
Shows how to connect AWS Lambda to MongoDB Atlas. | unknown | | [TODO](https://github.com/serverless/examples/tree/master/)
Connect to Dropbox's API using AWS Lambda. | unknown | | [Running Puppeteer on AWS Lambda](https://github.com/serverless/examples/tree/master/)
This example shows you how to run Puppeteer on AWS Lambda | unknown | | [AWS Recursive Lambda function Invocation example in NodeJS](https://github.com/serverless/examples/tree/master/)
This is an example of a function that will recursively call itself. | unknown | | [AWS Analyse Image from S3 with Amazon Rekognition example in NodeJS](https://github.com/serverless/examples/tree/master/)
This example shows how to analyze an image in an S3 bucket with Amazon Rekognition and return a list of labels. | unknown | | [TODO](https://github.com/serverless/examples/tree/master/)
This example demonstrate how to use MongoDB with AWS and Serverless. | unknown | | [AWS Simple HTTP Endpoint example in NodeJS with Typescript](https://github.com/serverless/examples/tree/master/)
This template demonstrates how to make a simple REST API with Node.js and Typescript running on AWS Lambda and API Gateway using the Serverless Framework v1. | unknown | | [Serverless Nodejs Rest API with TypeScript And MongoDB Atlas](https://github.com/serverless/examples/tree/master/)
This is simple REST API example for AWS Lambda By Serverless framwork with TypeScript and MongoDB Atlas. | unknown | | [AWS Serverless REST API with DynamoDB and offline support example in NodeJS](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to run a service locally, using the 'serverless-offline' plugin. It provides a REST API to manage Todos stored in DynamoDB. | unknown | | [AWS Serverless REST API example in NodeJS](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup a RESTful Web Service allowing you to create, list, get, update and delete Todos. DynamoDB is used to store the data. | unknown | | [AWS Simple HTTP Endpoint example in NodeJS](https://github.com/serverless/examples/tree/master/)
This template demonstrates how to make a simple REST API with Node.js running on AWS Lambda and API Gateway using the traditional Serverless Framework. | unknown | | [AWS Simple HTTP Endpoint example in NodeJS](https://github.com/serverless/examples/tree/master/)
This template demonstrates how to make a simple HTTP API with Node.js running on AWS Lambda and API Gateway using the Serverless Framework. | unknown | | [AWS S3 File Replicator](https://github.com/serverless/examples/tree/master/)
This example creates 2 AWS S3 buckets and copies files in one bucket to the other | unknown | | [AWS Node Scheduled Cron example in NodeJS](https://github.com/serverless/examples/tree/master/)
This is an example of creating a function that runs as a cron job using the serverless ''schedule'' event. | unknown | | [AWS Node Scheduled Weather example in NodeJS](https://github.com/serverless/examples/tree/master/)
This is an example of creating a function that runs as a cron job using the serverless 'schedule' event. It retrieves weather information at 10am (UTC) and emails it to a predefined recipient. | unknown | | [AWS Serving Dynamic HTML via API Gateway example in NodeJS](https://github.com/serverless/examples/tree/master/)
This example illustrates how to hookup an API Gateway endpoint to a Lambda function to render HTML on a GET request. | unknown | | [The Serverless Gong](https://github.com/serverless/examples/tree/master/)
A serverless gong with GitHub and Slack webhooks | unknown | | [AWS SES receive emails and process body](https://github.com/serverless/examples/tree/master/)
This example shows how to process receiving emails, and have S3 trigger a lambda function. | unknown | | [AWS SES receive an email, trigger a lambda function to process header.](https://github.com/serverless/examples/tree/master/)
This example shows how to process receiving email header, and trigger a lambda function. | unknown | | [Shared AWS API Gateway with multiple Node Lambdas](https://github.com/serverless/examples/tree/master/)
A sample of implementing shared API gateway with multiple Node Lambdas | unknown | | [AWS Node Signed Uploads](https://github.com/serverless/examples/tree/master/)
The approach implemented in this service is useful when you want to use Amazon API Gateway and you want to solve the 10MB payload limit | unknown | | [AWS Simple HTTP Endpoint example in NodeJS](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup a simple HTTP GET endpoint. Once you ping it, it will reply with the current time. | unknown | | [Simple AWS Transcribe example in NodeJS](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup a lambda function to transcribe your audio file (.wav format) into a text transcription. The lambda will be triggered whenever a new audio file is uploaded to S3 and the transcription (JSON format) will be saved to a S3 bucket. | unknown | | [AWS Single Page Application example in NodeJS](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup a Single Page Application. | unknown | | [Node SQS Producer Consumer on AWS](https://github.com/serverless/examples/tree/master/)
This template demonstrates how to develop and deploy a simple SQS-based producer-consumer service running on AWS Lambda using the traditional Serverless Framework. | unknown | | [AWS Stripe Integration example in NodeJS](https://github.com/serverless/examples/tree/master/)
This example for Stripe integration using AWS Lambda and API Gateway. | unknown | | [Simple Telegram bot](https://github.com/serverless/examples/tree/master/)
This is a simple echo bot on Telegram. | unknown | | [AWS Data Processing example in NodeJS](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup a simple data processing pipeline. | unknown | | [AWS Send SMS Message with Twilio example in NodeJS](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to send SMS messages with the Twilio SDK and AWS lambda. | unknown | | [Joke Twitter Bot](https://github.com/serverless/examples/tree/master/)
Twitter bot that will periodically tweet out a joke obtained from a joke API. | unknown | | [AWS Apollo Lambda (NodeJS & Typescript)](https://github.com/serverless/examples/tree/master/)
This example provides a setup for a Lambda Graphql API with apollo | unknown | | [AWS Kinesis Data Streams Example (NodeJS & Typescript)](https://github.com/serverless/examples/tree/master/)
Produce and Consume data on a Kinesis Data Stream with Typescript. | unknown | | [AWS Nest application example (NodeJS & Typescript)](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup a simple [Nest](https://github.com/nestjs/nest) application. | unknown | | [TODO](https://github.com/serverless/examples/tree/master/)
This example shows your how to create a TypeScript powered REST API with DynamoDB. | unknown | | [AWS SQS Standard Example (NodeJS & Typescript)](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup a SQS with Typescript. | unknown | | [AWS Upload a file to S3 to trigger a Lambda function example in NodeJS](https://github.com/serverless/examples/tree/master/)
This example shows how to upload a file to S3 using a HTML form, and have S3 trigger a lambda function. | unknown | | [Serverless side rendering with Vue.js and Nuxt.js](https://github.com/serverless/examples/tree/master/)
This project demonstrates how to use Nuxt.js to create a server-side rendered Vue.js app on AWS Lambda and AWS API Gateway. | unknown | | [Simple Websocket Authorizers](https://github.com/serverless/examples/tree/master/)
The example shows you how to deploy simple websocket authorizers | unknown | | [AWS NodeJS Example](https://github.com/serverless/examples/tree/master/)
This template demonstrates how to deploy a simple NodeJS function running on AWS Lambda using the Serverless Framework. | unknown | | [AWS Serverless Alexa Skill example in Python](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup your own Alexa skill using AWS Lambdas. | unknown | | [AWS API Gateway Custom Authorizer Function with Auth0 example in Python](https://github.com/serverless/examples/tree/master/)
This is an example of how to protect API endpoints with Auth0, JSON Web Tokens (jwt) and a custom authorizer lambda function in Python 3. | unknown | | [Python Flask API on AWS](https://github.com/serverless/examples/tree/master/)
This template demonstrates how to develop and deploy a simple Python Flask API running on AWS Lambda using the Serverless Framework. | unknown | | [Python Flask API backed by DynamoDB on AWS](https://github.com/serverless/examples/tree/master/)
This template demonstrates how to develop and deploy a simple Python Flask API service backed by DynamoDB running on AWS Lambda using the Serverless Framework. | unknown | | [Simple LINE bot](https://github.com/serverless/examples/tree/master/)
This is a simple echo bot on LINE bot. | unknown | | [AWS Serverless REST API with DynamoDB store and presigned URLs example in Python 3.6.](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup a RESTful Web Service allowing you to create, list, get, update and delete Assets. DynamoDB is used to store the data. | unknown | | [AWS Serverless REST API with DynamoDB store example in Python](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup a RESTful Web Service allowing you to create, list, get, update and delete Todos. DynamoDB is used to store the data. | unknown | | [AWS Serverless REST API with FaunaDB store example in Python](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup a RESTful Web Service allowing you to create, list, get, update and delete Todos. FaunaDB is used to store the data. | unknown | | [AWS Python Rest API with Pymongo](https://github.com/serverless/examples/tree/master/)
AWS Python Rest API with Pymongo Example | unknown | | [AWS Serverless REST API with DynamoDB store example in Python](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup a RESTful Web Service allowing you to create, list, get, update and delete Todos. DynamoDB is used to store the data. | unknown | | [AWS Simple HTTP Endpoint example in Python](https://github.com/serverless/examples/tree/master/)
This template demonstrates how to make a simple REST API with Python running on AWS Lambda and API Gateway using the traditional Serverless Framework. | unknown | | [AWS Simple HTTP Endpoint example in Python](https://github.com/serverless/examples/tree/master/)
This template demonstrates how to make a simple HTTP API with Python running on AWS Lambda and API Gateway using the Serverless Framework. | unknown | | [AWS Python Scheduled Cron example in Python](https://github.com/serverless/examples/tree/master/)
This is an example of creating a function that runs as a cron job using the serverless ''schedule'' event. | unknown | | [AWS Simple HTTP Endpoint example in Python](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup a simple HTTP GET endpoint. Once you ping it, it will reply with the current time. | unknown | | [Python SQS Producer Consumer on AWS](https://github.com/serverless/examples/tree/master/)
This template demonstrates how to develop and deploy a simple SQS-based producer-consumer service running on AWS Lambda using the traditional Serverless Framework. | unknown | | [TODO](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup an echo Telegram Bot using the Serverless Framework. | unknown | | [AWS Python Example](https://github.com/serverless/examples/tree/master/)
This template demonstrates how to deploy a Python function running on AWS Lambda using the Serverless Framework. | unknown | | [Ruby LINE bot](https://github.com/serverless/examples/tree/master/)
This example shows you how to create a LINE bot using Ruby. | unknown | | [AWS Simple HTTP Endpoint example in Ruby](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup a simple HTTP GET endpoint. Once you ping it, it will reply with the current time. | unknown | | [Ruby Sinatra API backed by DynamoDB on AWS](https://github.com/serverless/examples/tree/master/)
This template demonstrates how to develop and deploy a simple Ruby Sinatra API service backed by DynamoDB running on AWS Lambda using the traditional Serverless Framework. | unknown | | [AWS Ruby scheduled cron example backed by DynamoDB](https://github.com/serverless/examples/tree/master/)
This is an example of creating a function that runs as a cron job using the serverless 'schedule' event. With the usage of the AWS Lambda function, it creates a record to the DynamoDB each and every 30 minutes. | unknown | | [AWS Ruby Step Functions](https://github.com/serverless/examples/tree/master/)
AWS Ruby example that make usage of AWS Step Functions with AWS Lambda, DynamoDB and Step Functions flows. | unknown | | [Serverless AWS Ruby SQS with DynamoDB example](https://github.com/serverless/examples/tree/master/)
A serverless ruby example that creates DynamoDB records with the usage of SQS, API Gateway, and AWS Lambda functions. | unknown | | [AWS Serverless Boilerplate example in Rust](https://github.com/serverless/examples/tree/master/)
This example shows a Serverless boilerplate in Rust. | unknown | | [TODO](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup a serverless Line Bot using Node.js. | unknown | | [Azure Simple HTTP Endpoint example in NodeJS](https://github.com/serverless/examples/tree/master/)
In this example, we deploy an HTTP Node.js Azure Function. This example shows you how to read properties off of a query string or the request body, then set a result back to Azure. | unknown | | [TODO](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup a serverless Telegram Bot on Azure. | unknown | | [Using Azure Service Queue to trigger Azure Function](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to trigger an Azure function when a message arrives in Service Bus Queue | unknown | | [GCF Simple HTTP Endpoint example in golang](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup a simple golang HTTP GET endpoint on GCP Cloud Functions. When you ping the endpoint we've set up you'll see the time returned for the given request type. | unknown | | [GCF Simple HTTP Endpoint example in NodeJS](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup a simple HTTP GET endpoint. | unknown | | [Typescript HTTP Endpoint](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup a simple Typescript HTTP endpoint on GCP. | unknown | | [GCF Simple HTTP Endpoint example in Python](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup a simple python HTTP GET endpoint on GCP Cloud Functions. When you ping the endpoint we've set up you'll see the time returned for the given request type. | unknown | | [Kubeless Serverless Simple function example in Python](https://github.com/serverless/examples/tree/master/)
This example demonstrates a simple function example in Python. | unknown | | [Kubeless Serverless Simple scheduled function example in Python](https://github.com/serverless/examples/tree/master/)
This example demonstrates a simple sexample in Python for a scheduled function. | unknown | | [OpenWhisk Serverless Boilerplate example in Go](https://github.com/serverless/examples/tree/master/)
This example shows a Serverless boilerplate in Go. | unknown | | [OpenWhisk Serverless Boilerplate using Docker example in NodeJS](https://github.com/serverless/examples/tree/master/)
This example shows a Serverless boilerplate using Docker in NodeJS. | unknown | | [OpenWhisk Serverless Chaining Functions example in NodeJS](https://github.com/serverless/examples/tree/master/)
This example demonstrates chaining functions in NodeJS. | unknown | | [OpenWhisk Serverless Scheduled Cron job example in NodeJS](https://github.com/serverless/examples/tree/master/)
This example demonstrates scheduleding a cron job in NodeJS. | unknown | | [OpenWhisk Simple HTTP Endpoint example in NodeJS](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup a simple HTTP GET endpoint. | unknown | | [OpenWhisk Serverless Simple example in NodeJS](https://github.com/serverless/examples/tree/master/)
This example demonstrates a simple example in NodeJS. | unknown | | [OpenWhisk Serverless Simple example in PHP](https://github.com/serverless/examples/tree/master/)
This example demonstrates a simple example in PHP. | unknown | | [OpenWhisk Serverless Scheduled Cron job example in Python](https://github.com/serverless/examples/tree/master/)
This example demonstrates scheduleding a cron job. | unknown | | [OpenWhisk Simple HTTP Endpoint example in Python](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup a simple HTTP GET endpoint. | unknown | | [OpenWhisk Serverless Simple example in Python](https://github.com/serverless/examples/tree/master/)
This example demonstrates a simple example in Python. | unknown | | [OpenWhisk Serverless Simple example in Ruby](https://github.com/serverless/examples/tree/master/)
This example demonstrates a simple example in Ruby. | unknown | | [OpenWhisk Serverless Boilerplate example in Rust](https://github.com/serverless/examples/tree/master/)
This example shows a Serverless boilerplate in Rust. | unknown | | [OpenWhisk Swift example with external libraries and pre compiled binaries](https://github.com/serverless/examples/tree/master/)
This example shows you how to use external packages and deploy binaries | unknown | | [OpenWhisk Serverless Scheduled Cron job example in Swift](https://github.com/serverless/examples/tree/master/)
This example demonstrates scheduling a cron job. | unknown | | [OpenWhisk Simple HTTP Endpoint example in Swift](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup a simple HTTP GET endpoint. | unknown | | [OpenWhisk Serverless Simple example in Swift](https://github.com/serverless/examples/tree/master/)
This example demonstrates a simple example in Swift. | unknown | | [Twilio Forward a Call](https://github.com/serverless/examples/tree/master/)
This example projects helps you deploy a serverless function to the Twilio runtime. The function responds the TwiML configuration to forward phone call. | unknown | | [Serverless Lambda S3 Demonstration](https://github.com/serverless/examples/tree/master/)
This project demonstrates how the Serverless Framework can be used to deploy a NodeJS Lambda function that responds to events in an S3 bucket. | unknown | | [Spiderless, Web Spider on Serverless](https://github.com/serverless/examples/tree/master/)
A web spider / scraper / website change detector built with Lambda, API Gateway, DynamoDB and SNS | unknown | | [AWS Demo Java Spring Cloud Function Serverless](https://github.com/serverless/examples/tree/master/)
If Java is your choice of programming language-Spring Cloud Function,Serverless Framework makes a great technology stack. It boosts developer productivity by decoupling from Vendor specific FaaS API, and deployment activities. | unknown | | [Serverless Architecture Boilerplate](https://github.com/serverless/examples/tree/master/)
Boilerplate to organize and deploy big projects using Serverless and CloudFormation on AWS | unknown | | [JwtAuthorizr](https://github.com/serverless/examples/tree/master/)
Custom JWT Authorizer Lambda function for Amazon API Gateway with Bearer JWT | unknown | | [Slack signup serverless](https://github.com/serverless/examples/tree/master/)
Serverless signup to Slack and more. Lambda with Python, StepFunctions, and Web front end. Python boilerplate included. | unknown | | [Serverless graphql api](https://github.com/serverless/examples/tree/master/)
Serverless GraphQL API using Lambda and DynamoDB | unknown | | [Serverless screenshot](https://github.com/serverless/examples/tree/master/)
Serverless Screenshot Service using PhantomJS | unknown | | [Serverless postgraphql](https://github.com/serverless/examples/tree/master/)
GraphQL endpoint for PostgreSQL using postgraphql | unknown | | [Serverless messenger boilerplate](https://github.com/serverless/examples/tree/master/)
Serverless messenger bot boilerplate | unknown | | [Serverless npm registry](https://github.com/serverless/examples/tree/master/)
Serverless private npm registry, proxy and cache. | unknown | | [Serverless facebook quotebot](https://github.com/serverless/examples/tree/master/)
100% Serverless Facebook messenger chatbot which will respond with inspiring quotes | unknown | | [Serverless slack trevorbot](https://github.com/serverless/examples/tree/master/)
Slack bot for info on where in the world is Trevor Gerhardt? | unknown | | [Pfs email serverless](https://github.com/serverless/examples/tree/master/)
This is a lambda function created by the serverless framework. It searches through members in our mongodb who have not been sent emails and sends them an email with their custom token to unlock the pledge free stream. It then marks those members off as already receiving the email. | unknown | | [Plaid cashburndown service](https://github.com/serverless/examples/tree/master/)
Service for calculating cash burndown with plaid. Frontend code can be found here: https://github.com/cplee/cashburndown-site | unknown | | [Cordis serverless](https://github.com/serverless/examples/tree/master/)
A serverless API for EU Cordis data | unknown | | [Serverless newsletter signup](https://github.com/serverless/examples/tree/master/)
Saves user details into DynamoDB table. Required values are email, first_name and last_name. | unknown | | [Serverless slack cron](https://github.com/serverless/examples/tree/master/)
Lambda function which sends messages to Slack channel in regular intervals via cron trigger. | unknown | | [Sls access counter](https://github.com/serverless/examples/tree/master/)
Site visitor counter | unknown | | [Sls form mail](https://github.com/serverless/examples/tree/master/)
Send SNS email from form data | unknown | | [Serverless python sample](https://github.com/serverless/examples/tree/master/)
A simple serverless python sample with REST API endpoints and dependencies | unknown | | [Serverless slack emojibot](https://github.com/serverless/examples/tree/master/)
Serverless slack bot for emoji | unknown | | [Serverless cloudwatch rds custom metrics](https://github.com/serverless/examples/tree/master/)
A NodeJS-based MySQL RDS Data Collection script to push Custom Metrics to Cloudwatch with Serverless | unknown | | [Sc5 serverless boilerplate](https://github.com/serverless/examples/tree/master/)
A boilerplate that contains setup for test-driven development | unknown | | [Serverless blog to podcast](https://github.com/serverless/examples/tree/master/)
Service that reads RSS feed and converts the entries to a podcast feed and audio files using Amazon Polly | unknown | | [Offset trump](https://github.com/serverless/examples/tree/master/)
Single page app using Serverless (C# runtime) and S3 site hosting. Pledge to do a good thing for the next four years to offset the potential negative effects of the US Presidency | unknown | | [Serverless url shortener](https://github.com/serverless/examples/tree/master/)
A simple url-shortener, using Serverless framework | unknown | | [Serverless html pdf](https://github.com/serverless/examples/tree/master/)
Service that convert HTML to PDF using PhantomJS's rasterize example. | unknown | | [Serverless examples cached rds ws](https://github.com/serverless/examples/tree/master/)
A serverless framework example project that uses API Gateway, ElastiCache, and RDS PostgreSQL. | unknown | | [Bittman](https://github.com/serverless/examples/tree/master/)
A serverless project that follows a stock trading algorithm and uses scheduled functions to save data to DynamoDB and send emails through Mailgun. | unknown | | [Adoptable pet bot](https://github.com/serverless/examples/tree/master/)
Tweets adoptable pets using Serverless (Node.js) and AWS Lambda | unknown | | [Owntracks serverless](https://github.com/serverless/examples/tree/master/)
A serverless implementation of the OwnTracks HTTP backend | unknown | | [Serverless modern koa](https://github.com/serverless/examples/tree/master/)
Serverless modern koa starter kit | unknown | | [Serverless ReactJS Universal Rendering Boilerplate](https://github.com/serverless/examples/tree/master/)
ReactJS web app Starter kit does universal (isomorphic) rendering with Serverless | unknown | | [Open Bot](https://github.com/serverless/examples/tree/master/)
An unoptionated Github bot driven by a configuration file in the repository | unknown | | [Aws ses serverless example](https://github.com/serverless/examples/tree/master/)
AWS SES example in NodeJS using lambda | unknown | | [Aws node signed uploads](https://github.com/serverless/examples/tree/master/)
Upload files larger than 10MB with AWS Lambda and API Gateway. Can be developed and tested locally. | unknown | | [SQS Worker with AWS Lambda and CloudWatch Alarms](https://github.com/serverless/examples/tree/master/)
Process messages stored in SQS with an [auto-scaled AWS Lambda worker](https://sbstjn.com/serverless-sqs-worker-with-aws-lambda.html) function. | unknown | | [Serverless image manager](https://github.com/serverless/examples/tree/master/)
image upload / download with resizing. Used API gateway's binary support & serverless | unknown | | [Amazon Kinesis Streams fan out via Kinesis Analytics](https://github.com/serverless/examples/tree/master/)
Use Amazon Kinesis Analytics to fan-out your Kinesis Streams and avoid read throttling. | unknown | | [HoneyLambda](https://github.com/serverless/examples/tree/master/)
a simple, serverless application designed to create and monitor URL {honey}tokens, on top of AWS Lambda and Amazon API Gateway | unknown | | [Faultline](https://github.com/serverless/examples/tree/master/)
Error tracking tool on AWS managed services. | unknown | | [Stack Overflow Monitor](https://github.com/serverless/examples/tree/master/)
Monitor Stack Overflow questions and post them in a Slack channel | unknown | | [Serverless Analytics](https://github.com/serverless/examples/tree/master/)
Write your own Google Analytics clone and track website visitors serverless with API Gateway, Kinesis, Lambda, and DynamoDB. | unknown | | [Serverless + medium text to speech](https://github.com/serverless/examples/tree/master/)
Serverless-based, text-to-speech service for Medium articles | unknown | | [Serverless + java DynamoDB imlementation example](https://github.com/serverless/examples/tree/master/)
example for java programmers that want to work with AWS-Lambda and DynamoDB | unknown | | [AWS Cognito Custom User Pool Example](https://github.com/serverless/examples/tree/master/)
Example CloudFormation custom resource backed by a lambda using Cognito User Pools | unknown | | [AWS Lambda, Amazon API Gateway, S3, DynamoDB and Cognito Example](https://github.com/serverless/examples/tree/master/)
Step by step guide how to deploy simple web application on top of AWS Lambda, Amazon API Gateway, S3, DynamoDB and Cognito. | unknown | | [Run your Kubernetes Workloads on Amazon EC2 Spot Instances with Amazon EKS and Lambda Part 1](https://github.com/serverless/examples/tree/master/)
From this tutorial you'll learn how to add AWS EKS Cluster with Spot Instances to your cloud environment managed by Serverless framework | unknown | | [Serverless + lambda protobuf responses](https://github.com/serverless/examples/tree/master/)
Demo using API Gateway and Lambda with Protocol Buffer | unknown | | [Serverless Telegram Bot](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to setup an echo Telegram Bot using the Serverless Framework ⚡🤖 | unknown | | [Serverless + lambda + vpc + nat + redis](https://github.com/serverless/examples/tree/master/)
Demo using API Gateway and Lambda with VPC and NAT to access Internet and AWS Resource | unknown | | [Serverless Gitlab CI](https://github.com/serverless/examples/tree/master/)
Simple Gitlab CI template for automatic testing and deployments | unknown | | [Serverless ffmpeg](https://github.com/serverless/examples/tree/master/)
Bucket event driven FFMPEG using serverless. Input bucket => Serverless ffmpeg => Output bucket. | unknown | | [Realtime WW2 Alexa Skill](https://github.com/serverless/examples/tree/master/)
An alexa skill project that's using Alexa SDK. Can also be used for a working example of serverless-webpack (with use of async/await via babel). | unknown | | [Serverless Kakao Bot](https://github.com/serverless/examples/tree/master/)
Easy development for Kakaotalk Bot with Serverless | unknown | | [Personal Access Tokens Cron Check](https://github.com/serverless/examples/tree/master/)
Audit for leaked PAT in your Contentful organization. How to use serverless as cronjobs to keep your Personal Access Tokens secure | unknown | | [Daily Instance Backups with AMI Rotation](https://github.com/serverless/examples/tree/master/)
A simple Python application which scans through your entire AWS account for tagged instances, makes daily AMIs of them, and rotates their backups automatically | unknown | | [Serverless Instagram Crawler](https://github.com/serverless/examples/tree/master/)
Instagram hashtag Crawler with Lambda & DynamoDB. | unknown | | [Serverless Next.js Example](https://github.com/serverless/examples/tree/master/)
Next.js example project for development & deploy. | unknown | | [Serving binary files](https://github.com/serverless/examples/tree/master/)
Small example showing how to serve binary files using Serverless on AWS with the serverless-apigw-binary plugin, using generated Excel files as an example | unknown | | [Lambda PubSub via SNS Example](https://github.com/serverless/examples/tree/master/)
Example illustrating the flow: Lambda (publisher) => SNS => Lambda (consumer) | unknown | | [Serverless CloudWatch Proxy](https://github.com/serverless/examples/tree/master/)
Logging adapter that consumes log streams from AWS CloudWatch, streams them to other log destinations. Also capable of identying alerts and sending notifications via Slack/Email | unknown | | [Serverless side rendering with Vue.js and Nuxt.js](https://github.com/serverless/examples/tree/master/)
Sample project for using Nuxt.js to create a server-side rendered Vue.js app on AWS Lambda and AWS API Gateway. Can easily integrate with your own API or 3rd party APIs such as headless CMS, e-commerce or serverless architecture. | unknown | | [Aws mfa enforce](https://github.com/serverless/examples/tree/master/)
Serverless function to automate enforcement of Multi-Factor Authentication (MFA) to all AWS IAM users with access to AWS Management Console. | unknown | | [Vanity stargazer](https://github.com/serverless/examples/tree/master/)
Github vanity-stargazer is a serverless application to handle posting Github new star gazers to Slack | unknown | | [Fotopia Serverless](https://github.com/serverless/examples/tree/master/)
A photo archive web app including API, storage and face detection using serverless framework | unknown | | [Commenting API](https://github.com/serverless/examples/tree/master/)
A commenting api using Serverless Typescript GraphQl and Redis | unknown | | [Serverless node api dynamodb neo4j](https://github.com/serverless/examples/tree/master/)
Architecture example to stream DynamoDB data to a read-model using Neo4j | unknown | | [Serverless python rds cron](https://github.com/serverless/examples/tree/master/)
A serverless python example that periodically removes entries from AWS RDS | unknown | | [Nietzsche](https://github.com/serverless/examples/tree/master/)
A serverless application that fetches quotes from Goodreads and saves it to DynamoDB with example use cases using `Lambda`, `SNS`, `SQS`, `Step Functions`, `DynamoDB`, `API Gateway`, `CloudWatch` | unknown | | [Serverless DotNet BoilerPlate](https://github.com/serverless/examples/tree/master/)
A serverless starter solution for .NET Core, ready for local debugging in VS Code, HTTP Endpoint, etc. | unknown | | [Serverless Load Balancer](https://github.com/serverless/examples/tree/master/)
A sample that shows how to combine a load balancer with (vpc/subnet configuration) with a lambda. | unknown | | [Serverless api typescript template](https://github.com/serverless/examples/tree/master/)
A starter template for a Serverless API using Typescript and Jest | unknown | | [Serverless SNS SQS offline Example ](https://github.com/serverless/examples/tree/master/)
Minimal example of running serverless-offline with SQS and SNS in local environment. | unknown | | [Serverless RDS Log Sync S3](https://github.com/serverless/examples/tree/master/)
Annotated exmaple of a periodic scheduled task to sync changed RDS log files to an S3 bucket. | unknown | | [HTTP Headers Checks](https://github.com/serverless/examples/tree/master/)
Serverless Application to check integrity of the headers of a given HTTP server | unknown | | [Serverless Image Labeller](https://github.com/serverless/examples/tree/master/)
Serverless image labelling using Rekognition, s3, DynamoDB. | unknown | | [Serverless AppSync offline TypeScript with CircleCI](https://github.com/serverless/examples/tree/master/)
A Serverless Framework template that allows you to launch an AppSync emulator locally and proceed with development. Lambda Function build by TypeScript/Webpack. | unknown | | [Serverless Screenshot to S3](https://github.com/serverless/examples/tree/master/)
An example serverless stack which takes a screenshot using aws-chrome-lambda and puts it in s3. NodeJS. | unknown | | [Express Application With Lambda](https://github.com/serverless/examples/tree/master/)
This example demonstrates how to build an express application for AWS Lambda based on serverless framework. | unknown | | [DropBucket - Serverless file sharing](https://github.com/serverless/examples/tree/master/)
A serverless file sharing app powered by Cognito/S3/Lambda/API Gateway. Includes a React single-page app UI and virus scanning. | unknown | | [serverless-pokego](https://github.com/serverless/examples/tree/master/)
Serverless-powered API to fetch nearby Pokemon Go data | unknown | | [serverless-garden-aid](https://github.com/serverless/examples/tree/master/)
IoT Garden Aid Backend | unknown | | [serverless-react-boilerplate](https://github.com/serverless/examples/tree/master/)
A serverless react boilerplate for offline development | unknown | | [serverless-delivery-framework](https://github.com/serverless/examples/tree/master/)
This is a boilerplate for version release pipeline with serverless framework | unknown | | [serverless-mailgun-slack](https://github.com/serverless/examples/tree/master/)
A Serverless function for posting to a Slack Webhook in response to a Mailgun route | unknown | | [serverless-AWS-Rekognition-finpics](https://github.com/serverless/examples/tree/master/)
Use AWS Rekognition to provide a faces search of finpics.com | unknown | | [jrestless-examples](https://github.com/serverless/examples/tree/master/)
JRestless (Java / JAX-RS) examples for API Gateway Functions (plain JAX-RS), Spring, binary data requests/responses, custom authorizers and Cognito User Pool authorizers), SNS Functions) (asynchronous communication between functions) and Service Functions) (synchronous HTTP-like communication between functions - transparent through Feign) | unknown | | [AWS API Gateway Serverless project written in Go](https://github.com/serverless/examples/tree/master/)
A serverless project that contains an API Gateway endpoint powered by a Lambda function written in golang and built using [eawsy/aws-lambda-go-shim](https://github.com/eawsy/aws-lambda-go-shim). | unknown | | [video-preview-and-analysis-service](https://github.com/serverless/examples/tree/master/)
An event-driven service that generates labels using Amazon Rekognition and creates preview GIF animation from a video file. | unknown | | [Serverless ES6/7 CRUD API](https://github.com/serverless/examples/tree/master/)
Serverless Stack examples of backend CRUD APIs (DynamoDB + Lambda + API Gateway + Cognito User Pool authorizer) for React.js single-page app | unknown | | [AWS Lambda Power Tuning (powered by Step Functions)](https://github.com/serverless/examples/tree/master/)
Build a Step Functions state machine to optimize your AWS Lambda Function memory/power configuration. | unknown | | [React & Stripe Serverless Ecommerce](https://github.com/serverless/examples/tree/master/)
Serverless E-Commerce App with AWS Lambda, Stripe and React | unknown | | [Run your Kubernetes Workloads on Amazon EC2 Spot Instances with Amazon EKS and Lambda - Part 2](https://github.com/serverless/examples/tree/master/)
From this article you'll learn how to configure AWS Lambda functions to allow them manage your EKS Kubernetes cluster and run triggered jobs | unknown | | [Serverless Dashboard For Atom Editor](https://github.com/serverless/examples/tree/master/)
Atom editor package which allows you to deploy and visualize your serverless services with Serverless Framework on your editor. | unknown | | [Serverless SSH Command](https://github.com/serverless/examples/tree/master/)
Example of executing ssh command with OpenWhisk | unknown | | [JSON-Serverless](https://github.com/serverless/examples/tree/master/)
A simple & cheap serverless REST API using [json-server](https://github.com/typicode/json-server) in combination with AWS Lambda / S3 and the serverless framework | unknown | | [[Unly] Boilerplates Generator](https://github.com/serverless/examples/tree/master/)
A boilerplates generator, meant to help to quick-start Serverless (AWS Lambda/API GW) and OSS projects, using good defaults _(sentry for automated error handling, staging/prod environments, built-in support for env vars, jest support, babel/webpack)_, yet flexible to fit your needs. | unknown | | [Demo project for serverless-migrate-plugin](https://github.com/serverless/examples/tree/master/)
An example about how to use migrations in your serverless project with serverless-migrate-plugin | unknown | | [GoLive](https://github.com/serverless/examples/tree/master/)
Boilerplate to live stream using AWS MediaLive and MediaStore | unknown | | [Idempotent Serverless Functions](https://github.com/serverless/examples/tree/master/)
This repository demonstrates how to ensure the idempotence of serverless functions running on AWS Lambda. | unknown | | [File uploads using S3 presigned URLs](https://github.com/serverless/examples/tree/master/)
A Serverless photo upload service with API Gateway, S3 presigned URLs and Lambda. | unknown | | [Monorepo Typescript microservices](https://github.com/serverless/examples/tree/master/)
An opinionated Serverless template with several Typescript microservices in a monorepo | unknown | | [Serverless Python Twitch EventSub to Discord Webhook on AWS](https://github.com/serverless/examples/tree/master/)
This template takes go-live events from Twitch EventSub, and publishes the events through a Discord webhook | unknown | ## Community Examples [Add an example](https://github.com/serverless/examples/edit/master/community-examples.json) To install any of these you can run: ```bash serverless install -u https://github.com/author/project -n my-project ``` ## Contributing We are happy to accept more examples from the community. 🎉 ### Adding example code 1. Make sure your contribution matches the linting setup for this repo: Run the linting via ```bash npm run lint ``` 2. Add a `package.json` file in your example with the name of the example and a `description` and any `dependencies` used. 3. Regenerate the README.md with the following command ```bash npm run docs ``` 4. Open a new pull request with your example. ⚡️ ### Adding a community example We love hearing about projects happening in the community. Feel free to add your serverless project to our growing list. 1. Add `link`, `title`, and `description` to the [community-examples.json](https://github.com/serverless/examples/edit/master/community-examples.json) file. 2. Open a new pull request with your example. ⚡️ ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/.gitignore ================================================ git node_modules .serverless *.swp *.*~ project.lock.json .DS_Store *.pyc nupkg/ # Visual Studio Code .vscode # User-specific files *.suo *.user *.userosscache *.sln.docstates # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ [Oo]ut/ msbuild.log msbuild.err msbuild.wrn # Visual Studio 2015 .vs/ env.configs.yml ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/DotNetServerless.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.26124.0 MinimumVisualStudioVersion = 15.0.26124.0 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E45346EE-74B7-4F5B-943C-FEFDE57124D0}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{DBC29D13-84FE-4A90-B785-E325BDD494A8}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetServerless.Lambda", "src\DotNetServerless.Lambda\DotNetServerless.Lambda.csproj", "{9A74B45A-50B6-44C8-A2D7-F7778A61F18F}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetServerless.Tests", "tests\DotNetServerless.Tests\DotNetServerless.Tests.csproj", "{045DC8E9-8CA6-4B76-9C4A-781AB9589700}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetServerless.Application", "src\DotNetServerless.Application\DotNetServerless.Application.csproj", "{8F0BB856-4F83-4898-ACC7-68D672386742}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {9A74B45A-50B6-44C8-A2D7-F7778A61F18F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9A74B45A-50B6-44C8-A2D7-F7778A61F18F}.Debug|Any CPU.Build.0 = Debug|Any CPU {9A74B45A-50B6-44C8-A2D7-F7778A61F18F}.Debug|x64.ActiveCfg = Debug|Any CPU {9A74B45A-50B6-44C8-A2D7-F7778A61F18F}.Debug|x64.Build.0 = Debug|Any CPU {9A74B45A-50B6-44C8-A2D7-F7778A61F18F}.Debug|x86.ActiveCfg = Debug|Any CPU {9A74B45A-50B6-44C8-A2D7-F7778A61F18F}.Debug|x86.Build.0 = Debug|Any CPU {9A74B45A-50B6-44C8-A2D7-F7778A61F18F}.Release|Any CPU.ActiveCfg = Release|Any CPU {9A74B45A-50B6-44C8-A2D7-F7778A61F18F}.Release|Any CPU.Build.0 = Release|Any CPU {9A74B45A-50B6-44C8-A2D7-F7778A61F18F}.Release|x64.ActiveCfg = Release|Any CPU {9A74B45A-50B6-44C8-A2D7-F7778A61F18F}.Release|x64.Build.0 = Release|Any CPU {9A74B45A-50B6-44C8-A2D7-F7778A61F18F}.Release|x86.ActiveCfg = Release|Any CPU {9A74B45A-50B6-44C8-A2D7-F7778A61F18F}.Release|x86.Build.0 = Release|Any CPU {045DC8E9-8CA6-4B76-9C4A-781AB9589700}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {045DC8E9-8CA6-4B76-9C4A-781AB9589700}.Debug|Any CPU.Build.0 = Debug|Any CPU {045DC8E9-8CA6-4B76-9C4A-781AB9589700}.Debug|x64.ActiveCfg = Debug|Any CPU {045DC8E9-8CA6-4B76-9C4A-781AB9589700}.Debug|x64.Build.0 = Debug|Any CPU {045DC8E9-8CA6-4B76-9C4A-781AB9589700}.Debug|x86.ActiveCfg = Debug|Any CPU {045DC8E9-8CA6-4B76-9C4A-781AB9589700}.Debug|x86.Build.0 = Debug|Any CPU {045DC8E9-8CA6-4B76-9C4A-781AB9589700}.Release|Any CPU.ActiveCfg = Release|Any CPU {045DC8E9-8CA6-4B76-9C4A-781AB9589700}.Release|Any CPU.Build.0 = Release|Any CPU {045DC8E9-8CA6-4B76-9C4A-781AB9589700}.Release|x64.ActiveCfg = Release|Any CPU {045DC8E9-8CA6-4B76-9C4A-781AB9589700}.Release|x64.Build.0 = Release|Any CPU {045DC8E9-8CA6-4B76-9C4A-781AB9589700}.Release|x86.ActiveCfg = Release|Any CPU {045DC8E9-8CA6-4B76-9C4A-781AB9589700}.Release|x86.Build.0 = Release|Any CPU {8F0BB856-4F83-4898-ACC7-68D672386742}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8F0BB856-4F83-4898-ACC7-68D672386742}.Debug|Any CPU.Build.0 = Debug|Any CPU {8F0BB856-4F83-4898-ACC7-68D672386742}.Debug|x64.ActiveCfg = Debug|Any CPU {8F0BB856-4F83-4898-ACC7-68D672386742}.Debug|x64.Build.0 = Debug|Any CPU {8F0BB856-4F83-4898-ACC7-68D672386742}.Debug|x86.ActiveCfg = Debug|Any CPU {8F0BB856-4F83-4898-ACC7-68D672386742}.Debug|x86.Build.0 = Debug|Any CPU {8F0BB856-4F83-4898-ACC7-68D672386742}.Release|Any CPU.ActiveCfg = Release|Any CPU {8F0BB856-4F83-4898-ACC7-68D672386742}.Release|Any CPU.Build.0 = Release|Any CPU {8F0BB856-4F83-4898-ACC7-68D672386742}.Release|x64.ActiveCfg = Release|Any CPU {8F0BB856-4F83-4898-ACC7-68D672386742}.Release|x64.Build.0 = Release|Any CPU {8F0BB856-4F83-4898-ACC7-68D672386742}.Release|x86.ActiveCfg = Release|Any CPU {8F0BB856-4F83-4898-ACC7-68D672386742}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {9A74B45A-50B6-44C8-A2D7-F7778A61F18F} = {E45346EE-74B7-4F5B-943C-FEFDE57124D0} {045DC8E9-8CA6-4B76-9C4A-781AB9589700} = {DBC29D13-84FE-4A90-B785-E325BDD494A8} {8F0BB856-4F83-4898-ACC7-68D672386742} = {E45346EE-74B7-4F5B-943C-FEFDE57124D0} EndGlobalSection EndGlobal ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/README.MD ================================================ # DotNetServerless The following AWS lambda is built in .NET Core 2.1 ## Configure lambda It is possible configure the lambda by editing the `env.config.yml` file: ``` feature: version: 1.0.0.0 region: environment: accountId: dynamoTable: ``` ## Run The project contains a `package.json` file with the following commands: ``` npm run tests ``` ``` npm run build ``` ``` npm run deploy ``` ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/src/DotNetServerless.Application/DotNetServerless.Application.csproj ================================================ netstandard2.0 ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/src/DotNetServerless.Application/Entities/Item.cs ================================================ using Amazon.DynamoDBv2.DataModel; namespace DotNetServerless.Application.Entities { public class Item { [DynamoDBHashKey] public string Id { get; set; } [DynamoDBRangeKey] public string Code { get; set; } [DynamoDBProperty] public string Description { get; set; } [DynamoDBProperty] public bool IsChecked { get; set; } } } ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/src/DotNetServerless.Application/Handlers/CreateItemHandler.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using DotNetServerless.Application.Entities; using DotNetServerless.Application.Infrastructure.Repositories; using DotNetServerless.Application.Requests; using MediatR; namespace DotNetServerless.Application.Handlers { public class CreateItemHandler : IRequestHandler { private readonly IItemRepository _itemRepository; public CreateItemHandler(IItemRepository itemRepository) { _itemRepository = itemRepository; } public async Task Handle(CreateItemRequest request, CancellationToken cancellationToken) { var item = request.Map(); item.Id = Guid.NewGuid().ToString(); await _itemRepository.Save(item, cancellationToken); return item; } } } ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/src/DotNetServerless.Application/Handlers/GetItemHandler.cs ================================================ using System.Linq; using System.Threading; using System.Threading.Tasks; using DotNetServerless.Application.Entities; using DotNetServerless.Application.Infrastructure.Repositories; using DotNetServerless.Application.Requests; using MediatR; namespace DotNetServerless.Application.Handlers { public class GetItemHandler : IRequestHandler { private readonly IItemRepository _itemRepository; public GetItemHandler(IItemRepository itemRepository) { _itemRepository = itemRepository; } public async Task Handle(GetItemRequest request, CancellationToken cancellationToken) { var result = await _itemRepository.GetById(request.Id.ToString(), cancellationToken); return result.FirstOrDefault(); } } } ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/src/DotNetServerless.Application/Handlers/UpdateItemHandler.cs ================================================ using System.Threading; using System.Threading.Tasks; using DotNetServerless.Application.Entities; using DotNetServerless.Application.Infrastructure.Repositories; using DotNetServerless.Application.Requests; using MediatR; namespace DotNetServerless.Application.Handlers { public class UpdateItemHandler : IRequestHandler { private readonly IItemRepository _itemRepository; public UpdateItemHandler(IItemRepository itemRepository) { _itemRepository = itemRepository; } public async Task Handle(UpdateItemRequest request, CancellationToken cancellationToken) { var item = request.Map(); await _itemRepository.Save(item, cancellationToken); return item; } } } ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/src/DotNetServerless.Application/Infrastructure/AWSClientFactory.cs ================================================ using System; using Amazon.Runtime; using DotNetServerless.Application.Infrastructure.Configs; namespace DotNetServerless.Application.Infrastructure { public interface IAwsClientFactory { T GetAwsClient(); } public class AwsClientFactory : IAwsClientFactory where T : AmazonServiceClient, new() { private readonly IAwsBasicConfiguration _awsBasicConfiguration; public AwsClientFactory(AwsBasicConfiguration awsBasicConfiguration) { _awsBasicConfiguration = awsBasicConfiguration; } public T GetAwsClient() { return string.IsNullOrEmpty(_awsBasicConfiguration.AccessKey) || string.IsNullOrEmpty(_awsBasicConfiguration.SecretKey) ? (T) Activator.CreateInstance(typeof(T)) : (T) Activator.CreateInstance(typeof(T), _awsBasicConfiguration.GetAwsCredentials(), _awsBasicConfiguration.RegionEndpoint); } } } ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/src/DotNetServerless.Application/Infrastructure/Configs/AwsBasicConfiguration.cs ================================================ using System; using System.Linq; using Amazon; using Amazon.Runtime; using Newtonsoft.Json; namespace DotNetServerless.Application.Infrastructure.Configs { public interface IAwsBasicConfiguration { string SecretKey { get; } string AccessKey { get; } RegionEndpoint RegionEndpoint { get; } BasicAWSCredentials GetAwsCredentials(); } public class AwsBasicConfiguration : IAwsBasicConfiguration { private string Region { get; } = string.Empty; public string SecretKey { get; set; } public string AccessKey { get; set; } public BasicAWSCredentials GetAwsCredentials() { if (string.IsNullOrEmpty(AccessKey) || string.IsNullOrEmpty(SecretKey)) return null; return new BasicAWSCredentials(AccessKey, SecretKey); } [JsonIgnore] public RegionEndpoint RegionEndpoint { get { return RegionEndpoint.EnumerableAllRegions.FirstOrDefault(x => x.SystemName.Equals(Region, StringComparison.InvariantCultureIgnoreCase)); } } } } ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/src/DotNetServerless.Application/Infrastructure/Configs/DynamoDbConfiguration.cs ================================================ namespace DotNetServerless.Application.Infrastructure.Configs { public class DynamoDbConfiguration { public string TableName { get; set; } } } ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/src/DotNetServerless.Application/Infrastructure/Repositories/IItemRepository.cs ================================================ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using DotNetServerless.Application.Entities; namespace DotNetServerless.Application.Infrastructure.Repositories { public interface IItemRepository { Task> GetById(string id, CancellationToken cancellationToken); Task Save(Item item, CancellationToken cancellationToken); } } ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/src/DotNetServerless.Application/Infrastructure/Repositories/ItemDynamoRepository.cs ================================================ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Amazon.DynamoDBv2; using Amazon.DynamoDBv2.DataModel; using Amazon.DynamoDBv2.DocumentModel; using DotNetServerless.Application.Entities; using DotNetServerless.Application.Infrastructure.Configs; namespace DotNetServerless.Application.Infrastructure.Repositories { public class ItemDynamoRepository : IItemRepository { private readonly AmazonDynamoDBClient _client; private readonly DynamoDBOperationConfig _configuration; public ItemDynamoRepository(DynamoDbConfiguration configuration, IAwsClientFactory clientFactory) { _client = clientFactory.GetAwsClient(); _configuration = new DynamoDBOperationConfig { OverrideTableName = configuration.TableName, SkipVersionCheck = true }; } public async Task Save(Item item, CancellationToken cancellationToken) { using (var context = new DynamoDBContext(_client)) { await context.SaveAsync(item, _configuration, cancellationToken); } } public async Task> GetById(string id, CancellationToken cancellationToken) { var resultList = new List(); using (var context = new DynamoDBContext(_client)) { var scanCondition = new ScanCondition(nameof(Item.Id), ScanOperator.Equal, id); var search = context.ScanAsync(new[] { scanCondition }, _configuration); while (!search.IsDone) { var entities = await search.GetNextSetAsync(cancellationToken); resultList.AddRange(entities); } } return resultList; } } } ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/src/DotNetServerless.Application/Requests/CreateItemRequest.cs ================================================ using DotNetServerless.Application.Entities; using MediatR; namespace DotNetServerless.Application.Requests { public class CreateItemRequest : IRequest { public string Description { get; set; } public string Code { get; set; } public bool IsChecked { get; set; } public Item Map() { return new Item { Description = Description, Code = Code, IsChecked = IsChecked }; } } } ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/src/DotNetServerless.Application/Requests/GetItemRequest.cs ================================================ using System; using DotNetServerless.Application.Entities; using MediatR; namespace DotNetServerless.Application.Requests { public class GetItemRequest : IRequest { public Guid Id { get; set; } } } ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/src/DotNetServerless.Application/Requests/UpdateItemRequest.cs ================================================ using DotNetServerless.Application.Entities; using MediatR; namespace DotNetServerless.Application.Requests { public class UpdateItemRequest : IRequest { public string Id { get; set; } public string Description { get; set; } public string Code { get; set; } public bool IsChecked { get; set; } public Item Map() { return new Item { Id = Id, Description = Description, Code = Code, IsChecked = IsChecked }; } } } ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/src/DotNetServerless.Application/Responses/ItemResponse.cs ================================================ using System; namespace DotNetServerless.Application.Responses { public class ItemResponse { public Guid Id { get; set; } public string Code { get; set; } public string Description { get; set; } public bool IsChecked { get; set; } } } ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/src/DotNetServerless.Lambda/DotNetServerless.Lambda.csproj ================================================ netcoreapp2.1 true Lambda PreserveNewest PreserveNewest ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/src/DotNetServerless.Lambda/Extensions/ServicesExtensions.cs ================================================ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace DotNetServerless.Lambda.Extensions { public static class ServiceCollectionExtensions { public static IServiceCollection BindAndConfigure(this IServiceCollection services, IConfigurationSection section, TConfig config) where TConfig : class, new() { section.Bind(config); services.AddSingleton(config); return services; } } } ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/src/DotNetServerless.Lambda/Functions/CreateItemFunction.cs ================================================ using System; using System.Threading.Tasks; using Amazon.Lambda.APIGatewayEvents; using Amazon.Lambda.Core; using DotNetServerless.Application.Requests; using MediatR; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; namespace DotNetServerless.Lambda.Functions { public class CreateItemFunction { private readonly IServiceProvider _serviceProvider; public CreateItemFunction() : this(Startup .BuildContainer() .BuildServiceProvider()) { } public CreateItemFunction(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } [LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))] public async Task Run(APIGatewayProxyRequest request) { var requestModel = JsonConvert.DeserializeObject(request.Body); var mediator = _serviceProvider.GetService(); var result = await mediator.Send(requestModel); return new APIGatewayProxyResponse { StatusCode = 201, Body = JsonConvert.SerializeObject(result)}; } } } ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/src/DotNetServerless.Lambda/Functions/GetItemFunction.cs ================================================ using System; using System.Threading.Tasks; using Amazon.Lambda.APIGatewayEvents; using Amazon.Lambda.Core; using DotNetServerless.Application.Requests; using MediatR; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; namespace DotNetServerless.Lambda.Functions { public class GetItemFunction { private readonly IServiceProvider _serviceProvider; public GetItemFunction() : this(Startup .BuildContainer() .BuildServiceProvider()) { } public GetItemFunction(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } [LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))] public async Task Run(APIGatewayProxyRequest request) { var requestModel = new GetItemRequest {Id = new Guid(request.PathParameters["id"])}; var mediator = _serviceProvider.GetService(); var result = await mediator.Send(requestModel); return result == null ? new APIGatewayProxyResponse {StatusCode = 404} : new APIGatewayProxyResponse { StatusCode = 200, Body = JsonConvert.SerializeObject(result)}; } } } ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/src/DotNetServerless.Lambda/Functions/UpdateItemFunction.cs ================================================ using System; using System.Threading.Tasks; using Amazon.Lambda.APIGatewayEvents; using Amazon.Lambda.Core; using DotNetServerless.Application.Requests; using MediatR; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; namespace DotNetServerless.Lambda.Functions { public class UpdateItemFunction { private readonly IServiceProvider _serviceProvider; public UpdateItemFunction() : this(Startup .BuildContainer() .BuildServiceProvider()) { } public UpdateItemFunction(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } [LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))] public async Task Run(APIGatewayProxyRequest request) { var requestModel = JsonConvert.DeserializeObject(request.Body); var mediator = _serviceProvider.GetService(); var result = await mediator.Send(requestModel); return new APIGatewayProxyResponse { StatusCode = 200, Body = JsonConvert.SerializeObject(result)}; } } } ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/src/DotNetServerless.Lambda/Program.cs ================================================ namespace DotNetServerless.Lambda { public class Program { public static void Main(string[] args) { } } } ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/src/DotNetServerless.Lambda/Startup.cs ================================================ using System.IO; using DotNetServerless.Application.Infrastructure; using DotNetServerless.Application.Infrastructure.Configs; using DotNetServerless.Application.Infrastructure.Repositories; using DotNetServerless.Lambda.Extensions; using MediatR; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace DotNetServerless.Lambda { public class Startup { public static IServiceCollection BuildContainer() { var configuration = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddEnvironmentVariables() .Build(); return ConfigureServices(configuration); } private static IServiceCollection ConfigureServices(IConfigurationRoot configurationRoot) { var services = new ServiceCollection(); services .AddMediatR() .AddTransient(typeof(IAwsClientFactory<>), typeof(AwsClientFactory<>)) .AddTransient() .BindAndConfigure(configurationRoot.GetSection("DynamoDbConfiguration"), new DynamoDbConfiguration()) .BindAndConfigure(configurationRoot.GetSection("AwsBasicConfiguration"), new AwsBasicConfiguration()); return services; } } } ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/src/DotNetServerless.Lambda/package.json ================================================ { "name": "aws-dotnet-rest-api-with-dynamodb", "description": "Reading/Writing operations using .NET Core and DynamoDB", "author": "Samuele Resca ", "version": "1.0.0", "scripts": { "build": "dotnet restore && dotnet lambda package --configuration release --framework netcoreapp2.1 --output-package bin/release/netcoreapp2.1/deploy-package.zip", "test": " dotnet test ../../tests/DotNetServerless.Tests/DotNetServerless.Tests.csproj", "deploy": "npm run build && npm run test && node_modules/.bin/serverless deploy --verbose" }, "devDependencies": { "serverless": "^1.33.2" } } ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/src/DotNetServerless.Lambda/serverless.yml ================================================ service: ${file(env.configs.yml):feature} frameworkVersion: ">=1.6.0 <2.1.0" provider: name: aws stackName: ${file(env.configs.yml):feature}-${file(env.configs.yml):environment} runtime: dotnetcore2.1 region: ${file(env.configs.yml):region} accountId: ${file(env.configs.yml):accountId} environment: DynamoDbConfiguration__TableName: ${file(env.configs.yml):dynamoTable} iamRoleStatements: - Effect: Allow Action: - dynamodb:* Resource: "arn:aws:dynamodb:${self:provider.region}:*:table/${self:provider.environment.DynamoDbConfiguration__TableName}" package: artifact: bin/release/netcoreapp2.1/deploy-package.zip functions: create: handler: DotNetServerless.Lambda::DotNetServerless.Lambda.Functions.CreateItemFunction::Run events: - http: path: items method: post cors: true get: handler: DotNetServerless.Lambda::DotNetServerless.Lambda.Functions.GetItemFunction::Run events: - http: path: items/{id} method: get cors: true update: handler: DotNetServerless.Lambda::DotNetServerless.Lambda.Functions.UpdateItemFunction::Run events: - http: path: items method: put cors: true resources: Resources: ItemsDynamoDbTable: Type: 'AWS::DynamoDB::Table' DeletionPolicy: Retain Properties: AttributeDefinitions: - AttributeName: Id AttributeType: S - AttributeName: Code AttributeType: S KeySchema: - AttributeName: Id KeyType: HASH - AttributeName: Code KeyType: RANGE ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 TableName: ${self:provider.environment.DynamoDbConfiguration__TableName} ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/tests/DotNetServerless.Tests/DotNetServerless.Tests.csproj ================================================ netcoreapp2.1 false ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/tests/DotNetServerless.Tests/Functions/CreateItemFunctionTests.cs ================================================ using System.Threading; using System.Threading.Tasks; using Amazon.Lambda.APIGatewayEvents; using DotNetServerless.Application.Entities; using DotNetServerless.Application.Infrastructure.Repositories; using DotNetServerless.Application.Requests; using DotNetServerless.Lambda; using DotNetServerless.Lambda.Functions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Moq; using Newtonsoft.Json; using Xunit; namespace DotNetServerless.Tests.Functions { public class CreateItemFunctionTests { public CreateItemFunctionTests() { _mockRepository = new Mock(); _mockRepository.Setup(_ => _.Save(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); var serviceCollection = Startup.BuildContainer(); serviceCollection.Replace(new ServiceDescriptor(typeof(IItemRepository), _ => _mockRepository.Object, ServiceLifetime.Transient)); _sut = new CreateItemFunction(serviceCollection.BuildServiceProvider()); } private readonly CreateItemFunction _sut; private readonly Mock _mockRepository; [Fact] public async Task run_should_trigger_mediator_handler_and_repository() { await _sut.Run(new APIGatewayProxyRequest {Body = JsonConvert.SerializeObject(new CreateItemRequest())}); _mockRepository.Verify(_ => _.Save(It.IsAny(), It.IsAny()), Times.Once); } [Theory] [InlineData(201)] public async Task run_should_return_201_created(int statusCode) { var result = await _sut.Run(new APIGatewayProxyRequest {Body = JsonConvert.SerializeObject(new CreateItemRequest())}); Assert.Equal(result.StatusCode, statusCode); } } } ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/tests/DotNetServerless.Tests/Functions/GetItemFunctionTests.cs ================================================ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Amazon.Lambda.APIGatewayEvents; using DotNetServerless.Application.Entities; using DotNetServerless.Application.Infrastructure.Repositories; using DotNetServerless.Lambda; using DotNetServerless.Lambda.Functions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Moq; using Xunit; namespace DotNetServerless.Tests.Functions { public class GetItemFunctionTests { public GetItemFunctionTests() { _mockRepository = new Mock(); _mockRepository.Setup(_ => _.GetById(It.IsAny(), It.IsAny())).ReturnsAsync(new List{ new Item{ Id = Guid.NewGuid().ToString()}}); var serviceCollection = Startup.BuildContainer(); serviceCollection.Replace(new ServiceDescriptor(typeof(IItemRepository), _ => _mockRepository.Object, ServiceLifetime.Transient)); _sut = new GetItemFunction(serviceCollection.BuildServiceProvider()); } private readonly GetItemFunction _sut; private readonly Mock _mockRepository; [Fact] public async Task run_should_trigger_mediator_handler_and_repository() { await _sut.Run(new APIGatewayProxyRequest{ PathParameters = new Dictionary { { "id", Guid.NewGuid().ToString()} }}); _mockRepository.Verify(_ => _.GetById(It.IsAny(), It.IsAny()), Times.Once); } [Theory] [InlineData(200)] public async Task run_should_return_200_when_find_the_record(int statusCode) { var result = await _sut.Run(new APIGatewayProxyRequest{ PathParameters = new Dictionary { { "id", Guid.NewGuid().ToString()} }}); Assert.Equal(result.StatusCode, statusCode); } [Theory] [InlineData(404)] public async Task run_should_return_404_when_NOT_find_the_record(int statusCode) { _mockRepository.Setup(_ => _.GetById(It.IsAny(), It.IsAny())) .ReturnsAsync(new List()); var result = await _sut.Run(new APIGatewayProxyRequest{ PathParameters = new Dictionary { { "id", Guid.NewGuid().ToString()} }}); Assert.Equal(result.StatusCode, statusCode); } } } ================================================ FILE: aws-dotnet-rest-api-with-dynamodb/tests/DotNetServerless.Tests/Functions/UpdateItemFunctionTests.cs ================================================ using System.Threading; using System.Threading.Tasks; using Amazon.DynamoDBv2.Model; using Amazon.Lambda.APIGatewayEvents; using DotNetServerless.Application.Entities; using DotNetServerless.Application.Infrastructure.Repositories; using DotNetServerless.Lambda; using DotNetServerless.Lambda.Functions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Moq; using Newtonsoft.Json; using Xunit; namespace DotNetServerless.Tests.Functions { public class UpdateItemFunctionTests { public UpdateItemFunctionTests() { _mockRepository = new Mock(); _mockRepository.Setup(_ => _.Save(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); var serviceCollection = Startup.BuildContainer(); serviceCollection.Replace(new ServiceDescriptor(typeof(IItemRepository), _ => _mockRepository.Object, ServiceLifetime.Transient)); _sut = new UpdateItemFunction(serviceCollection.BuildServiceProvider()); } private readonly UpdateItemFunction _sut; private readonly Mock _mockRepository; [Fact] public async Task run_should_trigger_mediator_handler_and_repository() { await _sut.Run(new APIGatewayProxyRequest{ Body = JsonConvert.SerializeObject(new UpdateItemRequest())}); _mockRepository.Verify(_ => _.Save(It.IsAny(), It.IsAny()), Times.Once); } [Theory] [InlineData(200)] public async Task run_should_return_200_when_updates(int statusCode) { var result = await _sut.Run(new APIGatewayProxyRequest {Body = JsonConvert.SerializeObject(new UpdateItemRequest())}); Assert.Equal(result.StatusCode, statusCode); } } } ================================================ FILE: aws-ffmpeg-layer/.gitignore ================================================ .serverless/ node_modules/ layer/ ================================================ FILE: aws-ffmpeg-layer/README.md ================================================ # AWS FFmepg Layer & a service using it to create GIFs ``` git clone https://github.com/serverless/examples cd examples/aws-ffmpeg-layer ./build.sh sls deploy ``` See the blog post about this example for more details: https://serverless.com/blog/publish-aws-lambda-layers-serverless-framework/ ================================================ FILE: aws-ffmpeg-layer/build.sh ================================================ #!/bin/bash mkdir -p layer cd layer rm -rf * curl -O https://johnvansickle.com/ffmpeg/builds/ffmpeg-git-amd64-static.tar.xz tar -xf ffmpeg-git-amd64-static.tar.xz mv ffmpeg-git-*-amd64-static ffmpeg rm ffmpeg-git-amd64-static.tar.xz ================================================ FILE: aws-ffmpeg-layer/handler.js ================================================ const { spawnSync } = require("child_process"); const { readFileSync, writeFileSync, unlinkSync } = require("fs"); const AWS = require("aws-sdk"); const s3 = new AWS.S3(); module.exports.mkgif = async (event, context) => { if (!event.Records) { console.log("not an s3 invocation!"); return; } for (const record of event.Records) { if (!record.s3) { console.log("not an s3 invocation!"); continue; } if (record.s3.object.key.endsWith(".gif")) { console.log("already a gif"); continue; } // get the file const s3Object = await s3 .getObject({ Bucket: record.s3.bucket.name, Key: record.s3.object.key }) .promise(); // write file to disk writeFileSync(`/tmp/${record.s3.object.key}`, s3Object.Body); // convert to gif! spawnSync( "/opt/ffmpeg/ffmpeg", [ "-i", `/tmp/${record.s3.object.key}`, "-f", "gif", `/tmp/${record.s3.object.key}.gif` ], { stdio: "inherit" } ); // read gif from disk const gifFile = readFileSync(`/tmp/${record.s3.object.key}.gif`); // delete the temp files unlinkSync(`/tmp/${record.s3.object.key}.gif`); unlinkSync(`/tmp/${record.s3.object.key}`); // upload gif to s3 await s3 .putObject({ Bucket: record.s3.bucket.name, Key: `${record.s3.object.key}.gif`, Body: gifFile }) .promise(); } }; ================================================ FILE: aws-ffmpeg-layer/package.json ================================================ { "name": "aws-lambda-layer", "description": "", "version": "0.1.0", "dependencies": {}, "devDependencies": {} } ================================================ FILE: aws-ffmpeg-layer/serverless.yml ================================================ service: gifmaker frameworkVersion: ">=2.24.0" provider: name: aws runtime: nodejs12.x iamRoleStatements: - Effect: Allow Action: - s3:PutObject - s3:GetObject Resource: "arn:aws:s3:::${self:custom.bucket}/*" functions: mkgif: handler: handler.mkgif events: - s3: ${self:custom.bucket} layers: - {Ref: FfmpegLambdaLayer} layers: ffmpeg: path: layer custom: bucket: ${env:BUCKET, 'ffmpeg-layer-gif-maker'} ================================================ FILE: aws-golang-auth-examples/.gitignore ================================================ bin/** vendor .serverless *.coverprofile ================================================ FILE: aws-golang-auth-examples/Makefile ================================================ help: @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' functions := $(shell find functions -name \*main.go | awk -F'/' '{print $$2}') build: ## Build golang binaries @for function in $(functions) ; do \ env GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o bin/$$function functions/$$function/main.go ; \ done ================================================ FILE: aws-golang-auth-examples/README.md ================================================ # Go Serverless Examples A few example of AWS lambda functions written in GoLang. Functions: - `hello-world`: Exactly what is says on the tin. Listening on a `/hello` path. - `auth`: An AWS API Gateway custom authorizer that sits in front of `hello-world`. It expects an auth bearer of `hello` as a header and is on the base `/` path. The auth header should be `Authorization: bearer hello` - `auth2` and `hello-world2`: The same as `auth` above except using auth contexts. Any name can be used as a bearer token, for example `Authorization: bearer Bob`. The response will then return `Hello, Bob!` I hope to add to these examples over time, if you have ideas please feel free to raise issues or pull requests. For more info on these example check out the [blog post](https://cloudnative.ly/lambdas-with-golang-a-technical-guide-6f381284897b) ================================================ FILE: aws-golang-auth-examples/functions/auth/auth_suite_test.go ================================================ package main_test import ( "testing" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) func TestAuth(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Auth Suite") } ================================================ FILE: aws-golang-auth-examples/functions/auth/main.go ================================================ package main import ( "errors" "strings" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" ) func handler(request events.APIGatewayCustomAuthorizerRequest) (events.APIGatewayCustomAuthorizerResponse, error) { token := request.AuthorizationToken tokenSlice := strings.Split(token, " ") var bearerToken string if len(tokenSlice) > 1 { bearerToken = tokenSlice[len(tokenSlice)-1] } if bearerToken != "hello" { return events.APIGatewayCustomAuthorizerResponse{}, errors.New("Unauthorized") } return generatePolicy("user", "Allow", request.MethodArn), nil } func main() { lambda.Start(handler) } func generatePolicy(principalID, effect, resource string) events.APIGatewayCustomAuthorizerResponse { authResponse := events.APIGatewayCustomAuthorizerResponse{PrincipalID: principalID} if effect != "" && resource != "" { authResponse.PolicyDocument = events.APIGatewayCustomAuthorizerPolicy{ Version: "2012-10-17", Statement: []events.IAMPolicyStatement{ { Action: []string{"execute-api:Invoke"}, Effect: effect, Resource: []string{resource}, }, }, } } return authResponse } ================================================ FILE: aws-golang-auth-examples/functions/auth/main_test.go ================================================ package main import ( "github.com/aws/aws-lambda-go/events" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) var _ = Describe("the auth function", func() { var ( response events.APIGatewayCustomAuthorizerResponse request events.APIGatewayCustomAuthorizerRequest err error ) JustBeforeEach(func() { response, err = handler(request) }) AfterEach(func() { request = events.APIGatewayCustomAuthorizerRequest{} response = events.APIGatewayCustomAuthorizerResponse{} }) Context("When the auth bearer is not set", func() { It("Fails auth", func() { Expect(err).To(MatchError("Unauthorized")) Expect(response).To(Equal(events.APIGatewayCustomAuthorizerResponse{})) }) }) Context("When the auth bearer is set", func() { Context("and auth fails", func() { BeforeEach(func() { request = events.APIGatewayCustomAuthorizerRequest{ AuthorizationToken: "bearer token", MethodArn: "testARN", } }) It("Fails auth", func() { Expect(err).To(MatchError("Unauthorized")) Expect(response).To(Equal(events.APIGatewayCustomAuthorizerResponse{})) }) }) Context("and auth succeeds", func() { BeforeEach(func() { request = events.APIGatewayCustomAuthorizerRequest{ AuthorizationToken: "bearer hello", MethodArn: "testARN", } }) It("authorizes", func() { Expect(err).To(BeNil()) Expect(response.PolicyDocument.Version).To(Equal("2012-10-17")) Expect(response.PolicyDocument.Statement).To(Equal([]events.IAMPolicyStatement{ { Action: []string{"execute-api:Invoke"}, Effect: "Allow", Resource: []string{"testARN"}, }, })) }) }) }) }) ================================================ FILE: aws-golang-auth-examples/functions/auth2/auth_suite_test.go ================================================ package main_test import ( "testing" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) func TestAuth(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Auth Suite") } ================================================ FILE: aws-golang-auth-examples/functions/auth2/main.go ================================================ package main import ( "errors" "strings" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" ) func handler(request events.APIGatewayCustomAuthorizerRequest) (events.APIGatewayCustomAuthorizerResponse, error) { token := request.AuthorizationToken tokenSlice := strings.Split(token, " ") var bearerToken string if len(tokenSlice) > 1 { bearerToken = tokenSlice[len(tokenSlice)-1] } if bearerToken == "" { return events.APIGatewayCustomAuthorizerResponse{}, errors.New("Unauthorized") } return generatePolicy("user", "Allow", request.MethodArn, map[string]interface{}{"name": bearerToken}), nil } func main() { lambda.Start(handler) } func generatePolicy(principalID, effect, resource string, context map[string]interface{}) events.APIGatewayCustomAuthorizerResponse { authResponse := events.APIGatewayCustomAuthorizerResponse{PrincipalID: principalID} if effect != "" && resource != "" { authResponse.PolicyDocument = events.APIGatewayCustomAuthorizerPolicy{ Version: "2012-10-17", Statement: []events.IAMPolicyStatement{ { Action: []string{"execute-api:Invoke"}, Effect: effect, Resource: []string{resource}, }, }, } } authResponse.Context = context return authResponse } ================================================ FILE: aws-golang-auth-examples/functions/auth2/main_test.go ================================================ package main import ( "github.com/aws/aws-lambda-go/events" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) var _ = Describe("the auth function", func() { var ( response events.APIGatewayCustomAuthorizerResponse request events.APIGatewayCustomAuthorizerRequest err error ) JustBeforeEach(func() { response, err = handler(request) }) AfterEach(func() { request = events.APIGatewayCustomAuthorizerRequest{} response = events.APIGatewayCustomAuthorizerResponse{} }) Context("When the auth bearer is not set", func() { It("Fails auth", func() { Expect(err).To(MatchError("Unauthorized")) Expect(response).To(Equal(events.APIGatewayCustomAuthorizerResponse{})) }) }) Context("When the auth bearer is set", func() { Context("and auth succeeds", func() { BeforeEach(func() { request = events.APIGatewayCustomAuthorizerRequest{ AuthorizationToken: "bearer Bob", MethodArn: "testARN", } }) It("authorizes", func() { Expect(err).To(BeNil()) Expect(response.PolicyDocument.Version).To(Equal("2012-10-17")) Expect(response.PolicyDocument.Statement).To(Equal([]events.IAMPolicyStatement{ { Action: []string{"execute-api:Invoke"}, Effect: "Allow", Resource: []string{"testARN"}, }, })) Expect(response.Context["name"]).To(Equal("Bob")) }) }) }) }) ================================================ FILE: aws-golang-auth-examples/functions/hello-world/hello_world_suite_test.go ================================================ package main_test import ( "testing" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) func TestHelloWorld(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "HelloWorld Suite") } ================================================ FILE: aws-golang-auth-examples/functions/hello-world/main.go ================================================ package main import ( "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" ) func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { return events.APIGatewayProxyResponse{ Body: "Hello, World!", StatusCode: 200, }, nil } func main() { lambda.Start(handler) } ================================================ FILE: aws-golang-auth-examples/functions/hello-world/main_test.go ================================================ package main import ( "net/http" "github.com/aws/aws-lambda-go/events" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) var _ = Describe("Hello world", func() { var ( response events.APIGatewayProxyResponse request events.APIGatewayProxyRequest err error ) JustBeforeEach(func() { response, err = handler(request) Expect(err).To(BeNil()) }) It(`Returns "Hello, World!" and an OK status`, func() { Expect(response.Body).To(Equal("Hello, World!")) Expect(response.StatusCode).To(Equal(http.StatusOK)) }) }) ================================================ FILE: aws-golang-auth-examples/functions/hello-world2/hello_world_suite_test.go ================================================ package main_test import ( "testing" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) func TestHelloWorld(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "HelloWorld Suite") } ================================================ FILE: aws-golang-auth-examples/functions/hello-world2/main.go ================================================ package main import ( "fmt" "net/http" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" ) func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { var name string if _, ok := request.RequestContext.Authorizer["name"]; ok { name = request.RequestContext.Authorizer["name"].(string) } else { return events.APIGatewayProxyResponse{ Body: "Unauthorized: Must be an authorized user with a name", StatusCode: http.StatusUnauthorized, }, nil } return events.APIGatewayProxyResponse{ Body: fmt.Sprintf("Hello, %s!", name), StatusCode: http.StatusOK, }, nil } func main() { lambda.Start(handler) } ================================================ FILE: aws-golang-auth-examples/functions/hello-world2/main_test.go ================================================ package main import ( "net/http" "github.com/aws/aws-lambda-go/events" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) var _ = Describe("Hello world", func() { var ( response events.APIGatewayProxyResponse request events.APIGatewayProxyRequest err error ) JustBeforeEach(func() { response, err = handler(request) Expect(err).To(BeNil()) }) AfterEach(func() { request.RequestContext = events.APIGatewayProxyRequestContext{} }) Context("When the user is not authorized with a name", func() { It(`Returns unauthorized`, func() { Expect(response.Body).To(Equal("Unauthorized: Must be an authorized user with a name")) Expect(response.StatusCode).To(Equal(http.StatusUnauthorized)) }) }) Context("When the user is authorized with a name", func() { BeforeEach(func() { request.RequestContext.Authorizer = map[string]interface{}{ "name": "Bob", } }) It(`Returns "Hello, World!" and an OK status`, func() { Expect(response.Body).To(Equal("Hello, Bob!")) Expect(response.StatusCode).To(Equal(http.StatusOK)) }) }) }) ================================================ FILE: aws-golang-auth-examples/go.mod ================================================ module github.com/srbry/go-serverless-example require ( github.com/aws/aws-lambda-go v1.6.0 github.com/davecgh/go-spew v1.1.1 // indirect github.com/onsi/ginkgo v1.6.0 github.com/onsi/gomega v1.4.2 github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/testify v1.2.2 // indirect golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3 // indirect golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611 // indirect ) ================================================ FILE: aws-golang-auth-examples/go.sum ================================================ github.com/aws/aws-lambda-go v1.6.0 h1:T+u/g79zPKw1oJM7xYhvpq7i4Sjc0iVsXZUaqRVVSOg= github.com/aws/aws-lambda-go v1.6.0/go.mod h1:zUsUQhAUjYzR8AuduJPCfhBuKWUaDbQiPOG+ouzmE1A= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.2 h1:3mYCb7aPxS/RU7TI1y4rkEn1oKmPRjNJLNEXgw7MH2I= github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3 h1:dgd4x4kJt7G4k4m93AYLzM8Ni6h2qLTfh9n9vXJT3/0= golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611 h1:O33LKL7WyJgjN9CvxfTIomjIClbd/Kq86/iipowHQU0= golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= ================================================ FILE: aws-golang-auth-examples/package.json ================================================ { "name": "aws-golang-auth-examples", "version": "1.0.0", "description": "These example shows how to run a Golang lambda with authentication", "main": "", "keywords": [ "Go", "serverless", "aws", "dynamodb", "auth", "tdd", "test", "testing" ], "author": "Sam Bryant ", "license": "MIT", "devDependencies": { "serverless": "^1.30.3" } } ================================================ FILE: aws-golang-auth-examples/serverless.yml ================================================ service: hello-world provider: name: aws runtime: go1.x region: eu-west-1 memorySize: 128 package: exclude: - ./** include: - ./bin/** functions: authorize: handler: bin/auth authorize2: handler: bin/auth2 hello-world: handler: bin/hello-world events: - http: path: /hello method: get - http: path: / method: get authorizer: name: authorize resultTtlInSeconds: 0 hello-world2: handler: bin/hello-world2 events: - http: path: me method: get authorizer: name: authorize2 resultTtlInSeconds: 0 ================================================ FILE: aws-golang-dynamo-stream-to-elasticsearch/Gopkg.toml ================================================ # Gopkg.toml example # # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md # for detailed Gopkg.toml documentation. # # required = ["github.com/user/thing/cmd/thing"] # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] # # [[constraint]] # name = "github.com/user/project" # version = "1.0.0" # # [[constraint]] # name = "github.com/user/project2" # branch = "dev" # source = "github.com/myfork/project2" # # [[override]] # name = "github.com/x/y" # version = "2.4.0" # # [prune] # non-go = false # go-tests = true # unused-packages = true [[constraint]] name = "github.com/aws/aws-lambda-go" version = "1.4.0" [[constraint]] name = "github.com/aws/aws-sdk-go" version = "1.15.13" [[constraint]] name = "github.com/olivere/elastic" version = "6.1.27" [[constraint]] name = "github.com/satori/go.uuid" version = "1.2.0" [prune] go-tests = true unused-packages = true ================================================ FILE: aws-golang-dynamo-stream-to-elasticsearch/Makefile ================================================ build: dep ensure -v env GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o bin/aws-golang-dynamo-stream-to-elasticsearch cmd/aws-golang-dynamo-stream-to-elasticsearch/main.go .PHONY: clean clean: rm -rf ./bin ./vendor Gopkg.lock .PHONY: deploy deploy: clean build sls deploy --verbose ================================================ FILE: aws-golang-dynamo-stream-to-elasticsearch/README.md ================================================ # DynamoDB Stream To Elasticsearch ## Deploying cloud information costs $$, Elasticsearch is not part of the free tier, as such please deploy with caution. This serverless project acts as an example for: * Creating a DynamoDB Table via Cloudformation * Creating a single-node Elasticsearch Cluster via Cloudformation * Creating a generic Go function which maps the keyspace from DynamoDB to Elasticsearch As with all serverless projects, you must have severless installed! Listed here is a good way to get set up! [https://github.com/serverless/serverless#quick-start](https://github.com/serverless/serverless#quick-start) Once you have serverless installed on your system run these commands to get the project set up. ``` cd aws-golang-dynamo-stream-to-elasticsearch npm install make ./node_modules/serverless/bin/serverless deploy # or if you've installed serverless globally # sls deploy ``` This particular example will take ~15 minutes to deploy (Elasticsearch takes some time). Grab a coffee and sit back! 1 1 In production the deployment of persistent data stores (dynamodb, rds variants, elasticsearch) should be decoupled from application code ## Seeding Your DynamoDB Table with Data ``` go run cmd/seed-dynamo/main.go --table-name="$YOUR_TABLE_NAME" ``` Once data is written to Dynamo, your lambda function will trigger the DynamoDB Stream events and data should begin to flow into Elasticsearch. You should be able to create a Kibana index by navigating to your Kibana endpoint (found in the AWS Console) and clicking on the management tab. You should see something like this: ![kibana](docs/kibana.png) Follow the instructions to create the index, and you should now be able to query your data like so: ![query](docs/query.png) ## Access Policy for Kibana The Example itself is running without any further Policy definition, since the Lambda execution role has access to the ES endpoint. However, if you want to access Kibana with your Browser - there is an additonal definition needed. Possible restrictions can be done with aws cognito user auth, aws user auth, IP based or open access (not recommended): e.g. with ip restriction: ``` AccessPolicies: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: AWS: "*" Action: "es:*" Resource: "*" Condition: IpAddress: aws:sourceIp: - "123.123.123.123" ``` ================================================ FILE: aws-golang-dynamo-stream-to-elasticsearch/cmd/aws-golang-dynamo-stream-to-elasticsearch/main.go ================================================ package main import ( "fmt" "os" "strings" "github.com/olivere/elastic" "github.com/serverless/examples/aws-golang-dynamo-stream-to-elasticsearch/dstream" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/dynamodb" ) var awsSession = session.Must(session.NewSession(&aws.Config{})) var dynamoSvc = dynamodb.New(awsSession) var esclient = new(dstream.Elasticsearch) func handler(e events.DynamoDBEvent) error { var item map[string]events.DynamoDBAttributeValue fmt.Println("Beginning ES Sync") for _, v := range e.Records { switch v.EventName { case "INSERT": fallthrough case "MODIFY": tableName := strings.Split(v.EventSourceArn, "/")[1] item = v.Change.NewImage details, err := (&dstream.DynamoDetails{ DynamoDBAPI: dynamoSvc, }).Get(tableName) if err != nil { return err } svc, err := elastic.NewClient( elastic.SetSniff(false), elastic.SetURL(fmt.Sprintf("https://%s", os.Getenv("ELASTICSEARCH_URL"))), ) if err != nil { return err } esclient.Client = svc resp, err := esclient.Update(details, item) if err != nil { return err } fmt.Println(resp.Result) default: } } return nil } func main() { lambda.Start(handler) } ================================================ FILE: aws-golang-dynamo-stream-to-elasticsearch/cmd/seed-dynamo/main.go ================================================ package main import ( "flag" "fmt" "math/rand" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/dynamodb" "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" ) func init() { rand.Seed(time.Now().UTC().UnixNano()) } type puppy struct { ID string `json:"id"` Name string `json:"name"` Age float64 `json:"age"` Weight string `json:"weight"` PrimaryColor string `json:"primaryColor"` SecondaryColor string `json:"secondaryColor"` Owner string `json:"owner"` Location string `json:"location"` Motto string `json:"motto"` Breed string `json:"breed"` } func main() { tableName := flag.String("table-name", "", "Table Name is the name of the table to seed with data") flag.Parse() if tableName == nil { panic("no table name given") } var ( N = 100 p *puppy puppyMaxIndex = len(puppyNames) humanMaxIndex = len(humanNames) colorMaxIndex = len(colors) locationMaxIndex = len(locations) breedMaxIndex = len(breeds) maxWeight = 40 maxAge = 20 ) session := session.Must(session.NewSession(&aws.Config{Region: aws.String("us-east-1")})) svc := dynamodb.New(session) for N > 0 { p = &puppy{ Name: puppyNames[rand.Intn(puppyMaxIndex)], PrimaryColor: colors[rand.Intn(colorMaxIndex)], SecondaryColor: colors[rand.Intn(colorMaxIndex)], Owner: humanNames[rand.Intn(humanMaxIndex)], Location: locations[rand.Intn(locationMaxIndex)], Weight: fmt.Sprintf("%dlb", rand.Intn(maxWeight)+2), Motto: "Woof Woof!", Age: float64(rand.Intn(maxAge)), Breed: breeds[rand.Intn(breedMaxIndex)], } p.ID = uuid.NewV4().String() avm, err := dynamodbattribute.MarshalMap(p) if err != nil { fmt.Println(err.Error()) } else { _, err := svc.PutItem(&dynamodb.PutItemInput{ Item: avm, TableName: aws.String("puppies"), }) if err != nil { fmt.Println(err.Error()) } } N-- } } var colors = []string{"white", "black", "red", "golden", "black", "brown"} var humanNames = []string{"Hannelore Croswell", "Grazyna Keep", "Edison Sterrett", "Darby Paxton", "Meagan Tope", "Alonso Bourassa", "Malika Province", "Penni Heeren", "Pamula Nealey", "Janette Vanderwal", "Kourtney Backman", "Thea Cathcart", "Annie Vanscyoc", "Alene Hillman", "Wen Ardis", "Candance Bohlen", "Elidia Delosreyes", "Octavio Byford", "Donetta Lake", "Eufemia Kwak", "Treena Bona", "Lennie Crowl", "Tomasa Bonnie", "Aisha Hensel", "Shakia Releford", "Artie Eubanks", "Celsa Xie", "Camie Santore", "Fransisca Nourse", "Palmer Czapla", "Eneida Thor", "Antony Wolpert", "Pandora Binns", "Nickie Foti", "Ty Hasbrouck", "Alexandria Varnadoe", "Tim Amaro", "Arnoldo Daugherty", "Gwyn Schutz", "Kenneth Nowlen", "Isadora Plotkin", "Daniele Chenault", "Megan Hammons", "Pennie Shilling", "Holly Capers", "Paige Swinton", "Brianne Mean", "Bonita Braud", "Hassie Jarrells", "Randa Schmid"} var puppyNames = []string{"Max", "Bella", "Charlie", "Lucy", "Cooper", "Daisy", "Buddy", "Luna", "Jack", "Lola", "Rocky", "Sadie", "Bear", "Molly", "Tucker", "Bailey", "Oliver", "Maggie", "Duke", "Sophie", "Toby", "Chloe", "Bentley", "Stella", "Milo", "Lily", "Teddy", "Penny", "Leo", "Zoey", "Winston", "Coco", "Jax", "Roxy", "Zeus", "Gracie", "Louie", "Mia", "Murphy", "Nala", "Jake", "Ruby", "Dexter", "Rosie", "Riley", "Ellie", "Gus", "Abby", "Buster", "Zoe", "Harley", "Piper", "Bailey", "Ginger", "Jackson", "Lilly", "Henry", "Lulu", "Ollie", "Riley", "Oscar", "Sasha", "Finn", "Lexi", "Lucky", "Pepper", "Moose", "Emma", "Hank", "Layla", "Baxter", "Maya", "Bruno", "Izzy", "Diesel", "Lady", "Loki", "Annie", "Sam", "Olive", "Cody", "Harley", "Beau", "Belle", "Bandit", "Dixie", "Blue", "Millie", "Jasper", "Willow", "Apollo", "Princess", "Ace", "Charlie", "Sammy", "Maddie", "Thor", "Kona", "Gunner", "Cali", "Gizmo", "Ella", "Koda", "Winnie", "Shadow", "Roxie", "Scout", "Marley", "Brody", "Cookie", "Bo", "Hazel", "Marley", "Scout", "Simba", "Athena", "Roscoe", "Callie", "Otis", "Phoebe", "Rocco", "Honey", "Rex", "Angel", "George", "Dakota", "Hunter", "Minnie", "Tank", "Holly", "Luke", "Missy", "Ziggy", "Sugar", "Maverick", "Shelby", "Rusty", "Nova", "Boomer", "Leia", "Romeo", "Josie", "Tyson", "Penelope", "Chance", "Ava", "Benny", "Gigi", "Ranger", "Peanut", "Prince", "Fiona", "Oreo", "Cleo", "Bruce", "Jasmine", "Copper", "Sandy", "Benji", "Mocha", "Joey", "Harper", "Rudy", "Macy", "Samson", "Sydney", "Cash", "Paisley", "Peanut", "Lacey", "Frankie", "Bonnie", "Kobe", "Baby", "Coco", "Mila", "Chewy", "Delilah", "Chico", "Pearl", "Chase", "Charlotte", "Zeke", "Trixie", "King", "Ivy", "Chester", "Nina", "Odin", "Heidi", "Walter", "Georgia", "Brady", "Shadow", "Brutus", "Xena", "Mickey", "Allie", "Mac", "Oreo"} var locations = []string{"Alabama", "Alaska", "Arizona", "Arkansas", "California", "Colorado", "Connecticut", "Delaware", "Florida", "Georgia", "Hawaii", "Idaho", "Illinois", "Indiana", "Iowa", "Kansas", "Kentucky", "Louisiana", "Maine", "Maryland", "Massachusetts", "Michigan", "Minnesota", "Mississippi", "Missouri", "Montana", "Nebraska", "Nevada", "New Hampshire", "New Jersey", "New Mexico", "New York", "North Carolina", "North Dakota", "Ohio", "Oklahoma", "Oregon", "Pennsylvania", "Rhode Island", "South Carolina", "South Dakota", "Tennessee", "Texas", "Utah", "Vermont", "Virginia", "Washington", "West Virginia", "Wisconsin", "Wyoming"} var breeds = []string{"Affenpinscher", "Afghan Hound", "Airedale Terrier", "Alaskan Malamute", "American Staffordshire Bull Terrier", "Anatolian Shepherd Dog", "Australian Cattle Dog", "Australian Kelpie", "Australian Shepherd Dog", "Australian Silky Terrier", "Australian Terrier", "Basenji", "Basset Fauve de Bretagne", "Basset Hound", "Beagle", "Bearded Collie", "Bedlington Terrier", "Belgian Shepherd Dog Groenendael", "Belgian Shepherd Dog Laekenois", "Belgian Shepherd Dog Malinois", "Belgian Shepherd Dog Tervueren", "Bernese Mountain Dog", "Bichon Frise", "Bloodhound", "Border Collie", "Border Terrier", "Borzoi", "Boston Terrier", "Bouvier des Flandres", "Boxer", "Bracco Italiano", "Briard", "Brittany", "Bull Terrier", "Bull Terrier Miniature", "Bulldog", "Bullmastiff", "Cairn Terrier", "Cavalier King Charles Spaniel", "Cesky Terrier", "Chesapeake Bay Retriever", "Chihuahua (Smooth Coat)", "Chinese Crested", "Chow Chow (Smooth)", "Clumber Spaniel", "Collie (Rough)", "Collie (Smooth)", "Curly-Coated Retriever", "Dachshund (Miniature Long Haired)", "Dachshund (Miniature Smooth Haired)", "Dachshund (Miniature Wire Haired)", "Dachshund (Smooth Haired)", "Dachshund (Wire Haired)", "Dalmatian", "Dandie Dinmont Terrier", "Deerhound", "Dobermann", "Dogue de Bordeaux", "English Setter", "English Springer Spaniel", "English Toy Terrier (Black & Tan)", "Field Spaniel", "Finnish Lapphund", "Finnish Spitz", "Flat-Coated Retriever", "Fox Terrier Smooth Coat", "Fox Terrier Wire Coat", "Foxhound", "French Bulldog", "German Shepherd Dog", "German Short-Haired Pointer", "German Spitz Klein", "German Wire-Haired Pointer", "Golden Retriever", "Gordon Setter", "Great Dane", "Greyhound", "Harrier Hound", "Hungarian Vizsla", "Hungarian Wire-Haired Vizsla", "Ibizan Hound", "Irish Setter", "Irish Terrier", "Irish Water Spaniel", "Irish Wolfhound", "Italian Greyhound", "Japanese Akita", "Japanese Chin", "Japanese Spitz", "Keeshond", "Kerry Blue Terrier", "King Charles Spaniel", "Labrador Retriever", "Lakeland Terrier", "Leonberger", "Lhaso Apso", "Lowchen", "Maltese", "Manchester Terrier", "Maremma Sheepdog", "Mastiff", "Newfoundland", "Norfolk Terrier", "Norwich Terrier", "Nova Scotia Duck Tolling Retriever", "Old English Sheepdog", "Papillon", "Parson Jack Russell Terrier", "Pharaoh Hound", "Pointer", "Pomeranian", "Poodle Miniature", "Poodle Standard", "Poodle Toy", "Portuguese Water Dog", "Pug", "Pyrenean Mountain Dog", "Rhodesian Ridgeback", "Rottweiler", "Saluki", "Samoyed", "Schipperke", "Schnauzer Giant", "Schnauzer Miniature", "Schnauzer Standard", "Scottish Terrier", "Shar Pei", "Shetland Sheepdog", "Shih Tzu", "Siberian Husky", "Skye Terrier", "Sloughi", "Soft Coated Wheaten Terrier", "St Bernard", "Sussex Spaniel", "Swedish Vallhund", "Tenterfield Terrier", "Tibetan Mastiff", "Tibetan Spaniel", "Tibetan Terrier", "Weimaraner", "Welsh Corgi (Cardigan)", "Welsh Corgi (Pembroke)", "Welsh Springer Spaniel", "Welsh Terrier", "West Highland White Terrier", "Whippet", "Yorkshire Terrier", } ================================================ FILE: aws-golang-dynamo-stream-to-elasticsearch/dstream/details.go ================================================ package dstream import ( "errors" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/dynamodb" "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbiface" ) // DynamoDetails is a wrapper around the DynamoDBAPI interface, // it defines behavior for accessing DynamoDB Table metadata type DynamoDetails struct { dynamodbiface.DynamoDBAPI } // Details is the data needed to index the most recent dataoff to elasticsearch type Details struct { HashKey, RangeKey, TableName string } // Get Extracts out the attribute Value of Hash Key and Range key from the describe table output func (d *DynamoDetails) Get(tableName string) (details *Details, err error) { var out *dynamodb.DescribeTableOutput req, out := d.DescribeTableRequest(&dynamodb.DescribeTableInput{ TableName: aws.String(tableName), }) if err = req.Send(); err != nil { return nil, err } // We NEED a hash key to uniquely identify records hashKey := findAttributeByKeyType(out.Table.KeySchema, "HASH") if hashKey == "" { return nil, errors.New("Hash Key not found") } // range keys are nice but we don't necessarily need one to uniquely identify a Dynamo Record var rangeKey string r := findAttributeByKeyType(out.Table.KeySchema, "RANGE") rangeKey = r return &Details{ TableName: tableName, HashKey: hashKey, RangeKey: rangeKey, }, nil } func findAttributeByKeyType(schema []*dynamodb.KeySchemaElement, keyType string) string { var t *string for _, element := range schema { t = element.KeyType if t == &keyType { return *element.AttributeName } } return "" } ================================================ FILE: aws-golang-dynamo-stream-to-elasticsearch/dstream/update.go ================================================ package dstream import ( "context" "fmt" "strings" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-sdk-go/service/dynamodb" "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" es "github.com/olivere/elastic" ) // Elasticsearch is an ES Client which will perform Elasticsearch Updates for Dynamo Items type Elasticsearch struct { *es.Client } // Update takes a reference to adstream.Details object; // which is used to figure out which Elasticsearch Index to update; // And an item map[string]events.DynamoDBAttributeValue which will be turned into JSON // then indexed into Elasticsearch func (e *Elasticsearch) Update(d *Details, item map[string]events.DynamoDBAttributeValue) (*es.IndexResponse, error) { tmp := eventStreamToMap(item) var i interface{} if err := dynamodbattribute.UnmarshalMap(tmp, &i); err != nil { return nil, err } resp, err := e.Index(). Id(d.docID(item)). Type(d.docType()). Index(d.index()). BodyJson(i). Do(context.Background()) if err != nil { return nil, err } return resp, nil } func (d *Details) docType() string { if d.RangeKey != "" { return fmt.Sprintf("%s-%s", d.HashKey, d.RangeKey) } return d.HashKey } func (d *Details) docID(item map[string]events.DynamoDBAttributeValue) (id string) { if d != nil { if d.RangeKey != "" { id = fmt.Sprintf("%s-%s", item[d.HashKey].String(), item[d.RangeKey].String()) } else { id = item[d.HashKey].String() } } return id } func (d *Details) index() string { return strings.ToLower(d.TableName) } // ugly hack because the types // events.DynamoDBAttributeValue != *dynamodb.AttributeValue func eventStreamToMap(attribute interface{}) map[string]*dynamodb.AttributeValue { // Map to be returned m := make(map[string]*dynamodb.AttributeValue) tmp := make(map[string]events.DynamoDBAttributeValue) switch t := attribute.(type) { case map[string]events.DynamoDBAttributeValue: tmp = t case events.DynamoDBAttributeValue: tmp = t.Map() } for k, v := range tmp { switch v.DataType() { case events.DataTypeString: s := v.String() m[k] = &dynamodb.AttributeValue{ S: &s, } case events.DataTypeBoolean: b := v.Boolean() m[k] = &dynamodb.AttributeValue{ BOOL: &b, } case events.DataTypeMap: m[k] = &dynamodb.AttributeValue{ M: eventStreamToMap(v), } case events.DataTypeNumber: n := v.Number() m[k] = &dynamodb.AttributeValue{ N: &n, } case events.DataTypeList: m[k] = &dynamodb.AttributeValue{ L: eventStreamToList(v), } } } return m } // ugly hack because the types // events.DynamoDBAttributeValue != *dynamodb.AttributeValue func eventStreamToList(attribute interface{}) []*dynamodb.AttributeValue { // List to be returned l := make([]*dynamodb.AttributeValue, 0) var tmp []events.DynamoDBAttributeValue switch t := attribute.(type) { case []events.DynamoDBAttributeValue: tmp = t case events.DynamoDBAttributeValue: tmp = t.List() } for _, v := range tmp { switch v.DataType() { case events.DataTypeString: s := v.String() l = append(l, &dynamodb.AttributeValue{ S: &s, }) case events.DataTypeBoolean: b := v.Boolean() l = append(l, &dynamodb.AttributeValue{ BOOL: &b, }) case events.DataTypeMap: l = append(l, &dynamodb.AttributeValue{ M: eventStreamToMap(v), }) case events.DataTypeNumber: n := v.Number() l = append(l, &dynamodb.AttributeValue{ N: &n, }) case events.DataTypeList: l = append(l, &dynamodb.AttributeValue{ L: eventStreamToList(v), }) } } return l } ================================================ FILE: aws-golang-dynamo-stream-to-elasticsearch/package.json ================================================ { "name": "aws-golang-dynamo-stream-to-elasticsearch", "version": "1.0.0", "description": "This example deploys a DynamoDB Table, an Elasticsearch Node, and a lambda triggered off of a Dynamo Stream which updates an elasticsearch index with the data from the Dynamo Table", "main": "", "keywords": ["Go","serverless","aws","dynamodb","elasticsearch"], "author": "Brian Jones ", "license": "MIT", "devDependencies": { "serverless": "^1.30.3" } } ================================================ FILE: aws-golang-dynamo-stream-to-elasticsearch/serverless.yml ================================================ service: aws-golang-dynamo-stream-to-elasticsearch provider: name: aws runtime: go1.x environment: ELASTICSEARCH_URL: Fn::GetAtt: ["PuppySearch", "DomainEndpoint"] iam: role: statements: - Effect: Allow Action: - 'dynamodb:ListTables' - 'dynamodb:DescribeTable' - 'dynamodb:DescribeStream' - 'dynamodb:ListStreams' - 'dynamodb:GetShardIterator' - 'dynamodb:BatchGetItem' - 'dynamodb:GetItem' - 'dynamodb:Query' - 'dynamodb:Scan' - 'dynamodb:DescribeReservedCapacity' - 'dynamodb:DescribeReservedCapacityOfferings' - 'dynamodb:GetRecords' Resource: - { "Fn::GetAtt": ["PuppyDemo", "Arn"] } - Effect: Allow Action: - es:ESHttpPost - es:ESHttpPut - es:ESHttpDelete - es:ESHttpGet Resource: - { "Fn::GetAtt": ["PuppySearch", "DomainArn"] } - { "Fn::Join": ["", ["Fn::GetAtt": ["PuppySearch", "DomainArn"], "/*"]] } package: exclude: - ./** include: - ./bin/** functions: aws-golang-dynamo-stream-to-elasticsearch: name: aws-golang-dynamo-stream-to-elasticsearch handler: bin/aws-golang-dynamo-stream-to-elasticsearch memorySize: 128 timeout: 60 events: - stream: type: dynamodb batchSize: 100 enabled: true arn: { "Fn::GetAtt": ["PuppyDemo", "StreamArn"] } resources: Resources: PuppyDemo: Type: 'AWS::DynamoDB::Table' DeletionPolicy: Retain Properties: StreamSpecification: StreamViewType: NEW_AND_OLD_IMAGES AttributeDefinitions: - AttributeName: id AttributeType: S - AttributeName: name AttributeType: S KeySchema: - AttributeName: id KeyType: HASH - AttributeName: name KeyType: RANGE ProvisionedThroughput: ReadCapacityUnits: 5 WriteCapacityUnits: 5 TableName: puppies PuppySearch: Type: "AWS::Elasticsearch::Domain" Properties: ElasticsearchVersion: "6.3" DomainName: "puppy-search" ElasticsearchClusterConfig: DedicatedMasterEnabled: false InstanceCount: "1" ZoneAwarenessEnabled: false InstanceType: "t2.small.elasticsearch" EBSOptions: EBSEnabled: true Iops: 0 VolumeSize: 10 VolumeType: "gp2" ## Attention! Before you enable this lines, check out the README to avoid an open access policy # AccessPolicies: # Version: "2012-10-17" # Statement: # - # Effect: "Allow" # Principal: # AWS: "*" # Action: "es:*" # Resource: "*" AdvancedOptions: rest.action.multi.allow_explicit_index: "true" ================================================ FILE: aws-golang-googlemap/Gopkg.toml ================================================ # Gopkg.toml example # # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md # for detailed Gopkg.toml documentation. # # required = ["github.com/user/thing/cmd/thing"] # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] # # [[constraint]] # name = "github.com/user/project" # version = "1.0.0" # # [[constraint]] # name = "github.com/user/project2" # branch = "dev" # source = "github.com/myfork/project2" # # [[override]] # name = "github.com/x/y" # version = "2.4.0" [[constraint]] name = "github.com/aws/aws-lambda-go" version = "1.x" ================================================ FILE: aws-golang-googlemap/Makefile ================================================ .PHONY: build clean deploy build: dep ensure -v env GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o bin/getgeolocation getgeolocation/main.go env GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o bin/getsearchlocation getsearchlocation/main.go env GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o bin/getnearbylocation getnearbylocation/main.go env GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o bin/getgeodetail getgeodetail/main.go clean: rm -rf ./bin ./vendor Gopkg.lock deploy: clean build sls deploy --verbose ================================================ FILE: aws-golang-googlemap/README.md ================================================ # Serverless-golang google map api Serverless example using golang to hit google map api' when changing any main file call make comment in the folder directory then call serverless deploy -v change your API KEY in the yml file There are three endpoint provided: 1. GET Geolocation endpoint with address param (geolocation?address=$address) 2. GET Nearby Location with location and radius param, also name which is optional param (nearbylocation?name=$name&radius=$radius) 3. GET GeoDetail with place id (geodetail?placeid=$placeid) getgeodetail: handler: bin/getgeodetail events: - http: path: geodetail method: get request: parameters: querystrings: placeid: true ================================================ FILE: aws-golang-googlemap/geomap/geomap.go ================================================ package geomap import ( "context" "encoding/json" "errors" "io/ioutil" "net/http" ) /* Google Map API package Containing model and API call for: - Google geocoding - Google geolocation detail - Google get nearby - Google search location Each of the api call will need the google map API key */ type GooglePlaceDetailResponse struct { HTMLAttributions []interface{} `json:"html_attributions"` Result struct { AddressComponents []AddressComponent `json:"address_components"` AdrAddress string `json:"adr_address"` FormattedAddress string `json:"formatted_address"` FormattedPhoneNumber string `json:"formatted_phone_number"` Geometry GoogleGeometry `json:"geometry"` Icon string `json:"icon"` ID string `json:"id"` InternationalPhoneNumber string `json:"international_phone_number"` Name string `json:"name"` OpeningHours OpeningHour `json:"opening_hours"` Photos []Photo `json:"photos"` PlaceID string `json:"place_id"` PlusCode GooglePlusCode `json:"plus_code"` PriceLevel int `json:"price_level"` Rating float64 `json:"rating"` Reference string `json:"reference"` Reviews []GooglePlaceReview `json:"reviews"` Scope string `json:"scope"` Types []string `json:"types"` URL string `json:"url"` UserRatingsTotal int `json:"user_ratings_total"` UtcOffset int `json:"utc_offset"` Vicinity string `json:"vicinity"` Website string `json:"website"` } `json:"result"` Status string `json:"status"` } type GoogleGeocodeResponse struct { Results []struct { AddressComponents []AddressComponent `json:"address_components"` FormattedAddress string `json:"formatted_address"` Geometry GoogleGeometry `json:"geometry"` PlaceID string `json:"place_id"` PlusCode GooglePlusCode `json:"plus_code"` Types []string `json:"types"` } `json:"results"` Status string `json:"status"` } type GooglePlaceSearchResponse struct { Candidates []Candidate `json:"candidates"` Status string `json:"status"` } type GoogleNearbySearchResponse struct { HTMLAttributions []interface{} `json:"html_attributions"` Results []struct { Geometry GoogleGeometry `json:"geometry"` Icon string `json:"icon"` ID string `json:"id"` Name string `json:"name"` OpeningHours OpeningHour `json:"opening_hours"` Photos []Photo `json:"photos"` PlaceID string `json:"place_id"` PlusCode GooglePlusCode `json:"plus_code"` PriceLevel int `json:"price_level,omitempty"` Rating float64 `json:"rating"` Reference string `json:"reference"` Scope string `json:"scope"` Types []string `json:"types"` UserRatingsTotal int `json:"user_ratings_total"` Vicinity string `json:"vicinity"` } `json:"results"` Status string `json:"status"` } type OpeningHour struct { OpenNow bool `json:"open_now"` Periods []struct { Open struct { Day int `json:"day"` Time string `json:"time"` } `json:"open"` } `json:"periods,omitempty"` WeekdayText []string `json:"weekday_text,omitempty"` } type GooglePlaceReview struct { AuthorName string `json:"author_name"` AuthorURL string `json:"author_url"` Language string `json:"language"` ProfilePhotoURL string `json:"profile_photo_url"` Rating int `json:"rating"` RelativeTimeDescription string `json:"relative_time_description"` Text string `json:"text"` Time int `json:"time"` } type Candidate struct { FormattedAddress string `json:"formatted_address"` Name string `json:"name"` Photos []Photo `json:"photos"` Rating int `json:"rating"` } type Photo struct { Height int `json:"height"` HTMLAttributions []string `json:"html_attributions"` PhotoReference string `json:"photo_reference"` Width int `json:"width"` } type GooglePlusCode struct { CompoundCode string `json:"compound_code"` GlobalCode string `json:"global_code"` } type AddressComponent struct { LongName string `json:"long_name"` ShortName string `json:"short_name"` Types []string `json:"types"` } type GoogleGeometry struct { Location GoogleLocation `json:"location"` LocationType string `json:"location_type,omitempty"` Viewport GoogleViewport `json:"viewport"` } type GoogleLocation struct { Lat float64 `json:"lat"` Lng float64 `json:"lng"` } type GoogleViewport struct { Northeast GoogleLocation `json:"northeast"` SouthWest GoogleLocation `json:"southwest"` } var ( client *http.Client ) func init() { client = &http.Client{} } /* GetReverseGeoCode will return GoogleReverseGeocodeResponse on success the example of usage is sending params that contains "address" and "key" (both of them are required) more references https://developers.google.com/maps/documentation/geocoding/intro#Geocoding */ func GetGeocode(ctx context.Context, params map[string]string) (GoogleGeocodeResponse, error) { var googleGeocodeResponse GoogleGeocodeResponse //Generating url for geocode reqURL := "https://maps.googleapis.com/maps/api/geocode/json" req, err := http.NewRequest("GET", reqURL, nil) if err != nil { return googleGeocodeResponse, err } //Insert the query mapping into the request q := req.URL.Query() for key, val := range params { q.Add(key, val) } req.URL.RawQuery = q.Encode() resp, err := client.Do(req) if err != nil { return googleGeocodeResponse, err } if resp.StatusCode != http.StatusOK { return googleGeocodeResponse, errors.New("Status not OK") } defer resp.Body.Close() contents, err := ioutil.ReadAll(resp.Body) if err != nil { return googleGeocodeResponse, err } //Unmarshal the contents err = json.Unmarshal(contents, &googleGeocodeResponse) if err != nil { return googleGeocodeResponse, err } return googleGeocodeResponse, nil } /* FindPlace will return GooglePlaceSearchResponse on success more references https://developers.google.com/places/web-service/search */ func FindPlace(ctx context.Context, params map[string]string) (GooglePlaceSearchResponse, error) { var googleFindPlaceResponse GooglePlaceSearchResponse //Generating url for geocode reqURL := "https://maps.googleapis.com/maps/api/place/findplacefromtext/json" req, err := http.NewRequest("GET", reqURL, nil) if err != nil { return googleFindPlaceResponse, err } //Insert the query mapping into the request q := req.URL.Query() for key, val := range params { q.Add(key, val) } req.URL.RawQuery = q.Encode() resp, err := client.Do(req) if err != nil { return googleFindPlaceResponse, err } if resp.StatusCode != http.StatusOK { return googleFindPlaceResponse, errors.New("Status not OK") } defer resp.Body.Close() contents, err := ioutil.ReadAll(resp.Body) if err != nil { return googleFindPlaceResponse, err } //Unmarshal the contents err = json.Unmarshal(contents, &googleFindPlaceResponse) if err != nil { return googleFindPlaceResponse, err } return googleFindPlaceResponse, nil } /* FindPlace will return GoogleNearbySearchResponse on success more references https://developers.google.com/places/web-service/search */ func PlaceNearby(ctx context.Context, params map[string]string) (GoogleNearbySearchResponse, error) { var googleNearbySearchResponse GoogleNearbySearchResponse //Generating url for geocode reqURL := "https://maps.googleapis.com/maps/api/place/nearbysearch/json" req, err := http.NewRequest("GET", reqURL, nil) if err != nil { return googleNearbySearchResponse, err } //Insert the query mapping into the request q := req.URL.Query() for key, val := range params { q.Add(key, val) } req.URL.RawQuery = q.Encode() resp, err := client.Do(req) if err != nil { return googleNearbySearchResponse, err } if resp.StatusCode != http.StatusOK { return googleNearbySearchResponse, errors.New("Status not OK") } defer resp.Body.Close() contents, err := ioutil.ReadAll(resp.Body) if err != nil { return googleNearbySearchResponse, err } //Unmarshal the contents err = json.Unmarshal(contents, &googleNearbySearchResponse) if err != nil { return googleNearbySearchResponse, err } return googleNearbySearchResponse, nil } /* FindPlace will return GooglePlaceDetailResponse on success more references https://developers.google.com/places/web-service/details */ func PlaceDetail(ctx context.Context, params map[string]string) (GooglePlaceDetailResponse, error) { var googlePlaceDetailResponse GooglePlaceDetailResponse //Generating url for geocode reqURL := "https://maps.googleapis.com/maps/api/place/details/json" req, err := http.NewRequest("GET", reqURL, nil) if err != nil { return googlePlaceDetailResponse, err } //Insert the query mapping into the request q := req.URL.Query() for key, val := range params { q.Add(key, val) } req.URL.RawQuery = q.Encode() resp, err := client.Do(req) if err != nil { return googlePlaceDetailResponse, err } if resp.StatusCode != http.StatusOK { return googlePlaceDetailResponse, errors.New("Status not OK") } defer resp.Body.Close() contents, err := ioutil.ReadAll(resp.Body) if err != nil { return googlePlaceDetailResponse, err } //Unmarshal the contents err = json.Unmarshal(contents, &googlePlaceDetailResponse) if err != nil { return googlePlaceDetailResponse, err } return googlePlaceDetailResponse, nil } ================================================ FILE: aws-golang-googlemap/getgeodetail/main.go ================================================ package main import ( "context" "encoding/json" "gomapservice/geomap" "os" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" ) // Response is of type APIGatewayProxyResponse since we're leveraging the // AWS Lambda Proxy Request functionality (default behavior) // // https://serverless.com/framework/docs/providers/aws/events/apigateway/#lambda-proxy-integration type Response events.APIGatewayProxyResponse // Handler is our lambda handler invoked by the `lambda.Start` function call // Handler function Using AWS Lambda Proxy Request func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { ctx := context.Background() //required query placeid := request.QueryStringParameters["placeid"] //Replace with api key key := os.Getenv("GOOGLE_API_KEY") geoParams := map[string]string{ "placeid": placeid, "key": key, } //obtains place detail response to be processed googleResp, err := geomap.PlaceDetail(ctx, geoParams) if err != nil { return events.APIGatewayProxyResponse{Body: "Error", StatusCode: 400}, err } jsonString, _ := json.Marshal(googleResp) //Returning response with AWS Lambda Proxy Response return events.APIGatewayProxyResponse{Body: string(jsonString), StatusCode: 200}, nil } func main() { lambda.Start(Handler) } ================================================ FILE: aws-golang-googlemap/getgeolocation/main.go ================================================ package main import ( "context" "encoding/json" "gomapservice/geomap" "os" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" ) // Response is of type APIGatewayProxyResponse since we're leveraging the // AWS Lambda Proxy Request functionality (default behavior) // // https://serverless.com/framework/docs/providers/aws/events/apigateway/#lambda-proxy-integration type Response events.APIGatewayProxyResponse // Handler is our lambda handler invoked by the `lambda.Start` function call // Handler function Using AWS Lambda Proxy Request func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { ctx := context.Background() //required query address := request.QueryStringParameters["address"] //Replace with api key key := os.Getenv("GOOGLE_API_KEY") geoParams := map[string]string{ "address": address, "key": key, } //obtains geocode response to be processed googleResp, err := geomap.GetGeocode(ctx, geoParams) if err != nil { return events.APIGatewayProxyResponse{Body: "Error", StatusCode: 400}, err } jsonString, _ := json.Marshal(googleResp) //Returning response with AWS Lambda Proxy Response return events.APIGatewayProxyResponse{Body: string(jsonString), StatusCode: 200}, nil } func main() { lambda.Start(Handler) } ================================================ FILE: aws-golang-googlemap/getnearbylocation/main.go ================================================ package main import ( "context" "encoding/json" "gomapservice/geomap" "os" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" ) // Response is of type APIGatewayProxyResponse since we're leveraging the // AWS Lambda Proxy Request functionality (default behavior) // // https://serverless.com/framework/docs/providers/aws/events/apigateway/#lambda-proxy-integration type Response events.APIGatewayProxyResponse // Handler is our lambda handler invoked by the `lambda.Start` function call // Handler function Using AWS Lambda Proxy Request func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { ctx := context.Background() //required query location := request.QueryStringParameters["location"] radius := request.QueryStringParameters["radius"] name := request.QueryStringParameters["name"] googleType := request.QueryStringParameters["type"] //Replace with api key key := os.Getenv("GOOGLE_API_KEY") geoParams := map[string]string{ "location": location, "radius": radius, "key": key, } //optional query param if name != "" { geoParams["name"] = name } if googleType != "" { geoParams["type"] = googleType } //obtains place nearby response to be processed googleResp, err := geomap.PlaceNearby(ctx, geoParams) if err != nil { return events.APIGatewayProxyResponse{Body: "Error", StatusCode: 400}, err } jsonString, _ := json.Marshal(googleResp) //Returning response with AWS Lambda Proxy Response return events.APIGatewayProxyResponse{Body: string(jsonString), StatusCode: 200}, nil } func main() { lambda.Start(Handler) } ================================================ FILE: aws-golang-googlemap/getsearchlocation/main.go ================================================ package main import ( "context" "encoding/json" "gomapservice/geomap" "os" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" ) // Response is of type APIGatewayProxyResponse since we're leveraging the // AWS Lambda Proxy Request functionality (default behavior) // // https://serverless.com/framework/docs/providers/aws/events/apigateway/#lambda-proxy-integration type Response events.APIGatewayProxyResponse // Handler is our lambda handler invoked by the `lambda.Start` function call // Handler function Using AWS Lambda Proxy Request func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { ctx := context.Background() //required query address := request.QueryStringParameters["address"] //Replace with api key key := os.Getenv("GOOGLE_API_KEY") geoParams := map[string]string{ "address": address, "key": key, } //obtains find place response to be processed googleResp, err := geomap.FindPlace(ctx, geoParams) if err != nil { return events.APIGatewayProxyResponse{Body: "Error", StatusCode: 400}, err } jsonString, _ := json.Marshal(googleResp) //Returning response with AWS Lambda Proxy Response return events.APIGatewayProxyResponse{Body: string(jsonString), StatusCode: 200}, nil } func main() { lambda.Start(Handler) } ================================================ FILE: aws-golang-googlemap/serverless.yml ================================================ # Welcome to Serverless! # # This file is the main config file for your service. # It's very minimal at this point and uses default values. # You can always add more config options for more control. # We've included some commented out config examples here. # Just uncomment any of them to get that config option. # # For full config options, check the docs: # docs.serverless.com # # Happy Coding! service: gomapservice # NOTE: update this with your service name # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details # frameworkVersion: "=X.X.X" frameworkVersion: ">=2.24.0" provider: name: aws runtime: go1.x environment: GOOGLE_API_KEY: AIzaXXXX #CHANGE YOUR API KEY # you can overwrite defaults here # stage: dev region: ap-southeast-1 # you can add statements to the Lambda function's IAM Role here # iam: # role: # statements: # - Effect: "Allow" # Action: # - "s3:ListBucket" # Resource: { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "ServerlessDeploymentBucket" } ] ] } # - Effect: "Allow" # Action: # - "s3:PutObject" # Resource: # Fn::Join: # - "" # - - "arn:aws:s3:::" # - "Ref" : "ServerlessDeploymentBucket" # - "/*" # you can define service wide environment variables here # environment: # variable1: value1 package: exclude: - ./** include: - ./bin/** functions: getgeolocation: handler: bin/getgeolocation events: - http: path: geolocation method: get request: parameters: querystrings: address: true getnearbylocation: handler: bin/getnearbylocation events: - http: path: nearbylocation method: get request: parameters: querystrings: location: true radius: true name: false type: false getgeodetail: handler: bin/getgeodetail events: - http: path: geodetail method: get request: parameters: querystrings: placeid: true # The following are a few example events you can configure # NOTE: Please make sure to change your handler code to work with those events # Check the event documentation for details # events: # events: # - http: # path: users/create # method: get # - s3: ${env:BUCKET} # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 # - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" # - cloudwatchEvent: # event: # source: # - "aws.ec2" # detail-type: # - "EC2 Instance State-change Notification" # detail: # state: # - pending # - cloudwatchLog: '/aws/lambda/hello' # - cognitoUserPool: # pool: MyUserPool # trigger: PreSignUp # Define function environment variables here # environment: # variable2: value2 # you can add CloudFormation resource templates here #resources: # Resources: # NewResource: # Type: AWS::S3::Bucket # Properties: # BucketName: my-new-bucket # Outputs: # NewOutput: # Description: "Description for the output" # Value: "Some output value" ================================================ FILE: aws-golang-http-get-post/Gopkg.toml ================================================ # Gopkg.toml example # # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md # for detailed Gopkg.toml documentation. # # required = ["github.com/user/thing/cmd/thing"] # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] # # [[constraint]] # name = "github.com/user/project" # version = "1.0.0" # # [[constraint]] # name = "github.com/user/project2" # branch = "dev" # source = "github.com/myfork/project2" # # [[override]] # name = "github.com/x/y" # version = "2.4.0" [[constraint]] name = "github.com/aws/aws-lambda-go" version = "1.x" ================================================ FILE: aws-golang-http-get-post/Makefile ================================================ .PHONY: build clean deploy build: cd getFolder && env GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o ../bin/getBin getExample.go && cd .. cd getFolder && env GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o ../bin/getQueryBin getQueryExample.go && cd .. cd postFolder && env GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o ../bin/postBin ./postExample.go && cd .. clean: rm -rf ./bin ./vendor Gopkg.lock deploy: clean build sls deploy --verbose ================================================ FILE: aws-golang-http-get-post/README.md ================================================ # Serverless-golang http Get and Post Example Serverless boilerplate code for golang with GET and POST example This example is using AWS Request and Response Proxy Model, provided by AWS itself. If you want to test any changes don't forget to run `make` inside the service directory. There are three endpoint provided: 1. GET endpoint with name parameter (/get/{name}) 2. GET endpoint with query string parameter (getQ?name=$name) 3. POST endpoint with name in the body (/post - with JSON body {"name":$name} ================================================ FILE: aws-golang-http-get-post/getFolder/getExample.go ================================================ package main import ( "fmt" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" ) // Handler function Using AWS Lambda Proxy Request func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { //Get the path parameter that was sent name := request.PathParameters["name"] //Generate message that want to be sent as body message := fmt.Sprintf(" { \"Message\" : \"Hello %s \" } ", name) //Returning response with AWS Lambda Proxy Response return events.APIGatewayProxyResponse{Body: message, StatusCode: 200}, nil } func main() { lambda.Start(Handler) } ================================================ FILE: aws-golang-http-get-post/getFolder/getQueryExample.go ================================================ package main import ( "fmt" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" ) // Handler function Using AWS Lambda Proxy Request func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { //Get the path parameter that was sent name := request.QueryStringParameters["name"] //Generate message that want to be sent as body message := fmt.Sprintf(" { \"Message\" : \"Hello %s \" } ", name) //Returning response with AWS Lambda Proxy Response return events.APIGatewayProxyResponse{Body: message, StatusCode: 200}, nil } func main() { lambda.Start(Handler) } ================================================ FILE: aws-golang-http-get-post/getFolder/go.mod ================================================ module getFolder go 1.17 require github.com/aws/aws-lambda-go v1.26.0 ================================================ FILE: aws-golang-http-get-post/getFolder/go.sum ================================================ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/aws/aws-lambda-go v1.26.0 h1:6ujqBpYF7tdZcBvPIccs98SpeGfrt/UOVEiexfNIdHA= github.com/aws/aws-lambda-go v1.26.0/go.mod h1:jJmlefzPfGnckuHdXX7/80O3BvUUi12XOkbv4w9SGLU= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: aws-golang-http-get-post/package.json ================================================ { "name": "aws-golang-http-get-post", "version": "1.29.2", "description": "Example on Making Parameterized Get and Post Request with Golang", "author": "Pramono Winata ", "license": "MIT" } ================================================ FILE: aws-golang-http-get-post/postFolder/go.mod ================================================ module postFolder go 1.17 require github.com/aws/aws-lambda-go v1.26.0 ================================================ FILE: aws-golang-http-get-post/postFolder/go.sum ================================================ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/aws/aws-lambda-go v1.26.0 h1:6ujqBpYF7tdZcBvPIccs98SpeGfrt/UOVEiexfNIdHA= github.com/aws/aws-lambda-go v1.26.0/go.mod h1:jJmlefzPfGnckuHdXX7/80O3BvUUi12XOkbv4w9SGLU= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: aws-golang-http-get-post/postFolder/postExample.go ================================================ package main import ( "encoding/json" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" ) // BodyRequest is our self-made struct to process JSON request from Client type BodyRequest struct { RequestName string `json:"name"` } // BodyResponse is our self-made struct to build response for Client type BodyResponse struct { ResponseName string `json:"name"` } // Handler function Using AWS Lambda Proxy Request func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { // BodyRequest will be used to take the json response from client and build it bodyRequest := BodyRequest{ RequestName: "", } // Unmarshal the json, return 404 if error err := json.Unmarshal([]byte(request.Body), &bodyRequest) if err != nil { return events.APIGatewayProxyResponse{Body: err.Error(), StatusCode: 404}, nil } // We will build the BodyResponse and send it back in json form bodyResponse := BodyResponse{ ResponseName: bodyRequest.RequestName + " LastName", } // Marshal the response into json bytes, if error return 404 response, err := json.Marshal(&bodyResponse) if err != nil { return events.APIGatewayProxyResponse{Body: err.Error(), StatusCode: 404}, nil } //Returning response with AWS Lambda Proxy Response return events.APIGatewayProxyResponse{Body: string(response), StatusCode: 200}, nil } func main() { lambda.Start(Handler) } ================================================ FILE: aws-golang-http-get-post/serverless.yml ================================================ # Welcome to Serverless! # # This file is the main config file for your service. # It's very minimal at this point and uses default values. # You can always add more config options for more control. # We've included some commented out config examples here. # Just uncomment any of them to get that config option. # # For full config options, check the docs: # docs.serverless.com # # Happy Coding! service: goservice # NOTE: update this with your service name # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details # frameworkVersion: "=X.X.X" frameworkVersion: ">=2.24.0" provider: name: aws runtime: go1.x # you can overwrite defaults here # stage: dev # region: us-east-1 # you can add statements to the Lambda function's IAM Role here # iam: # role: # statements: # - Effect: "Allow" # Action: # - "s3:ListBucket" # Resource: { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "ServerlessDeploymentBucket" } ] ] } # - Effect: "Allow" # Action: # - "s3:PutObject" # Resource: # Fn::Join: # - "" # - - "arn:aws:s3:::" # - "Ref" : "ServerlessDeploymentBucket" # - "/*" # you can define service wide environment variables here # environment: # variable1: value1 package: individually: true exclude: - ./** functions: get: handler: bin/getBin package: include: - ./bin/getBin events: - http: path: get/{name} method: get request: parameter: paths: name: true getquery: handler: bin/getQueryBin package: include: - ./bin/getQueryBin events: - http: path: getQ method: get request: parameters: querystrings: name: true post: handler: bin/postBin package: include: - ./bin/getQueryBin events: - http: path: post method: post # The following are a few example events you can configure # NOTE: Please make sure to change your handler code to work with those events # Check the event documentation for details # events: # events: # - http: # path: users/create # method: get # - s3: ${env:BUCKET} # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 # - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" # - cloudwatchEvent: # event: # source: # - "aws.ec2" # detail-type: # - "EC2 Instance State-change Notification" # detail: # state: # - pending # - cloudwatchLog: '/aws/lambda/hello' # - cognitoUserPool: # pool: MyUserPool # trigger: PreSignUp # Define function environment variables here # environment: # variable2: value2 # you can add CloudFormation resource templates here #resources: # Resources: # NewResource: # Type: AWS::S3::Bucket # Properties: # BucketName: my-new-bucket # Outputs: # NewOutput: # Description: "Description for the output" # Value: "Some output value" ================================================ FILE: aws-golang-rest-api-with-dynamodb/.gitignore ================================================ .serverless bin *.pyc *.pyo # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) # vendor/ ================================================ FILE: aws-golang-rest-api-with-dynamodb/Makefile ================================================ .PHONY: build clean deploy build: env GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o bin/create todos/create.go env GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o bin/delete todos/delete.go env GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o bin/get todos/get.go env GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o bin/list todos/list.go env GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o bin/update todos/update.go clean: rm -rf ./bin ./vendor Gopkg.lock deploy: clean build sls deploy --verbose format: gofmt -w todos/create.go gofmt -w todos/delete.go gofmt -w todos/get.go gofmt -w todos/list.go gofmt -w todos/update.go ================================================ FILE: aws-golang-rest-api-with-dynamodb/README.md ================================================ # aws-golang-rest-api-with-dynamodb Build & Deploy ``` make deploy ``` # CRUD Operations ## Create ``` curl --request POST \ --url https://fz3n8nstdf.execute-api.us-east-1.amazonaws.com/dev/todos \ --header 'Content-Type: application/json' \ --data '{ "Title": "Walk the Dog", "Details": "Complete before 11am" }' curl --request POST \ --url https://fz3n8nstdf.execute-api.us-east-1.amazonaws.com/dev/todos \ --header 'Content-Type: application/json' \ --data '{ "Title": "Mow the Lawn", "Details": "Remember to buy gas" }' ``` ## Read ``` curl --request GET \ --url https://fz3n8nstdf.execute-api.us-east-1.amazonaws.com/dev/todos/{id} ``` ## Update ``` curl --request PUT \ --url https://fz3n8nstdf.execute-api.us-east-1.amazonaws.com/dev/todos/0d2263b7-c62d-4df6-8503-bb16ee8dd81 \ --header 'Content-Type: application/json' \ --data '{ "title": "Updated title", "details": "Updated details" }' ``` ## List ``` curl --request GET \ --url https://fz3n8nstdf.execute-api.us-east-1.amazonaws.com/dev/todos ``` ## Delete ``` curl --request DELETE \ --url https://fz3n8nstdf.execute-api.us-east-1.amazonaws.com/dev/todos/0d2263b7-c62d-4df6-8503-bb16ee8dd81 ``` ================================================ FILE: aws-golang-rest-api-with-dynamodb/go.mod ================================================ module github.com/serverless/examples/aws-golang-rest-api-with-dynamodb go 1.15 require ( github.com/aws/aws-lambda-go v1.22.0 github.com/aws/aws-sdk-go v1.37.1 // indirect github.com/google/uuid v1.2.0 // indirect ) ================================================ FILE: aws-golang-rest-api-with-dynamodb/go.sum ================================================ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/aws/aws-lambda-go v1.22.0 h1:X7BKqIdfoJcbsEIi+Lrt5YjX1HnZexIbNWOQgkYKgfE= github.com/aws/aws-lambda-go v1.22.0/go.mod h1:jJmlefzPfGnckuHdXX7/80O3BvUUi12XOkbv4w9SGLU= github.com/aws/aws-sdk-go v1.37.1 h1:BTHmuN+gzhxkvU9sac2tZvaY0gV9ihbHw+KxZOecYvY= github.com/aws/aws-sdk-go v1.37.1/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: aws-golang-rest-api-with-dynamodb/package.json ================================================ { "name": "aws-golang-rest-api-with-dynamodb", "version": "1.0.0", "description": "Serverless CRUD service exposing a REST HTTP interface", "author": "", "license": "MIT" } ================================================ FILE: aws-golang-rest-api-with-dynamodb/serverless.yml ================================================ app: aws-golang-rest-api-with-dynamodb service: aws-golang-rest-api-with-dynamodb frameworkVersion: ">=2.24.0" provider: name: aws runtime: go1.x environment: DYNAMODB_TABLE: ${self:service}-${opt:stage, self:provider.stage} iam: role: statements: - Effect: Allow Action: - dynamodb:Query - dynamodb:Scan - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}" functions: create: handler: bin/create package: include: - ./bin/create events: - http: path: todos method: post cors: true list: handler: bin/list package: include: - ./bin/list events: - http: path: todos method: get cors: true get: handler: bin/get package: include: - ./bin/get events: - http: path: todos/{id} method: get cors: true update: handler: bin/update package: include: - ./bin/update events: - http: path: todos/{id} method: put cors: true delete: handler: bin/delete package: include: - ./bin/deleteBin events: - http: path: todos/{id} method: delete cors: true resources: Resources: TodosDynamoDbTable: Type: 'AWS::DynamoDB::Table' DeletionPolicy: Retain Properties: AttributeDefinitions: - AttributeName: id AttributeType: S KeySchema: - AttributeName: id KeyType: HASH ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 TableName: ${self:provider.environment.DYNAMODB_TABLE} ================================================ FILE: aws-golang-rest-api-with-dynamodb/todos/create.go ================================================ package main import ( "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/dynamodb" "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" "github.com/google/uuid" "encoding/json" "fmt" "os" ) type Item struct { Id string `json:"id,omitempty"` Title string `json:"title"` Details string `json:"details"` } func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { // Creating session for client sess := session.Must(session.NewSessionWithOptions(session.Options{ SharedConfigState: session.SharedConfigEnable, })) // Create DynamoDB client svc := dynamodb.New(sess) // New uuid for item id itemUuid := uuid.New().String() fmt.Println("Generated new item uuid:", itemUuid) // Unmarshal to Item to access object properties itemString := request.Body itemStruct := Item{} json.Unmarshal([]byte(itemString), &itemStruct) if itemStruct.Title == "" { return events.APIGatewayProxyResponse{StatusCode: 400}, nil } // Create new item of type item item := Item{ Id: itemUuid, Title: itemStruct.Title, Details: itemStruct.Details, } // Marshal to dynamobb item av, err := dynamodbattribute.MarshalMap(item) if err != nil { fmt.Println("Error marshalling item: ", err.Error()) return events.APIGatewayProxyResponse{StatusCode: 500}, nil } tableName := os.Getenv("DYNAMODB_TABLE") // Build put item input fmt.Println("Putting item: %v", av) input := &dynamodb.PutItemInput{ Item: av, TableName: aws.String(tableName), } // PutItem request _, err = svc.PutItem(input) // Checking for errors, return error if err != nil { fmt.Println("Got error calling PutItem: ", err.Error()) return events.APIGatewayProxyResponse{StatusCode: 500}, nil } // Marshal item to return itemMarshalled, err := json.Marshal(item) fmt.Println("Returning item: ", string(itemMarshalled)) //Returning response with AWS Lambda Proxy Response return events.APIGatewayProxyResponse{Body: string(itemMarshalled), StatusCode: 200}, nil } func main() { lambda.Start(Handler) } ================================================ FILE: aws-golang-rest-api-with-dynamodb/todos/delete.go ================================================ package main import ( "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/dynamodb" "fmt" "os" ) type Item struct { Id string `json:"id,omitempty"` Title string `json:"title"` Details string `json:"details"` } func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { // Creating session for client sess := session.Must(session.NewSessionWithOptions(session.Options{ SharedConfigState: session.SharedConfigEnable, })) // Create DynamoDB client svc := dynamodb.New(sess) pathParamId := request.PathParameters["id"] input := &dynamodb.DeleteItemInput{ Key: map[string]*dynamodb.AttributeValue{ "id": { S: aws.String(pathParamId), }, }, TableName: aws.String(os.Getenv("DYNAMODB_TABLE")), } // DeleteItem request _, err := svc.DeleteItem(input) // Checking for errors, return error if err != nil { fmt.Println("Got error calling DeleteItem: ", err.Error()) return events.APIGatewayProxyResponse{StatusCode: 500}, nil } return events.APIGatewayProxyResponse{StatusCode: 204}, nil } func main() { lambda.Start(Handler) } ================================================ FILE: aws-golang-rest-api-with-dynamodb/todos/get.go ================================================ package main import ( "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/dynamodb" "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" "encoding/json" "fmt" "os" ) type Item struct { Id string `json:"id,omitempty"` Title string `json:"title"` Details string `json:"details"` } func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { // Creating session for client sess := session.Must(session.NewSessionWithOptions(session.Options{ SharedConfigState: session.SharedConfigEnable, })) // Create DynamoDB client svc := dynamodb.New(sess) // Getting id from path parameters pathParamId := request.PathParameters["id"] fmt.Println("Derived pathParamId from path params: ", pathParamId) // GetItem request result, err := svc.GetItem(&dynamodb.GetItemInput{ TableName: aws.String(os.Getenv("DYNAMODB_TABLE")), Key: map[string]*dynamodb.AttributeValue{ "id": { S: aws.String(pathParamId), }, }, }) // Checking for errors, return error if err != nil { fmt.Println(err.Error()) return events.APIGatewayProxyResponse{StatusCode: 500}, nil } // Checking type if len(result.Item) == 0 { return events.APIGatewayProxyResponse{StatusCode: 404}, nil } // Created item of type Item item := Item{} // result is of type *dynamodb.GetItemOutput // result.Item is of type map[string]*dynamodb.AttributeValue // UnmarshallMap result.item into item err = dynamodbattribute.UnmarshalMap(result.Item, &item) if err != nil { panic(fmt.Sprintf("Failed to UnmarshalMap result.Item: ", err)) } // Marshal to type []uint8 marshalledItem, err := json.Marshal(item) // Return marshalled item return events.APIGatewayProxyResponse{Body: string(marshalledItem), StatusCode: 200}, nil } func main() { lambda.Start(Handler) } ================================================ FILE: aws-golang-rest-api-with-dynamodb/todos/list.go ================================================ package main import ( "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/dynamodb" "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" "encoding/json" "fmt" "os" ) type Item struct { Id string `json:"id,omitempty"` Title string `json:"title"` Details string `json:"details"` } func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { // Creating session for client sess := session.Must(session.NewSessionWithOptions(session.Options{ SharedConfigState: session.SharedConfigEnable, })) // Create DynamoDB client svc := dynamodb.New(sess) // Build the query input parameters params := &dynamodb.ScanInput{ TableName: aws.String(os.Getenv("DYNAMODB_TABLE")), } // Scan table result, err := svc.Scan(params) // Checking for errors, return error if err != nil { fmt.Println("Query API call failed: ", err.Error()) return events.APIGatewayProxyResponse{StatusCode: 500}, nil } var itemArray []Item for _, i := range result.Items { item := Item{} // result is of type *dynamodb.GetItemOutput // result.Item is of type map[string]*dynamodb.AttributeValue // UnmarshallMap result.item to item err = dynamodbattribute.UnmarshalMap(i, &item) if err != nil { fmt.Println("Got error unmarshalling: ", err.Error()) return events.APIGatewayProxyResponse{StatusCode: 500}, nil } itemArray = append(itemArray, item) } fmt.Println("itemArray: ", itemArray) itemArrayString, err := json.Marshal(itemArray) if err != nil { fmt.Println("Got error marshalling result: ", err.Error()) return events.APIGatewayProxyResponse{StatusCode: 500}, nil } return events.APIGatewayProxyResponse{Body: string(itemArrayString), StatusCode: 200}, nil } func main() { lambda.Start(Handler) } ================================================ FILE: aws-golang-rest-api-with-dynamodb/todos/update.go ================================================ package main import ( "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/dynamodb" "encoding/json" "fmt" "os" ) type Item struct { Id string `json:"id,omitempty"` Title string `json:"title"` Details string `json:"details"` } func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { // Creating session for client sess := session.Must(session.NewSessionWithOptions(session.Options{ SharedConfigState: session.SharedConfigEnable, })) // Create DynamoDB client svc := dynamodb.New(sess) pathParamId := request.PathParameters["id"] itemString := request.Body itemStruct := Item{} json.Unmarshal([]byte(itemString), &itemStruct) info := Item{ Title: itemStruct.Title, Details: itemStruct.Details, } fmt.Println("Updating title to: ", info.Title) fmt.Println("Updating details to: ", info.Details) // Prepare input for Update Item input := &dynamodb.UpdateItemInput{ ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{ ":t": { S: aws.String(info.Title), }, ":d": { S: aws.String(info.Details), }, }, TableName: aws.String(os.Getenv("DYNAMODB_TABLE")), Key: map[string]*dynamodb.AttributeValue{ "id": { S: aws.String(pathParamId), }, }, ReturnValues: aws.String("UPDATED_NEW"), UpdateExpression: aws.String("set title = :t, details = :d"), } // UpdateItem request _, err := svc.UpdateItem(input) // Checking for errors, return error if err != nil { fmt.Println(err.Error()) return events.APIGatewayProxyResponse{StatusCode: 500}, nil } return events.APIGatewayProxyResponse{StatusCode: 204}, nil } func main() { lambda.Start(Handler) } ================================================ FILE: aws-golang-s3-file-replicator/.gitignore ================================================ # Serverless directories .serverless # golang output binary directory bin # golang vendor (dependencies) directory vendor # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out ================================================ FILE: aws-golang-s3-file-replicator/Makefile ================================================ .PHONY: build clean deploy gomodgen build: gomodgen export GO111MODULE=on env GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o bin/replicator src/main.go clean: rm -rf ./bin ./vendor Gopkg.lock deploy: clean build sls deploy --verbose gomodgen: chmod u+x gomod.sh ./gomod.sh ================================================ FILE: aws-golang-s3-file-replicator/README.md ================================================ # Serverless-golang AWS S3 object create event and replicator example Serverless boilerplate code for golang with S3 object create event and replicator example The example shows following steps: 1. Detect object create event with input bucket 2. Copy detected object and duplicate object to new bucket with assigned object name. 3. [optional] you will able to append file extension or rename object name with assign object name. ================================================ FILE: aws-golang-s3-file-replicator/go.mod ================================================ module github.com/examples/aws-golang-s3-file-replicator go 1.14 require ( github.com/aws/aws-lambda-go v1.6.0 github.com/aws/aws-sdk-go v1.30.7 // indirect ) ================================================ FILE: aws-golang-s3-file-replicator/go.sum ================================================ github.com/aws/aws-lambda-go v1.6.0 h1:T+u/g79zPKw1oJM7xYhvpq7i4Sjc0iVsXZUaqRVVSOg= github.com/aws/aws-lambda-go v1.6.0/go.mod h1:zUsUQhAUjYzR8AuduJPCfhBuKWUaDbQiPOG+ouzmE1A= github.com/aws/aws-sdk-go v1.30.7 h1:IaXfqtioP6p9SFAnNfsqdNczbR5UNbYqvcZUSsCAdTY= github.com/aws/aws-sdk-go v1.30.7/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= ================================================ FILE: aws-golang-s3-file-replicator/gomod.sh ================================================ #!/bin/bash set -eu if [ -f ./go.mod ]; then exit 0 fi touch go.mod PROJECT_NAME=$(basename $(pwd | xargs dirname)) CURRENT_DIR=$(basename $(pwd)) CONTENT=$(cat <<-EOD module github.com/${PROJECT_NAME}/${CURRENT_DIR} require github.com/aws/aws-lambda-go v1.6.0 EOD ) echo "$CONTENT" > go.mod ================================================ FILE: aws-golang-s3-file-replicator/serverless.yml ================================================ service: aws-golang-s3-file-replicator frameworkVersion: ">=2.24.0" custom: inputBucket: replicator-input-101 outputBucket: replicator-output-101 provider: name: aws runtime: go1.x stage: dev region: ap-northeast-1 memorySize: 128 timeout: 30 iam: role: statements: - Effect: Allow Action: - s3:* Resource: "arn:aws:s3:::${self:custom.outputBucket}/*" - Effect: Allow Action: - s3:* Resource: "arn:aws:s3:::${self:custom.inputBucket}/*" package: exclude: - ./** include: - ./bin/** functions: replicate: handler: bin/replicator environment: OUTPUT_BUCKET: ${self:custom.outputBucket} events: - s3: bucket: ${self:custom.inputBucket} existing: true event: s3:ObjectCreated:* ================================================ FILE: aws-golang-s3-file-replicator/src/main.go ================================================ package main import ( "context" "fmt" "os" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" ) func Handler(ctx context.Context, S3Event events.S3Event) { svc := s3.New(session.New()) input := &s3.CopyObjectInput{ CopySource: aws.String("/" + S3Event.Records[0].S3.Bucket.Name + "/" + S3Event.Records[0].S3.Object.Key), Bucket: aws.String(os.Getenv("OUTPUT_BUCKET")), // target bucket Key: aws.String(S3Event.Records[0].S3.Object.Key), // target object name } _, err := svc.CopyObject(input) if err != nil { // For information on other S3 API error codes see: // http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html if aerr, ok := err.(awserr.Error); ok { switch aerr.Code() { case s3.ErrCodeObjectNotInActiveTierError: fmt.Println(s3.ErrCodeObjectNotInActiveTierError, aerr.Error()) default: // Process error generically fmt.Println("Error:", aerr.Error()) } } else { // Process error generically fmt.Println("Error:", err.Error()) } } } func main() { lambda.Start(Handler) } ================================================ FILE: aws-golang-simple-http-endpoint/Gopkg.toml ================================================ # Gopkg.toml example # # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md # for detailed Gopkg.toml documentation. # # required = ["github.com/user/thing/cmd/thing"] # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] # # [[constraint]] # name = "github.com/user/project" # version = "1.0.0" # # [[constraint]] # name = "github.com/user/project2" # branch = "dev" # source = "github.com/myfork/project2" # # [[override]] # name = "github.com/x/y" # version = "2.4.0" [[constraint]] name = "github.com/aws/aws-lambda-go" version = "1.x" ================================================ FILE: aws-golang-simple-http-endpoint/Makefile ================================================ build: dep ensure -v env GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o bin/hello hello/main.go env GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o bin/world world/main.go .PHONY: clean clean: rm -rf ./bin ./vendor Gopkg.lock .PHONY: deploy deploy: clean build sls deploy --verbose ================================================ FILE: aws-golang-simple-http-endpoint/README.md ================================================ ================================================ FILE: aws-golang-simple-http-endpoint/hello/main.go ================================================ package main import ( "bytes" "context" "encoding/json" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" ) // Response is of type APIGatewayProxyResponse since we're leveraging the // AWS Lambda Proxy Request functionality (default behavior) // // https://serverless.com/framework/docs/providers/aws/events/apigateway/#lambda-proxy-integration type Response events.APIGatewayProxyResponse // Handler is our lambda handler invoked by the `lambda.Start` function call func Handler(ctx context.Context) (Response, error) { var buf bytes.Buffer body, err := json.Marshal(map[string]interface{}{ "message": "Go Serverless v1.0! Your function executed successfully!", }) if err != nil { return Response{StatusCode: 404}, err } json.HTMLEscape(&buf, body) resp := Response{ StatusCode: 200, IsBase64Encoded: false, Body: buf.String(), Headers: map[string]string{ "Content-Type": "application/json", "X-MyCompany-Func-Reply": "hello-handler", }, } return resp, nil } func main() { lambda.Start(Handler) } ================================================ FILE: aws-golang-simple-http-endpoint/package.json ================================================ { "name": "aws-golang-simple-http-endpoint", "version": "0.0.1", "description": "Example demonstrates how to setup a simple HTTP GET endpoint with golang", "author": "Sebastian Borza ", "license": "MIT" } ================================================ FILE: aws-golang-simple-http-endpoint/serverless.yml ================================================ service: aws-golang-simple-http-endpoint frameworkVersion: '2' provider: name: aws runtime: go1.x functions: hello: handler: bin/hello events: - httpApi: path: /hello method: get world: handler: bin/world events: - httpApi: path: /world method: get package: exclude: - ./** include: - ./bin/** ================================================ FILE: aws-golang-simple-http-endpoint/world/main.go ================================================ package main import ( "bytes" "context" "encoding/json" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" ) // Response is of type APIGatewayProxyResponse since we're leveraging the // AWS Lambda Proxy Request functionality (default behavior) // // https://serverless.com/framework/docs/providers/aws/events/apigateway/#lambda-proxy-integration type Response events.APIGatewayProxyResponse // Handler is our lambda handler invoked by the `lambda.Start` function call func Handler(ctx context.Context) (Response, error) { var buf bytes.Buffer body, err := json.Marshal(map[string]interface{}{ "message": "Okay so your other function also executed successfully!", }) if err != nil { return Response{StatusCode: 404}, err } json.HTMLEscape(&buf, body) resp := Response{ StatusCode: 200, IsBase64Encoded: false, Body: buf.String(), Headers: map[string]string{ "Content-Type": "application/json", "X-MyCompany-Func-Reply": "world-handler", }, } return resp, nil } func main() { lambda.Start(Handler) } ================================================ FILE: aws-golang-stream-kinesis-to-elasticsearch/Gopkg.toml ================================================ # Gopkg.toml example # # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html # for detailed Gopkg.toml documentation. # # required = ["github.com/user/thing/cmd/thing"] # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] # # [[constraint]] # name = "github.com/user/project" # version = "1.0.0" # # [[constraint]] # name = "github.com/user/project2" # branch = "dev" # source = "github.com/myfork/project2" # # [[override]] # name = "github.com/x/y" # version = "2.4.0" # # [prune] # non-go = false # go-tests = true # unused-packages = true [[constraint]] name = "github.com/aws/aws-lambda-go" version = "1.6.0" [[constraint]] name = "github.com/olivere/elastic" version = "6.2.12" [prune] go-tests = true unused-packages = true ================================================ FILE: aws-golang-stream-kinesis-to-elasticsearch/Makefile ================================================ build: dep ensure -v env GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o bin/stream main.go .PHONY: clean clean: rm -rf ./bin ./vendor .PHONY: deploy deploy: clean build sls deploy --verbose ================================================ FILE: aws-golang-stream-kinesis-to-elasticsearch/README.md ================================================ ================================================ FILE: aws-golang-stream-kinesis-to-elasticsearch/elastic/elastic.go ================================================ package elastic import ( "context" "log" "os" "time" "github.com/aws/aws-lambda-go/events" "github.com/olivere/elastic" ) // Elastic is our default struct for connecting through to elasticsearch type Elastic struct { Client *elastic.Client } // NewClient instantiates a connection to elasticsearch for direct push func NewClient(host, schema string) (*Elastic, error) { client, err := elastic.NewClient( elastic.SetURL(host), elastic.SetScheme(schema), elastic.SetSniff(false), elastic.SetErrorLog(log.New(os.Stderr, "ELASTIC ", log.LstdFlags)), elastic.SetInfoLog(log.New(os.Stdout, "", log.LstdFlags)), ) return &Elastic{client}, err } // PushRecords sends a particular batch of docs over to elasticsearch endpoint func (e *Elastic) PushRecords(data []events.KinesisEventRecord) error { p, err := e.Client.BulkProcessor(). Name("kinesisWorkers"). Workers(2). BulkSize(2 << 20). // set the max bulk size to 2MB FlushInterval(10 * time.Second). Do(context.Background()) if err != nil { return err } defer p.Close() for _, x := range data { rec := elastic.NewBulkIndexRequest().Index("kinesis-apm").Type("doc").Doc(x.Kinesis.Data) p.Add(rec) } return p.Flush() } ================================================ FILE: aws-golang-stream-kinesis-to-elasticsearch/main.go ================================================ package main import ( "context" "os" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" elastic "github.com/serverless/examples/aws-golang-stream-kinesis-to-elasticsearch/elastic" ) func handler(ctx context.Context, event events.KinesisEvent) error { client, err := elastic.NewClient(os.Getenv("ELASTICSEARCH_HOST"), os.Getenv("ELASTICSEARCH_SCHEMA")) if err != nil { return err } return client.PushRecords(event.Records) } func main() { lambda.Start(handler) } ================================================ FILE: aws-golang-stream-kinesis-to-elasticsearch/package.json ================================================ { "name": "aws-golang-stream-kinesis-to-elasticsearch", "version": "0.0.1", "description": "Pull data from AWS Kinesis streams and forward to elasticsearch", "author": "Sebastian Borza ", "license": "MIT" } ================================================ FILE: aws-golang-stream-kinesis-to-elasticsearch/serverless.yml ================================================ service: aws-golang-kinesis-to-elasticsearch frameworkVersion: ">=1.28.0 <2.0.0" provider: name: aws runtime: go1.x environment: ELASTICSEARCH_HOST: my.elastic.host ELASTICSEARCH_SCHEMA: http functions: streamer: handler: bin/stream events: - stream: type: kinesis arn: arn:aws:kinesis:::stream/ batchSize: 100 startingPosition: TRIM_HORIZON enabled: true package: exclude: - ./** include: - ./bin/** ================================================ FILE: aws-java-simple-http-endpoint/.gitignore ================================================ *.class .gradle /build/ /bin/ /.settings/ .project .classpath target # Package Files *.jar *.war *.ear # Serverless directories .serverless ================================================ FILE: aws-java-simple-http-endpoint/README.md ================================================ # Simple HTTP Endpoint Example This example demonstrates how to setup a simple HTTP GET endpoint using Java. Once you fetch it, it will reply with the current time. [Jackson](https://github.com/FasterXML/jackson) is used to serialize objects to JSON. ## Use Cases - Wrapping an existing internal or external endpoint/service ## Build It is required to build prior to deploying. You can build the deployment artifact using Gradle or Maven. ### Gradle In order to build using Gradle simply run ```bash gradle wrapper # to build the gradle wrapper jar ./gradlew build # to build the application jar ``` The expected result should be similar to: ```bash Starting a Gradle Daemon, 1 incompatible Daemon could not be reused, use --status for details :compileJava :processResources :classes :jar :assemble :buildZip :compileTestJava UP-TO-DATE :processTestResources UP-TO-DATE :testClasses UP-TO-DATE :test UP-TO-DATE :check UP-TO-DATE :build BUILD SUCCESSFUL Total time: 8.195 secs ``` ### Maven In order to build using Maven simply run ```bash mvn package ``` Note: you can install Maven with 1. [sdkman](http://sdkman.io/) using `sdk install maven` (yes, use as default) 2. `sudo apt-get install mvn` 3. `brew install maven` If you use Maven to build, then in `serverless.yml` you have to replace ```yaml package: artifact: build/distributions/aws-java-simple-http-endpoint.zip ``` with ```yaml package: artifact: target/aws-java-simple-http-endpoint.jar ``` before deploying. ## Deploy After having built the deployment artifact using Gradle or Maven as described above you can deploy by simply running ```bash serverless deploy ``` The expected result should be similar to: ```bash Serverless: Creating Stack... Serverless: Checking Stack create progress... ..... Serverless: Stack create finished... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading service .zip file to S3... Serverless: Updating Stack... Serverless: Checking Stack update progress... .............................. Serverless: Stack update finished... Service Information service: aws-java-simple-http-endpoint stage: dev region: us-east-1 api keys: None endpoints: GET - https://XXXXXXX.execute-api.us-east-1.amazonaws.com/time functions: aws-java-simple-http-endpoint-dev-currentTime: arn:aws:lambda:us-east-1:XXXXXXX:function:aws-java-simple-http-endpoint-dev-currentTime ``` ## Usage You can now invoke the Lambda function directly and even see the resulting log via ```bash serverless invoke --function currentTime --log ``` The expected result should be similar to: ```bash { "statusCode": 200, "body": "{\"message\":\"Hello, the current time is Wed Jan 04 23:44:37 UTC 2017\"}", "headers": { "X-Powered-By": "AWS Lambda & Serverless", "Content-Type": "application/json" }, "isBase64Encoded": false } -------------------------------------------------------------------- START RequestId: XXXXXXX Version: $LATEST 2004 23:44:37 INFO com.serverless.Handler:18 - received: {} END RequestId: XXXXXXX REPORT RequestId: XXXXXXX Duration: 0.51 ms Billed Duration: 100 ms Memory Size: 1024 MB Max Memory Used: 53 MB ``` Finally you can send an HTTP request directly to the endpoint using a tool like curl ```bash curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/time ``` The expected result should be similar to: ```bash {"message": "Hello, the current time is Wed Jan 04 23:44:37 UTC 2017"}% ``` ## Scaling By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 1000. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit). ================================================ FILE: aws-java-simple-http-endpoint/build.gradle ================================================ apply plugin: 'java' repositories { mavenCentral() } sourceCompatibility = 1.8 targetCompatibility = 1.8 dependencies { compile ( 'com.amazonaws:aws-lambda-java-core:1.1.0', 'com.amazonaws:aws-lambda-java-log4j:1.0.0', 'com.fasterxml.jackson.core:jackson-core:2.8.5', 'com.fasterxml.jackson.core:jackson-databind:2.8.5', 'com.fasterxml.jackson.core:jackson-annotations:2.8.5' ) } // http://docs.aws.amazon.com/lambda/latest/dg/create-deployment-pkg-zip-java.html task buildZip(type: Zip) { baseName = "aws-java-simple-http-endpoint" from compileJava from processResources into('lib') { from configurations.runtime } } build.dependsOn buildZip task wrapper(type: Wrapper) { gradleVersion = '3.2.1' } ================================================ FILE: aws-java-simple-http-endpoint/gradle/wrapper/gradle-wrapper.properties ================================================ #Sat Jan 14 14:33:39 PST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-3.2.1-bin.zip ================================================ FILE: aws-java-simple-http-endpoint/gradlew ================================================ #!/usr/bin/env sh ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn ( ) { echo "$*" } die ( ) { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save ( ) { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=$(save "$@") # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then cd "$(dirname "$0")" fi exec "$JAVACMD" "$@" ================================================ FILE: aws-java-simple-http-endpoint/gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: aws-java-simple-http-endpoint/pom.xml ================================================ 4.0.0 com.serverless aws-java-simple-http-endpoint jar dev aws-java-simple-http-endpoint 1.8 1.8 UTF-8 com.amazonaws aws-lambda-java-core 1.1.0 com.amazonaws aws-lambda-java-log4j 1.0.0 com.fasterxml.jackson.core jackson-core 2.9.8 com.fasterxml.jackson.core jackson-databind 2.10.0.pr1 com.fasterxml.jackson.core jackson-annotations 2.9.8 org.apache.maven.plugins maven-shade-plugin 2.3 false package shade ${project.artifactId} ================================================ FILE: aws-java-simple-http-endpoint/serverless.yml ================================================ service: aws-java-simple-http-endpoint frameworkVersion: '2' provider: name: aws runtime: java8 package: artifact: build/distributions/aws-java-simple-http-endpoint.zip functions: currentTime: handler: com.serverless.Handler events: - httpApi: path: /time method: get ================================================ FILE: aws-java-simple-http-endpoint/src/main/java/com/serverless/ApiGatewayResponse.java ================================================ package com.serverless; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Collections; import java.util.Map; import org.apache.log4j.Logger; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; public class ApiGatewayResponse { private final int statusCode; private final String body; private final Map headers; private final boolean isBase64Encoded; public ApiGatewayResponse(int statusCode, String body, Map headers, boolean isBase64Encoded) { this.statusCode = statusCode; this.body = body; this.headers = headers; this.isBase64Encoded = isBase64Encoded; } public int getStatusCode() { return statusCode; } public String getBody() { return body; } public Map getHeaders() { return headers; } // API Gateway expects the property to be called "isBase64Encoded" => isIs public boolean isIsBase64Encoded() { return isBase64Encoded; } public static Builder builder() { return new Builder(); } public static class Builder { private static final Logger LOG = Logger.getLogger(ApiGatewayResponse.Builder.class); private static final ObjectMapper objectMapper = new ObjectMapper(); private int statusCode = 200; private Map headers = Collections.emptyMap(); private String rawBody; private Object objectBody; private byte[] binaryBody; private boolean base64Encoded; public Builder setStatusCode(int statusCode) { this.statusCode = statusCode; return this; } public Builder setHeaders(Map headers) { this.headers = headers; return this; } /** * Builds the {@link ApiGatewayResponse} using the passed raw body string. */ public Builder setRawBody(String rawBody) { this.rawBody = rawBody; return this; } /** * Builds the {@link ApiGatewayResponse} using the passed object body * converted to JSON. */ public Builder setObjectBody(Object objectBody) { this.objectBody = objectBody; return this; } /** * Builds the {@link ApiGatewayResponse} using the passed binary body * encoded as base64. {@link #setBase64Encoded(boolean) * setBase64Encoded(true)} will be in invoked automatically. */ public Builder setBinaryBody(byte[] binaryBody) { this.binaryBody = binaryBody; setBase64Encoded(true); return this; } /** * A binary or rather a base64encoded responses requires *
    *
  1. "Binary Media Types" to be configured in API Gateway *
  2. a request with an "Accept" header set to one of the "Binary Media * Types" *
*/ public Builder setBase64Encoded(boolean base64Encoded) { this.base64Encoded = base64Encoded; return this; } public ApiGatewayResponse build() { String body = null; if (rawBody != null) { body = rawBody; } else if (objectBody != null) { try { body = objectMapper.writeValueAsString(objectBody); } catch (JsonProcessingException e) { LOG.error("failed to serialize object", e); throw new RuntimeException(e); } } else if (binaryBody != null) { body = new String(Base64.getEncoder().encode(binaryBody), StandardCharsets.UTF_8); } return new ApiGatewayResponse(statusCode, body, headers, base64Encoded); } } } ================================================ FILE: aws-java-simple-http-endpoint/src/main/java/com/serverless/Handler.java ================================================ package com.serverless; import java.util.Date; import java.util.HashMap; import java.util.Map; import org.apache.log4j.Logger; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; public class Handler implements RequestHandler, ApiGatewayResponse> { private static final Logger LOG = Logger.getLogger(Handler.class); @Override public ApiGatewayResponse handleRequest(Map input, Context context) { LOG.info("received: " + input); Response responseBody = new Response("Hello, the current time is " + new Date()); Map headers = new HashMap<>(); headers.put("X-Powered-By", "AWS Lambda & Serverless"); headers.put("Content-Type", "application/json"); return ApiGatewayResponse.builder() .setStatusCode(200) .setObjectBody(responseBody) .setHeaders(headers) .build(); } } ================================================ FILE: aws-java-simple-http-endpoint/src/main/java/com/serverless/Response.java ================================================ package com.serverless; public class Response { private final String message; public Response(String message) { this.message = message; } public String getMessage() { return this.message; } } ================================================ FILE: aws-java-simple-http-endpoint/src/main/resources/log4j.properties ================================================ log = . log4j.rootLogger = DEBUG, LAMBDA log4j.appender.LAMBDA=com.amazonaws.services.lambda.runtime.log4j.LambdaAppender log4j.appender.LAMBDA.layout=org.apache.log4j.PatternLayout log4j.appender.LAMBDA.layout.conversionPattern=%d{yyyy-MM-dd HH:mm:ss} <%X{AWSRequestId}> %-5p %c:%L - %m%n ================================================ FILE: aws-multiple-runtime/README.md ================================================ ================================================ FILE: aws-multiple-runtime/api/.gitignore ================================================ # package directories node_modules jspm_packages # Serverless directories .serverless ================================================ FILE: aws-multiple-runtime/api/handler.js ================================================ 'use strict'; module.exports.timestamp = (event, context, callback) => { const response = { statusCode: 200, headers: { 'Content-Type': 'text/plain', }, body: parseInt(Date.now() / 1000, 10), }; callback(null, response); }; ================================================ FILE: aws-multiple-runtime/serverless.yml ================================================ service: hellotime-app provider: name: aws functions: hello: runtime: python3.6 events: - httpApi: method: get path: /greet handler: web/handler.hello time: runtime: nodejs12.x events: - httpApi: method: get path: /time handler: api/handler.timestamp ================================================ FILE: aws-multiple-runtime/web/handler.py ================================================ from datetime import datetime import http.client def hello(event, context): rc = event["requestContext"] servicePath = rc["path"][:-len(rc["resourcePath"])] # path minus the resource path '/greet' # GET from the /time endpoint connection = http.client.HTTPSConnection(event["headers"]["Host"]) connection.request("GET", "{0}/time".format(servicePath)) timestamp = connection.getresponse().read().decode() timeStr = datetime.fromtimestamp(int(timestamp)).strftime("%B %d, %Y") return { "statusCode": 200, "body": "

Hello! It is now {0}.

".format(timeStr), "headers": { "Content-Type": "text/html" } } ================================================ FILE: aws-node/.gitignore ================================================ node_modules .serverless ================================================ FILE: aws-node/README.md ================================================ # Serverless Framework AWS NodeJS Example This template demonstrates how to deploy a simple NodeJS function running on AWS Lambda using the Serverless Framework. The deployed function does not include any event definitions or any kind of persistence (database). For more advanced configurations check out the [examples repo](https://github.com/serverless/examples/) which include use cases like API endpoints, workers triggered by SQS, persistence with DynamoDB, and scheduled tasks. For details about configuration of specific events, please refer to our [documentation](https://www.serverless.com/framework/docs/providers/aws/events/). ## Usage ### Deployment In order to deploy the example, you need to run the following command: ``` serverless deploy ``` After running deploy, you should see output similar to: ``` Deploying "aws-node" to stage "dev" (us-east-1) ✔ Service deployed to stack aws-node-dev (90s) functions: hello: aws-node-dev-hello (1.5 kB) ``` ### Invocation After successful deployment, you can invoke the deployed function by using the following command: ``` serverless invoke --function hello ``` Which should result in response similar to the following: ```json { "statusCode": 200, "body": "{\"message\":\"Go Serverless v4.0! Your function executed successfully!\"}" } ``` ### Local development The easiest way to develop and test your function is to use the Serverless Framework's `dev` command: ``` serverless dev ``` This will start a local emulator of AWS Lambda and tunnel your requests to and from AWS Lambda, allowing you to interact with your function as if it were running in the cloud. Now you can invoke the function as before, but this time the function will be executed locally. Now you can develop your function locally, invoke it, and see the results immediately without having to re-deploy. When you are done developing, don't forget to run `serverless deploy` to deploy the function to the cloud. ================================================ FILE: aws-node/handler.js ================================================ exports.hello = async (event) => { return { statusCode: 200, body: JSON.stringify({ message: 'Go Serverless v4.0! Your function executed successfully!' }) }; }; ================================================ FILE: aws-node/serverless.yml ================================================ service: aws-node # NOTE: update this with your service name frameworkVersion: '4' provider: name: aws runtime: nodejs20.x functions: hello: handler: handler.hello ================================================ FILE: aws-node-alexa-skill/.gitignore ================================================ node_modules .serverless ================================================ FILE: aws-node-alexa-skill/README.md ================================================ # Serverless Alexa Skill Example This example demonstrates how to setup your own Alexa skill using AWS Lambdas. ## Use-cases - Building custom Alexa skills ## How it works In the Alexa Developer Portal you can add your own skill. To do so you need to define the available intents and then connect them to a Lambda. You can update and define the Lambda with Serverless. ## Setup In order to deploy the endpoint simply run ```bash serverless deploy ``` The expected result should be similar to: ```bash Serverless: Packaging service... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading service .zip file to S3 (378 B)... Serverless: Updating Stack... Serverless: Checking Stack update progress... ......... Serverless: Stack update finished... Serverless: Removing old service versions... Service Information service: aws-node-alexa-skill-2 stage: dev region: us-east-1 api keys: None endpoints: None functions: aws-node-alexa-skill-2-dev-luckyNumber: arn:aws:lambda:us-east-1:377024778620:function:aws-node-alexa-skill-2-dev-luckyNumber ``` Next we need to setup an Alexa skill. Once you've signed up for the Amazon Developer Platform visit `https://developer.amazon.com/edw/home.html`. There you should see the following screen: ![Welcome](https://cloud.githubusercontent.com/assets/223045/21183285/8403b37c-c211e6-89c0-d36582010af8.png) Next click on `Add a new Skill`: ![Add Skill](https://cloud.githubusercontent.com/assets/223045/21183286/840512c211e6-84945b6b45e83b.png) Go through the steps and fill in all the required fields e.g. Intent Schema and Sample Utterances: Intent Schema ``` { "intents": [ { "intent": "GetLuckyNumbers", "slots": [ { "name": "UpperLimit", "type": "AMAZON.NUMBER" } ] } ] } ``` Sample Utterances ``` GetLuckyNumbers what are my lucky numbers GetLuckyNumbers tell me my lucky numbers GetLuckyNumbers what are my lucky numbers lower than {UpperLimit} GetLuckyNumbers tell me my lucky numbers lower than {UpperLimit} ``` ![Skill Information](https://cloud.githubusercontent.com/assets/223045/21183279/83eec4c211e6-841b-d8925f0804a5.png) ![Interaction Model](https://cloud.githubusercontent.com/assets/223045/21183280/83ef3dc211e6-87a5-bb8dcbb903f8.png) Fill in the Lambda ARN which was printed or run `serverless info` to retrieve the ARN again. ![Configuration](https://cloud.githubusercontent.com/assets/223045/21183281/83f170c211e6-89b7-2f6d96ac559c.png) Next up visit the test page, fill in the utterance and click on `Ask LuckyNumbers`. ![Test](https://cloud.githubusercontent.com/assets/223045/21183283/83f1f6c211e6-858d-41b1a3154e91.png) ![Test](https://cloud.githubusercontent.com/assets/223045/21183282/83f1f6c211e6-974e-b7c051ffb6eb.png) ![Test](https://cloud.githubusercontent.com/assets/223045/21183284/83f708ac-c211e6-819489e8f3e494.png) ![Test](https://cloud.githubusercontent.com/assets/223045/21185805/78c1dfc211e6-9cf9-ce44edc30cdd.gif) You should have received a response containing the text `Your lucky number is` followed by your lucky number :) Check out this [Amazon guide](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/overviews/steps-to-build-a-custom-skill#your-skill-is-published-now-what) to learn more about how to submit your skill for publication. ================================================ FILE: aws-node-alexa-skill/handler.js ================================================ 'use strict'; // Returns a random integer between min (inclusive) and max (inclusive) const getRandomInt = (min, max) => Math.floor(Math.random() * ((max - min) + 1)) + min; module.exports.luckyNumber = (event, context, callback) => { const upperLimit = event.request.intent.slots.UpperLimit.value || 100; const number = getRandomInt(0, upperLimit); const response = { version: '1.0', response: { outputSpeech: { type: 'PlainText', text: `Your lucky number is ${number}`, }, shouldEndSession: false, }, }; callback(null, response); }; ================================================ FILE: aws-node-alexa-skill/package.json ================================================ { "name": "aws-alexa-skill", "version": "1.0.0", "description": "This example demonstrates how to use an AWS Lambdas for your custom Alexa skill.", "author": "", "license": "MIT" } ================================================ FILE: aws-node-alexa-skill/serverless.yml ================================================ service: aws-node-alexa-skill frameworkVersion: ">=1.4.0 <2.0.0" provider: name: aws runtime: nodejs12.x functions: luckyNumber: handler: handler.luckyNumber events: - alexaSkill ================================================ FILE: aws-node-auth0-cognito-custom-authorizers-api/.gitignore ================================================ node_modules .serverless ================================================ FILE: aws-node-auth0-cognito-custom-authorizers-api/README.md ================================================ # API Gateway Authorizer Function for Auth0 or AWS Cognito using the [JWKS](https://auth0.com/docs/jwks) method. This is an example of how to protect API endpoints with [Auth0](https://auth0.com/) or [AWS Cognito](https://aws.amazon.com/cognito/) using JSON Web Key Sets ([JWKS](https://auth0.com/docs/jwks)) and a [custom authorizer lambda function](https://serverless.com/framework/docs/providers/aws/events/apigateway#http-endpoints-with-custom-authorizers). Custom Authorizers allow you to run an AWS Lambda Function via API Gateway before your targeted AWS Lambda Function is run. This is useful for Microservice Architectures or when you simply want to do some Authorization before running your business logic. ## Use cases - Protect API routes for authorized users - Rate limiting APIs - Remotely revoke tokens ## Setup 1. `npm install` json web token dependencies 2. In [auth.js](auth.js#L10) replace the value of `iss` with either your [Auth0 iss](http://bit.ly/2hoeRXk) or [AWS Cognito ISS](http://amzn.to/2fo77UI). Make sure the `iss` url ends in a trailing `/`. ```js /* auth.js */ // Replace with your auth0 or Cognito values const iss = "https://.com/"; ``` 3. Deploy the service with `sls deploy` and grab the public and private endpoints. ## Test Authentication: - Test with [Postman](https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop?hl=en): Make a new GET request with the Header containing "Authorization" with the value being "bearer ``" for your `api/private` url. - Test using curl: ```sh curl --header "Authorization: bearer " https://{api}.execute-api.{region}.amazonaws.com/api/private ``` ================================================ FILE: aws-node-auth0-cognito-custom-authorizers-api/auth.js ================================================ 'use strict'; const jwk = require('jsonwebtoken'); const jwkToPem = require('jwk-to-pem'); const request = require('request'); // For Auth0: https://.auth0.com/ // refer to: http://bit.ly/2hoeRXk // For AWS Cognito: https://cognito-idp..amazonaws.com/ // refer to: http://amzn.to/2fo77UI const iss = 'https://.com/'; // Generate policy to allow this user on this API: const generatePolicy = (principalId, effect, resource) => { const authResponse = {}; authResponse.principalId = principalId; if (effect && resource) { const policyDocument = {}; policyDocument.Version = '2012-10-17'; policyDocument.Statement = []; const statementOne = {}; statementOne.Action = 'execute-api:Invoke'; statementOne.Effect = effect; statementOne.Resource = resource; policyDocument.Statement[0] = statementOne; authResponse.policyDocument = policyDocument; } return authResponse; }; // Reusable Authorizer function, set on `authorizer` field in serverless.yml module.exports.authorize = (event, context, cb) => { console.log('Auth function invoked'); if (event.authorizationToken) { // Remove 'bearer ' from token: const token = event.authorizationToken.substring(7); // Make a request to the iss + .well-known/jwks.json URL: request( { url: `${iss}/.well-known/jwks.json`, json: true }, (error, response, body) => { if (error || response.statusCode !== 200) { console.log('Request error:', error); cb('Unauthorized'); } const keys = body; // Based on the JSON of `jwks` create a Pem: const k = keys.keys[0]; const jwkArray = { kty: k.kty, n: k.n, e: k.e, }; const pem = jwkToPem(jwkArray); // Verify the token: jwk.verify(token, pem, { issuer: iss }, (err, decoded) => { if (err) { console.log('Unauthorized user:', err.message); cb('Unauthorized'); } else { cb(null, generatePolicy(decoded.sub, 'Allow', event.methodArn)); } }); }); } else { console.log('No authorizationToken found in the header.'); cb('Unauthorized'); } }; ================================================ FILE: aws-node-auth0-cognito-custom-authorizers-api/handler.js ================================================ 'use strict'; // Public API module.exports.publicEndpoint = (event, context, cb) => { cb(null, { message: 'Welcome to our Public API!' }); }; // Private API module.exports.privateEndpoint = (event, context, cb) => { cb(null, { message: 'Only logged in users can see this' }); }; ================================================ FILE: aws-node-auth0-cognito-custom-authorizers-api/package.json ================================================ { "name": "aws-node-auth0-cognito-custom-authorizers-api", "version": "1.0.0", "description": "Authorize your API Gateway with either Auth0 or Cognito RS256 tokens.", "license": "MIT", "dependencies": { "jsonwebtoken": "^7.1.9", "jwk-to-pem": "^1.2.6", "request": "^2.82.0" }, "author": "Shahzeb Khan" } ================================================ FILE: aws-node-auth0-cognito-custom-authorizers-api/serverless.yml ================================================ service: aws-node-auth0-cognito-custom-authorizers-api provider: name: aws runtime: nodejs12.x functions: publicEndpoint: handler: handler.publicEndpoint events: - http: path: api/public method: get integration: lambda cors: true auth: handler: auth.authorize privateEndpoint: handler: handler.privateEndpoint events: - http: path: api/private method: get authorizer: auth cors: origins: - '*' headers: - Content-Type - X-Amz-Date - Authorization - X-Api-Key - X-Amz-Security-Token ================================================ FILE: aws-node-auth0-custom-authorizers-api/.gitignore ================================================ node_modules .serverless secrets.json public_key /frontend/misc.md ================================================ FILE: aws-node-auth0-custom-authorizers-api/README.md ================================================ # API Gateway Custom Authorizer Function + Auth0 This is an example of how to protect API endpoints with [auth0](https://auth0.com/), JSON Web Tokens (jwt) and a [custom authorizer lambda function](https://serverless.com/framework/docs/providers/aws/events/apigateway#http-endpoints-with-custom-authorizers). Custom Authorizers allow you to run an AWS Lambda Function before your targeted AWS Lambda Function. This is useful for Microservice Architectures or when you simply want to do some Authorization before running your business logic. ### [View live demo](http://auth0-serverless-protected-routes-demo.surge.sh/) ## Use cases - Protect API routes for authorized users - Rate limiting APIs ## Setup 1. `npm install` json web token dependencies 2. Setup an [auth0 application](https://auth0.com/docs/applications). 3. Get your `Client ID` (under `applications->${YOUR_APP_NAME}->settings`) and plugin your `AUTH0_CLIENT_ID` in a new file called `secrets.json` (based on `secrets.example.json`). 4. Get your `public key` (under `applications->${YOUR_APP_NAME}->settings->Show Advanced Settings->Certificates->DOWNLOAD CERTIFICATE`). Download it as `PEM` format and save it as a new file called `public_key` 5. Deploy the service with `serverless deploy` and grab the public and private endpoints. 6. Plugin your `AUTH0_CLIENT_ID`, `AUTH0_DOMAIN`, and the `PUBLIC_ENDPOINT` + `PRIVATE_ENDPOINT` from aws in top of the `frontend/app.js` file. ```js /* frontend/app.js */ // replace these values in app.js const AUTH0_CLIENT_ID = 'your-auth0-client-id-here'; const AUTH0_DOMAIN = 'your-auth0-domain-here.auth0.com'; const PUBLIC_ENDPOINT = 'https://your-aws-endpoint-here.amazonaws.com/dev/api/public'; const PRIVATE_ENDPOINT = 'https://your-aws-endpoint-here.us-east-1.amazonaws.com/dev/api/private'; ``` 7. Deploy Frontend to host of your choosing and make sure to configure the `Allowed Callback URL` and `Allowed Origins` in your auth0 client in the [auth0 dashboard](https://manage.auth0.com). We used `http://auth0-serverless-protected-routes-demo.surge.sh/` for our demo. ## Custom authorizer functions [Custom authorizers functions](https://aws.amazon.com/blogs/compute/introducing-custom-authorizers-in-amazon-api-gateway/) are executed before a Lambda function is executed and return an Error or a Policy document. The Custom authorizer function is passed an `event` object as below: ```javascript { "type": "TOKEN", "authorizationToken": "", "methodArn": "arn:aws:execute-api:::///" } ``` ## Frontend The frontend is a bare bones vanilla javascript implementation. You can replace it with whatever frontend framework you like =) If you do implement in another framework, please consider adding it our [growing list of examples](https://github.com/serverless/examples/)! API calls are made with the browser's native `fetch` api. ================================================ FILE: aws-node-auth0-custom-authorizers-api/frontend/app.css ================================================ html, body { padding: 0px; margin: 0px; } body { font-family: "proxima-nova", sans-serif; text-align: center; font-size: 14px; font-weight: 100; } h1, h2, h3 { font-weight: 100; } #message { min-height: 40px; font-size: 22px; } #logo img { width: 100px; margin-bottom: 10px; margin-top: 50px; } .btn { font-size: 16px; letter-spacing: 1px; border: 0; background-color: #16214D; color: white; min-width: 80px; min-height: 30px; display: flex; align-items: center; justify-content: center; cursor: pointer; min-width: 200px; } .btn:hover { background-color: #44C7F4; } .btn:focus { outline: none !important; } .btn:disabled { background-color: #333; color: #666; } .api-actions, .user-actions { display: flex; align-items: center; justify-content: center; margin-bottom: 30px; } .api-actions button:last-of-type, .user-actions button:last-of-type { margin-left: 20px; } @media (max-width: 768px) { .api-actions, .user-actions { flex-direction: column; } .api-actions button:last-of-type, .user-actions button:last-of-type { margin-left: 0px; margin-top: 20px; } } ================================================ FILE: aws-node-auth0-custom-authorizers-api/frontend/app.js ================================================ /* global window document localStorage fetch alert */ // Fill in with your values const AUTH0_CLIENT_ID = 'your-auth0-client-id-here'; const AUTH0_DOMAIN = 'your-auth0-domain-here.auth0.com'; const AUTH0_CALLBACK_URL = window.location.href; // eslint-disable-line const PUBLIC_ENDPOINT = 'https://your-aws-endpoint-here.amazonaws.com/dev/api/public'; const PRIVATE_ENDPOINT = 'https://your-aws-endpoint-here.us-east-1.amazonaws.com/dev/api/private'; // initialize auth0 lock const lock = new Auth0Lock(AUTH0_CLIENT_ID, AUTH0_DOMAIN, { // eslint-disable-line no-undef auth: { params: { scope: 'openid email', }, responseType: 'token id_token', }, }); function updateUI() { const isLoggedIn = localStorage.getItem('id_token'); if (isLoggedIn) { // swap buttons document.getElementById('btn-login').style.display = 'none'; document.getElementById('btn-logout').style.display = 'inline'; const profile = JSON.parse(localStorage.getItem('profile')); // show username document.getElementById('nick').textContent = profile.email; } } // Handle login lock.on('authenticated', (authResult) => { console.log(authResult); lock.getUserInfo(authResult.accessToken, (error, profile) => { if (error) { // Handle error return; } document.getElementById('nick').textContent = profile.nickname; localStorage.setItem('accessToken', authResult.accessToken); localStorage.setItem('id_token', authResult.idToken); localStorage.setItem('profile', JSON.stringify(profile)); updateUI(); }); }); updateUI(); // Handle login document.getElementById('btn-login').addEventListener('click', () => { lock.show(); }); // Handle logout document.getElementById('btn-logout').addEventListener('click', () => { localStorage.removeItem('id_token'); localStorage.removeItem('access_token'); localStorage.removeItem('profile'); document.getElementById('btn-login').style.display = 'flex'; document.getElementById('btn-logout').style.display = 'none'; document.getElementById('nick').textContent = ''; }); // Handle public api call document.getElementById('btn-public').addEventListener('click', () => { // call public API fetch(PUBLIC_ENDPOINT, { cache: 'no-store', method: 'POST', }) .then(response => response.json()) .then((data) => { console.log('Message:', data); document.getElementById('message').textContent = ''; document.getElementById('message').textContent = data.message; }) .catch((e) => { console.log('error', e); }); }); // Handle private api call document.getElementById('btn-private').addEventListener('click', () => { // Call private API with JWT in header const token = localStorage.getItem('id_token'); /* // block request from happening if no JWT token present if (!token) { document.getElementById('message').textContent = '' document.getElementById('message').textContent = 'You must login to call this protected endpoint!' return false }*/ // Do request to private endpoint fetch(PRIVATE_ENDPOINT, { method: 'POST', headers: { Authorization: `Bearer ${token}`, }, }) .then(response => response.json()) .then((data) => { console.log('Token:', data); document.getElementById('message').textContent = ''; document.getElementById('message').textContent = data.message; }) .catch((e) => { console.log('error', e); }); }); ================================================ FILE: aws-node-auth0-custom-authorizers-api/frontend/index.html ================================================

Auth0 Serverless Example

Welcome

View the source on github
================================================ FILE: aws-node-auth0-custom-authorizers-api/handler.js ================================================ const jwt = require('jsonwebtoken'); // Set in `environment` of serverless.yml const AUTH0_CLIENT_ID = process.env.AUTH0_CLIENT_ID; const AUTH0_CLIENT_PUBLIC_KEY = process.env.AUTH0_CLIENT_PUBLIC_KEY; // Policy helper function const generatePolicy = (principalId, effect, resource) => { const authResponse = {}; authResponse.principalId = principalId; if (effect && resource) { const policyDocument = {}; policyDocument.Version = '2012-10-17'; policyDocument.Statement = []; const statementOne = {}; statementOne.Action = 'execute-api:Invoke'; statementOne.Effect = effect; statementOne.Resource = resource; policyDocument.Statement[0] = statementOne; authResponse.policyDocument = policyDocument; } return authResponse; }; // Reusable Authorizer function, set on `authorizer` field in serverless.yml module.exports.auth = (event, context, callback) => { console.log('event', event); if (!event.authorizationToken) { return callback('Unauthorized'); } const tokenParts = event.authorizationToken.split(' '); const tokenValue = tokenParts[1]; if (!(tokenParts[0].toLowerCase() === 'bearer' && tokenValue)) { // no auth token! return callback('Unauthorized'); } const options = { audience: AUTH0_CLIENT_ID, }; try { jwt.verify(tokenValue, AUTH0_CLIENT_PUBLIC_KEY, options, (verifyError, decoded) => { if (verifyError) { console.log('verifyError', verifyError); // 401 Unauthorized console.log(`Token invalid. ${verifyError}`); return callback('Unauthorized'); } // is custom authorizer function console.log('valid from customAuthorizer', decoded); return callback(null, generatePolicy(decoded.sub, 'Allow', event.methodArn)); }); } catch (err) { console.log('catch error. Invalid token', err); return callback('Unauthorized'); } }; // Public API module.exports.publicEndpoint = (event, context, callback) => callback(null, { statusCode: 200, headers: { /* Required for CORS support to work */ 'Access-Control-Allow-Origin': '*', /* Required for cookies, authorization headers with HTTPS */ 'Access-Control-Allow-Credentials': true, }, body: JSON.stringify({ message: 'Hi ⊂◉‿◉つ from Public API', }), }); // Private API module.exports.privateEndpoint = (event, context, callback) => callback(null, { statusCode: 200, headers: { /* Required for CORS support to work */ 'Access-Control-Allow-Origin': '*', /* Required for cookies, authorization headers with HTTPS */ 'Access-Control-Allow-Credentials': true, }, body: JSON.stringify({ message: 'Hi ⊂◉‿◉つ from Private API. Only logged in users can see this', }), }); ================================================ FILE: aws-node-auth0-custom-authorizers-api/package.json ================================================ { "name": "aws-auth0-api-gateway", "version": "1.0.0", "description": "Demonstration of protecting API gateway endpoints with auth0", "license": "MIT", "dependencies": { "jsonwebtoken": "^8.1.0" }, "devDependencies": { "serverless-offline": "^3.18.0" } } ================================================ FILE: aws-node-auth0-custom-authorizers-api/public_key-example ================================================ -----BEGIN CERTIFICATE----- PUBLIC KEY - can be found in `https://manage.auth0.com -> applications->${YOUR_APP_NAME}->settings->Show Advanced Settings->Certificates->DOWNLOAD CERTIFICATE.` You should download the certificate in PEM format and save it as a new file called `public_key` -----END CERTIFICATE----- ================================================ FILE: aws-node-auth0-custom-authorizers-api/secrets.example.json ================================================ { "AUTH0_CLIENT_ID": "your-client-id" } ================================================ FILE: aws-node-auth0-custom-authorizers-api/serverless.yml ================================================ service: aws-custom-authorizer-auth0 plugins: - serverless-offline provider: name: aws runtime: nodejs12.x region: us-west-2 environment: AUTH0_CLIENT_ID: ${file(./secrets.json):AUTH0_CLIENT_ID} AUTH0_CLIENT_PUBLIC_KEY: ${file(./public_key)} functions: auth: handler: handler.auth publicEndpoint: handler: handler.publicEndpoint events: - http: path: api/public method: post cors: true privateEndpoint: handler: handler.privateEndpoint events: - http: path: api/private method: post # See custom authorizer docs here: http://bit.ly/2gXw9pO authorizer: auth cors: true resources: Resources: # This response is needed for custom authorizer failures cors support ¯\_(ツ)_/¯ GatewayResponse: Type: 'AWS::ApiGateway::GatewayResponse' Properties: ResponseParameters: gatewayresponse.header.Access-Control-Allow-Origin: "'*'" gatewayresponse.header.Access-Control-Allow-Headers: "'*'" ResponseType: EXPIRED_TOKEN RestApiId: Ref: 'ApiGatewayRestApi' StatusCode: '401' AuthFailureGatewayResponse: Type: 'AWS::ApiGateway::GatewayResponse' Properties: ResponseParameters: gatewayresponse.header.Access-Control-Allow-Origin: "'*'" gatewayresponse.header.Access-Control-Allow-Headers: "'*'" ResponseType: UNAUTHORIZED RestApiId: Ref: 'ApiGatewayRestApi' StatusCode: '401' ================================================ FILE: aws-node-cdk-extension/README.md ================================================ # Welcome to your CDK JavaScript project This is a blank project for CDK development with JavaScript. The `cdk.json` file tells the CDK Toolkit how to execute your app. The build step is not required when using JavaScript. ## Useful commands * `serverless ext cdk deploy` deploy this stack to your default AWS account/region * `serverless ext cdk diff` compare deployed stack with current state * `serverless ext cdk synth` emits the synthesized CloudFormation template ================================================ FILE: aws-node-cdk-extension/bin/example.js ================================================ #!/usr/bin/env node const cdk = require('aws-cdk-lib'); const { ExampleStack } = require('../lib/example-stack'); const app = new cdk.App(); new ExampleStack(app, 'ExampleStack', { /* If you don't specify 'env', this stack will be environment-agnostic. * Account/Region-dependent features and context lookups will not work, * but a single synthesized template can be deployed anywhere. */ /* Uncomment the next line to specialize this stack for the AWS Account * and Region that are implied by the current CLI configuration. */ // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }, /* Uncomment the next line if you know exactly what Account and Region you * want to deploy the stack to. */ // env: { account: '123456789012', region: 'us-east-1' }, /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */ }); ================================================ FILE: aws-node-cdk-extension/cdk.json ================================================ { "app": "node bin/example.js", "progress": "events", "watch": { "include": [ "**" ], "exclude": [ "README.md", "cdk*.json", "jest.config.js", "package*.json", "yarn.lock", "node_modules", "test" ] }, "context": { "@aws-cdk/aws-lambda:recognizeLayerVersion": true, "@aws-cdk/core:checkSecretUsage": true, "@aws-cdk/core:target-partitions": [ "aws", "aws-cn" ], "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, "@aws-cdk/aws-iam:minimizePolicies": true, "@aws-cdk/core:validateSnapshotRemovalPolicy": true, "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, "@aws-cdk/core:enablePartitionLiterals": true, "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, "@aws-cdk/aws-iam:standardizedServicePrincipals": true, "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, "@aws-cdk/aws-route53-patters:useCertificate": true, "@aws-cdk/customresources:installLatestAwsSdkDefault": false, "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, "@aws-cdk/aws-redshift:columnId": true, "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, "@aws-cdk/aws-kms:aliasNameRef": true, "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, "@aws-cdk/aws-efs:denyAnonymousAccess": true, "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true } } ================================================ FILE: aws-node-cdk-extension/lib/example-stack.js ================================================ const { Stack, Duration, CfnOutput } = require('aws-cdk-lib') const sqs = require('aws-cdk-lib/aws-sqs') class ExampleStack extends Stack { /** * * @param {Construct} scope * @param {string} id * @param {StackProps=} props */ constructor (scope, id, props) { super(scope, id, props) // The code that defines your stack goes here // example resource const queue = new sqs.Queue(this, 'ExampleQueue', { visibilityTimeout: Duration.seconds(300) }) // eslint-disable-next-line @typescript-eslint/no-unused-vars const queueUrlOutput = new CfnOutput(this, 'queueUrl', { value: queue.queueUrl }) // eslint-disable-next-line @typescript-eslint/no-unused-vars const customOutput = new CfnOutput(this, 'customOutput', { value: 'customOutputValue', description: 'This is a custom output' }) } } module.exports = { ExampleStack } ================================================ FILE: aws-node-cdk-extension/package.json ================================================ { "name": "example", "version": "0.1.0", "bin": { "example": "bin/example.js" }, "devDependencies": { "aws-cdk": "2.131.0", "jest": "^29.7.0" }, "dependencies": { "aws-cdk-lib": "2.131.0", "constructs": "^10.0.0" } } ================================================ FILE: aws-node-cdk-extension/serverless.yml ================================================ service: aws-node-cdk-extension cdk-instance: extension: serverless/cdk@latest config: region: us-east-2 ================================================ FILE: aws-node-dynamic-image-resizer/Dockerfile ================================================ FROM amazonlinux WORKDIR /deploy RUN yum -y install make gcc* RUN curl --silent --location https://rpm.nodesource.com/setup_8.x | bash - RUN yum -y install nodejs RUN npm install -g serverless COPY . . RUN npm i --production RUN ["chmod", "+x", "deploy.sh"] CMD ./deploy.sh ; sleep 2m ================================================ FILE: aws-node-dynamic-image-resizer/README.md ================================================ # Dynamic image resizing with Node.js and Serverless framework In this example, we set up a dynamic image resizing solution with AWS S3 and a Serverless framework function written in Node.js. We use [the `sharp` package](https://www.npmjs.com/package/sharp) for image resizing. `sharp` includes native dependencies, so in this example we are building and deploying the Serverless function from a Docker container that’s based on Amazon Linux. ## Pre-requisites In order to deploy the function, you will need the following: - API credentials for AWS, with Administrator permissions (for simplicity, not recommended in production). - An S3 bucket in your AWS account. - Serverless framework installed locally via `yarn global add serverless`. - Node.js 8 and `yarn` installed locally. - Docker and docker-compose installed locally. ## Deploying the Serverless project 1. Clone the repository and install the dependencies:\ ``` yarn ``` 2. Add your AWS credentials into the `secrets/secrets.env` file. 3. Deploy the Serverless project:\ ``` docker-compose up --build ``` ## Setting up the S3 bucket Make sure that your S3 bucket is public. Then follow these additional setup steps: 1. Configure the S3 bucket for website hosting as shown [in the S3 documentation](https://docs.aws.amazon.com/AmazonS3/latest/dev/HowDoIWebsiteConfiguration.html). 2. In the [Advanced Conditional Redirects section](https://docs.aws.amazon.com/AmazonS3/latest/dev/how-to-page-redirect.html#advanced-conditional-redirects) of the Website Hosting settings for the S3 bucket, set up the following redirect rule: ``` 404 https your_api_endpoint.execute-api.us-east-1.amazonaws.com dev-1/ 307 ``` You will need to replace `your_api_endpoint` part with the URL of your Serverless endpoint. You can find out what’s the endpoint URL by running: ``` serverless info ``` or observing the output of the deployment step. ## Any questions or suggestions? Please feel free to open an issue on this repository if something doesn’t work is doesn’t behave as described here. Thanks for giving this project a try! ================================================ FILE: aws-node-dynamic-image-resizer/config/pull-secret.sh ================================================ echo "🔍 $(tput bold)Pull secret$(tput sgr0)" if [ "$#" -lt 1 ]; then printf "Secret key: " read key else key=$1 fi; aws ssm get-parameter --name $key --region us-east-1 --with-decryption ================================================ FILE: aws-node-dynamic-image-resizer/config/push-secret.sh ================================================ echo "💾 $(tput bold)Push secret$(tput sgr0)" if [ "$#" -lt 2 ]; then printf "Secret key: " read key printf "Secret value: " read value else value=$1 key=$2 fi; aws ssm put-parameter --name $key --value $value --type SecureString --region us-east-1 --overwrite ================================================ FILE: aws-node-dynamic-image-resizer/deploy.sh ================================================ stage=${STAGE} region=${REGION} bucket=${BUCKET} secrets='/deploy/secrets/secrets.json' sls config credentials \ --provider aws \ --key ${SLS_KEY} \ --secret ${SLS_SECRET} \ --profile ${PROFILE} echo 'Deploying' sls deploy -s ${STAGE} ================================================ FILE: aws-node-dynamic-image-resizer/docker-compose.yml ================================================ version: "3" services: image-resize: build: . volumes: - ./secrets:/deploy/secrets env_file: - ./secrets/secrets.env ================================================ FILE: aws-node-dynamic-image-resizer/package.json ================================================ { "name": "aws-node-dynamic-image-resizer", "version": "1.0.0", "license": "MIT", "author": "Sebastian Borza", "scripts": { "eslint": "eslint ./src", "deploy": "SLS_DEBUG=* sls deploy --stage $STAGE --region $REGION --verbose", "deploy:local": "export $(cat ./config/.env | xargs) && serverless deploy --stage $STAGE --region $REGION --verbose", "remove:stack": "export $(cat ./config/.env | xargs) && sls remove -s $STAGE", "offline": "export $(cat ./config/.env | xargs) && serverless offline start --stage $STAGE", "add:env": "./config/push-secret.sh" }, "dependencies": { "@babel/runtime": "^7.3.1", "babel-runtime": "^7.0.0-beta.3", "lodash": "^4.17.19", "serverless-offline": "^4.7.0", "serverless-webpack": "^5.2.0", "sharp": "^0.21.3", "source-map-support": "^0.5.10", "stream": "^0.0.2" }, "devDependencies": { "@babel/cli": "^7.2.3", "@babel/core": "^7.3.4", "@babel/plugin-transform-runtime": "^7.2.0", "@babel/preset-env": "^7.3.1", "aws-sdk": "^2.409.0", "babel-core": "^7.0.0-bridge.0", "babel-jest": "^24.1.0", "babel-loader": "^8.0.5", "babel-plugin-source-map-support": "^2.0.1", "babel-plugin-transform-runtime": "^6.23.0", "babel-preset-env": "^1.7.0", "babel-preset-stage-3": "^6.24.1", "jest": "^23.6.0", "serverless": "^1.35.1", "webpack": "^4.28.3", "webpack-node-externals": "^1.7.2" }, "resolutions": { "babel-core": "7.0.0-bridge.0" } } ================================================ FILE: aws-node-dynamic-image-resizer/secrets/secrets.env ================================================ SLS_KEY= SLS_SECRET= PROFILE= STAGE=dev-1 REGION=us-east-1 ================================================ FILE: aws-node-dynamic-image-resizer/secrets/secrets.json ================================================ {"DOMAIN":""} ================================================ FILE: aws-node-dynamic-image-resizer/serverless.yml ================================================ service: name: ${self:custom.serviceName} plugins: - serverless-offline - serverless-webpack provider: name: aws runtime: nodejs12.x profile: dev-fii usagePlan: quota: limit: 100 offset: 2 period: MONTH throttle: burstLimit: 100 rateLimit: 50 custom: serviceName: image-resizing stage: ${opt:stage, self:custom.defaultStage} bucket: dynamic-image-resizing webpack: includeModules: forceExclude: - aws-sdk defaultStage: dev stages: - ${opt:stage} - dev - integration - production functions: resize: handler: src/handlers/resizer/index.handler events: - http: path: /{size}/{image} method: get environment: # Provide your bucket name here BUCKET: dynamic-image-resizing REGION: us-east-1 # layers: # - {Ref: ImageLibsLambdaLayer} iamRoleStatements: - Effect: "Allow" Action: - "s3:GetObject" Resource: "arn:aws:s3:::dynamic-image-resizing/*" - Effect: "Allow" Action: - "s3:PutObject" Resource: "arn:aws:s3:::dynamic-image-resizing/*" package: exclude: - layer/** - node_modules/** - '!node_modules/babel-runtime/**' - '!node_modules/sharp/**' # layers: # imageLibs: # path: layer # compatibleRuntimes: # - nodejs12.x ================================================ FILE: aws-node-dynamic-image-resizer/src/handlers/resizer/index.js ================================================ import { resizeHandler } from './resizeHandler' export const handler = async (event) => { try { const imagePath = await resizeHandler._process(event) const URL = `http://${process.env.BUCKET}.s3-website.${process.env.REGION}.amazonaws.com` return { headers: { 'location': `${URL}/${imagePath}` }, statusCode: 301, body: '' } } catch (error) { console.log(error) return new Error(error) } } ================================================ FILE: aws-node-dynamic-image-resizer/src/handlers/resizer/resizeHandler.js ================================================ import { s3Handler } from './s3Handler' //Core image processing package const sharp = require('sharp') class ResizerHandler { constructor(){ } async _process(event) { const { size, image } = event.pathParameters return await this.resize(size, image) } async resize(size, path) { try { const sizeArray = size.split('x') const width = parseInt(sizeArray[0]) const height = parseInt(sizeArray[1]) const Key = path const newKey = '' + width + 'x' + height + '/' + path const Bucket = process.env.BUCKET const streamResize = sharp() .resize(width, height) .toFormat('png') const readStream = s3Handler.readStream({ Bucket, Key }) const { writeStream, uploaded } = s3Handler.writeStream({ Bucket, Key: newKey }) //data streaming readStream .pipe(streamResize) .pipe(writeStream) await uploaded return newKey } catch (error) { throw new Error(error) } } } export const resizeHandler = new ResizerHandler() ================================================ FILE: aws-node-dynamic-image-resizer/src/handlers/resizer/s3Handler.js ================================================ import * as AWS from 'aws-sdk' import stream from 'stream' AWS.config.region = 'us-east-1' const S3 = new AWS.S3() class S3Handler { constructor() { } readStream({ Bucket, Key }) { return S3.getObject({ Bucket, Key }).createReadStream() } writeStream({ Bucket, Key }) { const passThrough = new stream.PassThrough() return { writeStream: passThrough, uploaded: S3.upload({ ContentType: 'image/png', Body: passThrough, Bucket, Key }).promise() } } } export const s3Handler = new S3Handler() ================================================ FILE: aws-node-dynamic-image-resizer/src/lib/BaseHandler.js ================================================ export default class BaseHandler { constructor() { this.handlerName = this.constructor.name } async execute(event, context) { console.log(`Executing handler ${this.handlerName}`) return this._process(event, context) } async _process(event, context) { console.log(event) console.log(context) console.log('Remember to overwrite this method if you need to provide custom handling logic.') } } ================================================ FILE: aws-node-dynamic-image-resizer/webpack.config.js ================================================ const slsw = require('serverless-webpack') const webpack = require('webpack') module.exports = { entry: slsw.lib.entries, target: 'node', mode: 'development', devtool: 'source-map', optimization: { minimize: false }, module: { rules: [ { test: /\.js$/, loaders: ['babel-loader'], include: __dirname, exclude: /node_modules/ } ] }, externals: ['sharp', 'aws-sdk'] } ================================================ FILE: aws-node-dynamodb-backup/README.md ================================================ # DynamoDB stream events to AWS S3 Which effectively creates a **backup of your dynamoDB table** assuming an event was caught for every record. Hint: [Introduce a new field "backedup"](https://s.natalian.org/2022/rupdated.js) to effectively trigger a backup. NOTE: DynamoDB triggers need to be manually associated / setup with the lambda function. * https://ap-southeast-1.console.aws.amazon.com/dynamodb/home?region=ap-southeast-1#tables:selected=staging_EXAMPLE * https://ap-southeast-1.console.aws.amazon.com/dynamodb/home?region=ap-southeast-1#tables:selected=production_EXAMPLE Upon deletion the image.json becomes an empty file. ================================================ FILE: aws-node-dynamodb-backup/handler.js ================================================ 'use strict'; const aws = require('aws-sdk'); const s3 = new aws.S3({ region: 'ap-southeast-1' }); module.exports.backup = (event, context, callback) => { const records = event.Records; Promise.all(records.map((record) => { const keysList = Object.keys(record.dynamodb.Keys).map((key) => { const keyDefinition = record.dynamodb.Keys[key]; const type = Object.keys(keyDefinition)[0]; const value = keyDefinition[type]; return value; }); const keysString = keysList.join('/'); const image = aws.DynamoDB.Converter.output({ M: record.dynamodb.NewImage }); return s3.putObject({ Bucket: process.env.BUCKET, Key: `${process.env.PREFIX}/${process.env.TABLE}/${keysString}/image.json`, Body: JSON.stringify(image), }).promise() .then((response) => { console.log(`${keysString} snapshot done`, response); }) .catch((err) => { console.error('Error', err); }); })) .then(v => callback(null, v), callback); }; ================================================ FILE: aws-node-dynamodb-backup/package.json ================================================ { "name": "aws-node-dynamodb-backup", "version": "1.0.0", "description": "Serverless DynamoDB changes backed up to S3", "repository": "", "author": "Kai Hendry ", "license": "MIT", "dependencies": { "aws-sdk": "^2.73.0" } } ================================================ FILE: aws-node-dynamodb-backup/serverless.yml ================================================ service: serverless-dynamodb-backup custom: bucket: EXAMPLE dynamoDBTableName: "${opt:stage, self:provider.stage}_EXAMPLE" prefix: FOO provider: name: aws runtime: nodejs12.x stage: staging iam: role: statements: - Effect: Allow Action: - s3:PutObject Resource: "arn:aws:s3:::${self:custom.bucket}/${self:custom.prefix}/${self:custom.dynamoDBTableName}/*" - Effect: Allow Action: - "dynamodb:GetRecords" - "dynamodb:GetShardIterator" - "dynamodb:DescribeStream" - "dynamodb:ListStreams" Resource: "arn:aws:dynamodb:ap-southeast-1:*:table/${self:custom.dynamoDBTableName}/stream/*" functions: backup: handler: handler.backup environment: STAGE: "${opt:stage, self:provider.stage}" BUCKET: "${self:custom.bucket}" TABLE: "${self:custom.dynamoDBTableName}" PREFIX: "${self:custom.prefix}" timeout: 300 ================================================ FILE: aws-node-env-variables/.gitignore ================================================ node_modules .serverless ================================================ FILE: aws-node-env-variables/README.md ================================================ # Serverless Environment Variables Usage This example demonstrates how to use environment variables for AWS Lambdas. ## Use-cases - Provide settings as environment variables to your Lambda functions ## How it works The first time you create or update Lambda functions that use environment variables in a region, a default service key is created for you automatically within AWS KMS. This key is used to encrypt environment variables. When you create or update Lambda functions that use environment variables, AWS Lambda encrypts them using the AWS Key Management Service. When your Lambda function is invoked, those values are decrypted and made available to the Lambda code. Read more in the official AWS [docs](http://docs.aws.amazon.com/lambda/latest/dg/env_variables.html). ## Setup None needed. ## Deploy In order to deploy the you endpoint simply run ```bash serverless deploy ``` The expected result should be similar to: ```bash Serverless: Creating Stack… Serverless: Checking Stack create progress… ..... Serverless: Stack create finished… Serverless: Packaging service… Serverless: Uploading CloudFormation file to S3… Serverless: Uploading service .zip file to S3… Serverless: Updating Stack… Serverless: Checking Stack update progress… ................ Serverless: Stack update finished… Service Information service: function-with-environment-variables stage: dev region: us-east-1 api keys: None endpoints: None functions: function-with-environment-variables-dev-resetPassword: arn:aws:lambda:us-east-1:377024778620:function:function-with-environment-variables-dev-resetPassword function-with-environment-variables-dev-createUser: arn:aws:lambda:us-east-1:377024778620:function:function-with-environment-variables-dev-createUser ``` ## Usage You can now invoke each of the Lambdas directly and print their log statements via ```bash serverless invoke --function=createUser --log ``` The expected result should be similar to: ```bash { "statusCode": 200, "body": "{\"message\":\"User created\"}" } -------------------------------------------------------------------- START RequestId: 78b0785d-afd3-11e6-85a7abb1cd48ef Version: $LATEST 2021 11:15:48.575 (+01:00) 78b0785d-afd3-11e6-85a7abb1cd48ef PASSWORD_ITERATIONS: 4096 2021 11:15:48.576 (+01:00) 78b0785d-afd3-11e6-85a7abb1cd48ef PASSWORD_DERIVED_KEY_LENGTH: 256 2021 11:15:48.576 (+01:00) 78b0785d-afd3-11e6-85a7abb1cd48ef EMAIL_SERVICE_API_KEY: KEYEXAMPLE1234 END RequestId: 78b0785d-afd3-11e6-85a7abb1cd48ef REPORT RequestId: 78b0785d-afd3-11e6-85a7abb1cd48ef Duration: 3.36 ms Billed Duration: 100 ms Memory Size: 1024 MB Max Memory Used: 15 MB ``` ```bash serverless invoke --function=resetPassword --log ``` The expected result should be similar to: ```bash { "statusCode": 200, "body": "{\"message\":\"Password sent.\"}" } -------------------------------------------------------------------- START RequestId: 9cc33dafd3-11e6-b919a4e146bf3d Version: $LATEST 2021 11:16:48.838 (+01:00) 9cc33dafd3-11e6-b919a4e146bf3d EMAIL_SERVICE_API_KEY: KEYEXAMPLE1234 END RequestId: 9cc33dafd3-11e6-b919a4e146bf3d REPORT RequestId: 9cc33dafd3-11e6-b919a4e146bf3d Duration: 3.14 ms Billed Duration: 100 ms Memory Size: 1024 MB Max Memory Used: 15 MB ``` Especially helpful for local development you can also invoke the Lambda locally and see the resulting log via ```bash serverless invoke local --function=createUser --log ``` The expected result should be similar to: ```bash PASSWORD_ITERATIONS: 4096 PASSWORD_DERIVED_KEY_LENGTH: 256 EMAIL_SERVICE_API_KEY: KEYEXAMPLE1234 { "statusCode": 200, "body": "{\"message\":\"User created\"}" } ``` ================================================ FILE: aws-node-env-variables/handler.js ================================================ 'use strict'; module.exports.createUser = (event, context, callback) => { // logs `4096` console.log('PASSWORD_ITERATIONS: ', process.env.PASSWORD_ITERATIONS); // logs `256` console.log('PASSWORD_DERIVED_KEY_LENGTH: ', process.env.PASSWORD_DERIVED_KEY_LENGTH); // logs `KEYEXAMPLE1234` console.log('EMAIL_SERVICE_API_KEY: ', process.env.EMAIL_SERVICE_API_KEY); // In this case could use the env vars to generate a secure password hash. // const passwordHash = PBKDF2( // passphrase, // salt, // process.env.PASSWORD_ITERATIONS, // process.env.PASSWORD_DERIVED_KEY_LENGTH // ); // The email service api key would be used to send a verfication email to the user. const response = { statusCode: 200, body: JSON.stringify({ message: 'User created', }), }; callback(null, response); }; module.exports.resetPassword = (event, context, callback) => { // logs `KEYEXAMPLE1234` console.log('EMAIL_SERVICE_API_KEY: ', process.env.EMAIL_SERVICE_API_KEY); // The email service api key would be used to send a reset password email. const response = { statusCode: 200, body: JSON.stringify({ message: 'Password sent.', }), }; callback(null, response); }; ================================================ FILE: aws-node-env-variables/package.json ================================================ { "name": "aws-env-variables", "version": "1.0.0", "description": "This example demonstrates how to use environment variables for AWS Lambdas.", "author": "", "license": "MIT" } ================================================ FILE: aws-node-env-variables/serverless.yml ================================================ service: function-with-environment-variables frameworkVersion: ">=1.2.0 <2.0.0" provider: name: aws runtime: nodejs12.x environment: EMAIL_SERVICE_API_KEY: KEYEXAMPLE1234 functions: createUser: handler: handler.createUser environment: PASSWORD_ITERATIONS: 4096 PASSWORD_DERIVED_KEY_LENGTH: 256 resetPassword: handler: handler.resetPassword ================================================ FILE: aws-node-env-variables-encrypted-in-a-file/.gitignore ================================================ node_modules .serverless ================================================ FILE: aws-node-env-variables-encrypted-in-a-file/README.md ================================================ # Serverless IMPORTANT NOTE: As pointed out in the [AWS documentation](http://docs.aws.amazon.com/lambda/latest/dg/env_variables.html) for storing sensible the `Ciphertext` should be stored in the environment variables. This tutorial doesn't go into that yet, but we will update it soon accordingly. This example demonstrates how to store secrets like API keys encrypted in your repository while providing them as environment variables to your AWS Lambda functions. ## Use-cases - Provide secrets like API keys to your Lambda functions ## Why? While repository hosting services like Github or Bitbucket have very high security standards it's recommended to not store your unencrypted secrets there. In addition in larger teams not everybody needs to have access to those secrets of your production environment. Encrypting your secrets per stage and only adding the encrypted files into your repository is a sensible strategy to fulfill the previously described goals. The passwords to decrypt and encrypt the secrets files should only be shared between the necessary developers over a secure channel. In case you are using a Continuous Integration to deploy your infrastructure obviously this system must be aware of the passwords as well. ## Setup Since this plugin uses the Serverless plugin `serverless-secrets-plugin` you need to setup the `node_modules` by running: ```bash npm install ``` ## Usage ### Decrypt and Deploy In order to deploy the you endpoint simply run ```bash serverless deploy --stage dev ``` The expected result should be similar to: ```bash Error -------------------------------------------------- Couldn't find the secrets file for this stage: secrets.dev.yml For debugging logs, run again after setting SLS_DEBUG env var. Get Support -------------------------------------------- Docs: docs.serverless.com Bugs: github.com/serverless/serverless/issues Please report this error. We think it might be a bug. Your Environment Information ----------------------------- OS: darwin Node Version: 6.2.2 Serverless Version: 1.2.0 ``` This is happening since the `serverless-secrets-plugin` makes sure a secrets file for the specific stage exists. Let's decrypt the secrets file so you can deploy the service. To do so run ```bash serverless decrypt --stage dev --password 'va$27dC}9382G7ac6?V' ``` The expected result should be similar to: ```bash Serverless: Sucessfully encrypted 'secrets.dev.yml.encrypted' to 'secrets.dev.yml' ``` Now that you have the unencrypted version of your secrets file this directory you can deploy with ```bash serverless deploy --stage dev ``` ### Encrypt In case you want to add, update or remove entries in your secrets file simply modify your secrets file. Once you are done encrypt it with ```bash serverless encrypt --stage dev --password 'va$27dC}9382G7ac6?V' ``` The expected result should be: ```bash Serverless: Sucessfully encrypted 'secrets.dev.yml' to 'secrets.dev.yml.encrypted' ``` The encrypted file can be checked into your version control system e.g. Git. ### Decrypt and Encrypt the Production Secrets ```bash serverless decrypt --stage prod --password 'v2]83WDneGt9AGXv]X6QfP9NW3^J&K3V' ``` ```bash serverless encrypt --stage prod --password 'v2]83WDneGt9AGXv]X6QfP9NW3^J&K3V' ``` # Important Note Make sure the the unencrypted secrets files are listed in .gitignore or similar to make sure they are never checked into your repository. ================================================ FILE: aws-node-env-variables-encrypted-in-a-file/handler.js ================================================ 'use strict'; module.exports.resetPassword = (event, context, callback) => { console.log('SESSION_KEY: ', process.env.SESSION_KEY); // Authenticate the user session console.log('EMAIL_SERVICE_API_KEY: ', process.env.EMAIL_SERVICE_API_KEY); // The email service api key would be used to send a reset password email. const response = { statusCode: 200, body: JSON.stringify({ message: 'Password sent.', }), }; callback(null, response); }; ================================================ FILE: aws-node-env-variables-encrypted-in-a-file/package.json ================================================ { "name": "aws-env-variables-encrypted-in-a-file", "version": "1.0.0", "description": "Serverless example managing secrets in an encrypted file", "author": "", "license": "MIT", "dependencies": { "serverless-secrets-plugin": "^0.0.1" } } ================================================ FILE: aws-node-env-variables-encrypted-in-a-file/secrets.prod.yml.encrypted ================================================ q۵2)8GGg4}׬}J ]f۲Ulf"ŵ]<~L8>6_gL諩gMHkwWm>/%BRNEM%!Nb ================================================ FILE: aws-node-env-variables-encrypted-in-a-file/serverless.yml ================================================ service: env-variables-encrypted-in-a-file frameworkVersion: ">=1.2.0 <2.0.0" plugins: - serverless-secrets-plugin provider: name: aws runtime: nodejs12.x stage: dev custom: secrets: ${file(secrets.${opt:stage, self:provider.stage}.yml)} functions: resetPassword: handler: handler.resetPassword environment: SESSION_KEY: ${self:custom.secrets.SESSION_KEY} EMAIL_SERVICE_API_KEY: ${self:custom.secrets.EMAIL_SERVICE_API_KEY} ================================================ FILE: aws-node-express-api/.gitignore ================================================ node_modules .serverless ================================================ FILE: aws-node-express-api/README.md ================================================ # Serverless Framework Node Express API on AWS This template demonstrates how to develop and deploy a simple Node Express API service running on AWS Lambda using the Serverless Framework. This template configures a single function, `api`, which is responsible for handling all incoming requests using the `httpApi` event. To learn more about `httpApi` event configuration options, please refer to [httpApi event docs](https://www.serverless.com/framework/docs/providers/aws/events/http-api/). As the event is configured in a way to accept all incoming requests, the Express.js framework is responsible for routing and handling requests internally. This implementation uses the `serverless-http` package to transform the incoming event request payloads to payloads compatible with Express.js. To learn more about `serverless-http`, please refer to the [serverless-http README](https://github.com/dougmoscrop/serverless-http). ## Usage ### Deployment Install dependencies with: ``` npm install ``` and then deploy with: ``` serverless deploy ``` After running deploy, you should see output similar to: ``` Deploying "aws-node-express-api" to stage "dev" (us-east-1) ✔ Service deployed to stack aws-node-express-api-dev (96s) endpoint: ANY - https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com functions: api: aws-node-express-api-dev-api (2.3 kB) ``` _Note_: In current form, after deployment, your API is public and can be invoked by anyone. For production deployments, you might want to configure an authorizer. For details on how to do that, refer to [`httpApi` event docs](https://www.serverless.com/framework/docs/providers/aws/events/http-api/). ### Invocation After successful deployment, you can call the created application via HTTP: ``` curl https://xxxxxxx.execute-api.us-east-1.amazonaws.com/ ``` Which should result in the following response: ```json { "message": "Hello from root!" } ``` ### Local development The easiest way to develop and test your function is to use the `dev` command: ``` serverless dev ``` This will start a local emulator of AWS Lambda and tunnel your requests to and from AWS Lambda, allowing you to interact with your function as if it were running in the cloud. Now you can invoke the function as before, but this time the function will be executed locally. Now you can develop your function locally, invoke it, and see the results immediately without having to re-deploy. When you are done developing, don't forget to run `serverless deploy` to deploy the function to the cloud. ================================================ FILE: aws-node-express-api/handler.js ================================================ const serverless = require("serverless-http"); const express = require("express"); const app = express(); app.get("/", (req, res, next) => { return res.status(200).json({ message: "Hello from root!", }); }); app.get("/hello", (req, res, next) => { return res.status(200).json({ message: "Hello from path!", }); }); app.use((req, res, next) => { return res.status(404).json({ error: "Not Found", }); }); exports.handler = serverless(app); ================================================ FILE: aws-node-express-api/package.json ================================================ { "name": "aws-node-express-api", "version": "1.0.0", "description": "", "dependencies": { "express": "^4.19.2", "serverless-http": "^3.2.0" } } ================================================ FILE: aws-node-express-api/serverless.yml ================================================ service: aws-node-express-api frameworkVersion: "4" provider: name: aws runtime: nodejs20.x # Uncomment to easily set up a custom domain. Read the docs for more details: # https://www.serverless.com/framework/docs/providers/aws/guide/domains # domain: api.example.com functions: api: handler: handler.handler events: - httpApi: "*" ================================================ FILE: aws-node-express-dynamodb-api/.gitignore ================================================ node_modules .serverless ================================================ FILE: aws-node-express-dynamodb-api/README.md ================================================ # Serverless Framework Node Express API on AWS This template demonstrates how to develop and deploy a simple Node Express API service, backed by DynamoDB table, running on AWS Lambda using the Serverless Framework. This template configures a single function, `api`, which is responsible for handling all incoming requests using the `httpApi` event. To learn more about `httpApi` event configuration options, please refer to [httpApi event docs](https://www.serverless.com/framework/docs/providers/aws/events/http-api/). As the event is configured in a way to accept all incoming requests, the Express.js framework is responsible for routing and handling requests internally. This implementation uses the `serverless-http` package to transform the incoming event request payloads to payloads compatible with Express.js. To learn more about `serverless-http`, please refer to the [serverless-http README](https://github.com/dougmoscrop/serverless-http). Additionally, it also handles provisioning of a DynamoDB database that is used for storing data about users. The Express.js application exposes two endpoints, `POST /users` and `GET /user/:userId`, which create and retrieve a user record. ## Usage ### Deployment Install dependencies with: ``` npm install ``` and then deploy with: ``` serverless deploy ``` After running deploy, you should see output similar to: ``` Deploying "aws-node-express-dynamodb-api" to stage "dev" (us-east-1) ✔ Service deployed to stack aws-node-express-dynamodb-api-dev (109s) endpoint: ANY - https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com functions: api: aws-node-express-dynamodb-api-dev-api (3.8 MB) ``` _Note_: In current form, after deployment, your API is public and can be invoked by anyone. For production deployments, you might want to configure an authorizer. For details on how to do that, refer to [`httpApi` event docs](https://www.serverless.com/framework/docs/providers/aws/events/http-api/). Additionally, in current configuration, the DynamoDB table will be removed when running `serverless remove`. To retain the DynamoDB table even after removal of the stack, add `DeletionPolicy: Retain` to its resource definition. ### Invocation After successful deployment, you can create a new user by calling the corresponding endpoint: ``` curl --request POST 'https://xxxxxx.execute-api.us-east-1.amazonaws.com/users' --header 'Content-Type: application/json' --data-raw '{"name": "John", "userId": "someUserId"}' ``` Which should result in the following response: ```json { "userId": "someUserId", "name": "John" } ``` You can later retrieve the user by `userId` by calling the following endpoint: ``` curl https://xxxxxxx.execute-api.us-east-1.amazonaws.com/users/someUserId ``` Which should result in the following response: ```json { "userId": "someUserId", "name": "John" } ``` ### Local development The easiest way to develop and test your function is to use the `dev` command: ``` serverless dev ``` This will start a local emulator of AWS Lambda and tunnel your requests to and from AWS Lambda, allowing you to interact with your function as if it were running in the cloud. Now you can invoke the function as before, but this time the function will be executed locally. Now you can develop your function locally, invoke it, and see the results immediately without having to re-deploy. When you are done developing, don't forget to run `serverless deploy` to deploy the function to the cloud. ================================================ FILE: aws-node-express-dynamodb-api/handler.js ================================================ const { DynamoDBClient } = require("@aws-sdk/client-dynamodb"); const { DynamoDBDocumentClient, GetCommand, PutCommand, } = require("@aws-sdk/lib-dynamodb"); const express = require("express"); const serverless = require("serverless-http"); const app = express(); const USERS_TABLE = process.env.USERS_TABLE; const client = new DynamoDBClient(); const docClient = DynamoDBDocumentClient.from(client); app.use(express.json()); app.get("/users/:userId", async (req, res) => { const params = { TableName: USERS_TABLE, Key: { userId: req.params.userId, }, }; try { const command = new GetCommand(params); const { Item } = await docClient.send(command); if (Item) { const { userId, name } = Item; res.json({ userId, name }); } else { res .status(404) .json({ error: 'Could not find user with provided "userId"' }); } } catch (error) { console.log(error); res.status(500).json({ error: "Could not retrieve user" }); } }); app.post("/users", async (req, res) => { const { userId, name } = req.body; if (typeof userId !== "string") { res.status(400).json({ error: '"userId" must be a string' }); } else if (typeof name !== "string") { res.status(400).json({ error: '"name" must be a string' }); } const params = { TableName: USERS_TABLE, Item: { userId, name }, }; try { const command = new PutCommand(params); await docClient.send(command); res.json({ userId, name }); } catch (error) { console.error(error); res.status(500).json({ error: "Could not create user" }); } }); app.use((req, res, next) => { return res.status(404).json({ error: "Not Found", }); }); exports.handler = serverless(app); ================================================ FILE: aws-node-express-dynamodb-api/package.json ================================================ { "name": "aws-node-express-dynamodb-api", "version": "1.0.0", "description": "", "dependencies": { "@aws-sdk/client-dynamodb": "^3.585.0", "@aws-sdk/lib-dynamodb": "^3.585.0", "express": "^4.19.2", "serverless-http": "^3.2.0" } } ================================================ FILE: aws-node-express-dynamodb-api/serverless.yml ================================================ service: aws-node-express-dynamodb-api frameworkVersion: "4" stages: default: params: tableName: "users-table-${sls:stage}" provider: name: aws runtime: nodejs20.x # Uncomment to easily set up a custom domain. Read the docs for more details: # https://www.serverless.com/framework/docs/providers/aws/guide/domains # domain: api.example.com iam: role: statements: - Effect: Allow Action: - dynamodb:Query - dynamodb:Scan - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem Resource: - Fn::GetAtt: [UsersTable, Arn] environment: USERS_TABLE: ${param:tableName} functions: api: handler: handler.handler events: - httpApi: "*" resources: Resources: UsersTable: Type: AWS::DynamoDB::Table Properties: AttributeDefinitions: - AttributeName: userId AttributeType: S KeySchema: - AttributeName: userId KeyType: HASH BillingMode: PAY_PER_REQUEST TableName: ${param:tableName} ================================================ FILE: aws-node-fetch-file-and-store-in-s3/.gitignore ================================================ node_modules .serverless ================================================ FILE: aws-node-fetch-file-and-store-in-s3/README.md ================================================ # Fetch image from URL then upload to s3 Example This example display how to fetch an image from remote source (URL) and then upload this image to a S3 bucket. ## Use-cases - Store a user's profile picture from another service. ## How it works We first fetch the data from given url and then call the S3 API `putObject` to upload it to the bucket. ```js fetch('image URL') .then(res => { return s3.putObject({Bucket, Key, Body: res.body}).promise(); }).then(res => { callback(null, res); }).catch(err => { callback(err, null); }); ``` ## Setup Since this plugin uses the Serverless plugin `serverless-secrets-plugin` you need to setup the `node_modules` by running: ```bash npm install ``` In addition you need to create an S3 bucket you want to store the files in. After you created the bucket change the bucket name in `serverless.yml` custom settings to your buckets. ```yml custom: bucket: ``` ## Deploy In order to deploy the you endpoint simply run ```bash serverless deploy ``` The expected result should be similar to: ```bash Serverless: Creating Stack... Serverless: Checking Stack create progress... ..... Serverless: Stack create finished... Serverless: Packaging service... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading service .zip file to S3 (1.8 KB)... Serverless: Updating Stack... Serverless: Checking Stack update progress... ................ Serverless: Stack update finished... Service Information service: aws-node-fetch-file-and-store-in-s3 stage: dev region: us-west-1 api keys: None endpoints: None functions: aws-node-fetch-file-and-store-in-s3-dev-save: arn:aws:lambda:us-west-1:377024778620:function:aws-node-fetch-file-and-store-in-s3-dev-save ``` ## Usage You can now send an HTTP request directly to the endpoint using a tool like curl ```bash serverless invoke --function save --log --data='{ "image_url": "https://assets-cdn.github.com/images/modules/open_graph/github-mark.png", "key": "github.png"}' ``` The expected result should be similar to: ```bash "Saved" -------------------------------------------------------------------- START RequestId: c658859d-bd11e6-ac1f-c7a7ee5bd7f3 Version: $LATEST END RequestId: c658859d-bd11e6-ac1f-c7a7ee5bd7f3 REPORT RequestId: c658859d-bd11e6-ac1f-c7a7ee5bd7f3 Duration: 436.94 ms Billed Duration: 500 ms Memory Size: 1024 MB Max Memory Used: 29 MB ``` ## Scaling By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 100. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit). ================================================ FILE: aws-node-fetch-file-and-store-in-s3/handler.js ================================================ 'use strict'; const fetch = require('node-fetch'); const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies const s3 = new AWS.S3(); module.exports.save = (event, context, callback) => { fetch(event.image_url) .then((response) => { if (response.ok) { return response; } return Promise.reject(new Error( `Failed to fetch ${response.url}: ${response.status} ${response.statusText}`)); }) .then(response => response.buffer()) .then(buffer => ( s3.putObject({ Bucket: process.env.BUCKET, Key: event.key, Body: buffer, }).promise() )) .then(v => callback(null, v), callback); }; ================================================ FILE: aws-node-fetch-file-and-store-in-s3/package.json ================================================ { "name": "aws-fetch-file-and-store-in-s3", "description": "Fetch an image from remote source (URL) and then upload the image to a S3 bucket.", "version": "1.0.0", "author": "Bozhao Yu", "license": "MIT", "dependencies": { "aws-sdk": "^2.7.9", "node-fetch": "^1.6.3" } } ================================================ FILE: aws-node-fetch-file-and-store-in-s3/serverless.yml ================================================ service: fetch-file-and-store-in-s3 frameworkVersion: ">=2.24.0" custom: bucket: provider: name: aws runtime: nodejs12.x stage: dev region: us-west-1 iam: role: statements: - Effect: Allow Action: - s3:PutObject - s3:PutObjectAcl Resource: "arn:aws:s3:::${self:custom.bucket}/*" functions: save: handler: handler.save environment: BUCKET: ${self:custom.bucket} ================================================ FILE: aws-node-fullstack/README.md ================================================ # Example – Serverless Email Sign-Up Form This demo application helps you test Serverless Framework Enterprise's main features: * **Insights** - Monitoring, metrics and alerts for your functions. * **Safeguards** - Best practice policies that run before you perform a deployment. * **Secrets** - Store sensitive credentials in the Serverless Enterprise Dashboard and reference them in your Serverless Framework Project. ![Serverless Framework Enterprise Email Sign-Up Form Example](https://s3.amazonaws.com/assets.sales.serverless/github/enterprise-examples/email_form_preview.gif) ## Installation #### Clone this repository ```shell $ git clone https://github.com/serverless/enterprise.git ``` #### Install Front-End & Back-End Dependencies Navigate into this example project and install dependencies on the frontend and backend. ```shell # location - enterprise/examples/email-signup-form/frontend $ npm i ``` ```shell # location - enterprise/examples/email-signup-form/backend $ npm i ``` #### Create a Tenant and Application in Serverless Framework Enterprise The Serverless Enterprise Plugin adds a `login` command to the Serverless Framework, use it like this to log you in: ```shell # location - enterprise/backend $ serverless login ``` Make sure to follow the prompts and create your Tenant (it's like a Github Org) and Application. #### Add the Tenant and Application to this project's `serverless.yml` ![App and Tenant](https://s3.amazonaws.com/assets.sales.serverless/github/enterprise-examples/email_form_appandtenant.png) #### Deploy the back-end ```shell # location - enterprise/backend $ serverless deploy ``` #### Run the front-end ```shell # location - enterprise/backend $ npm run start ``` #### Add the back-end URL in the front-end The front-end form is not directed at the API endpoint out of the box. You must copy the POST URL that is returned on `serverless deploy` of the `backend` into the front-end. The URL should resemble this. ``` https://bpcn36m16a.execute-api.us-east-1.amazonaws.com/dev/submit ``` In the front-end, click "Demo Utilities" and paste this URL into the `FORM API` field. The form should now work, as well as the testing features in the Utilites panel. ## Testing Serverless Insights The user interface of this example application has a few utilities you can use to test out Serverless Framework Enterprise. Click on "Demo Utilities" in the top right. A side panel will expand which you can use to invoke the example application's Function several times, to fill Serverless Framework Enterprise with invocation data. You can also use the panel to generate a random Function code error that will appear in Serverless Framework Enterprise. Read more about [Insights here](https://github.com/serverless/enterprise/blob/master/docs/insights.md). ## Testing Serverless Secrets The goal of our Secrets feature is to support storing and using any generic secret as a [Serverless Variable](https://serverless.com/framework/docs/providers/aws/guide/variables/). This will be supported in upcoming weeks. What Secrets supports now is creating a specific type of secret: AWS Access Keys. You can use Secrets to reference temporary AWS Access Keys that last for 1 hour, used for the purpose of deploying your Serverless Framework project to the underlying AWS account. Since these are temporary credentials, they mitigate the risk of developers leaving long-term credentials anywhere (e.g. Github) and are perfect for CI/CD. Read more about [Secrets here](https://github.com/serverless/enterprise/blob/master/docs/secrets.md). ## Testing Serverless Safeguards The goal of our Safeguards feature is to be alike a linter for serverless architectures. Safeguards are best practices and organizational policies that are enforced upon deployment. When a deployment happens, Framework Enterprise scans your `serverless.yml` and CloudFormation file before deployment and looks for issues. Safeguards are immediately applied, out-of-the-box, when you add the Serverless Enterprise Plugin. Read more about [Safeguards here](https://github.com/serverless/enterprise/blob/master/docs/safeguards.md). ================================================ FILE: aws-node-fullstack/backend/.gitignore ================================================ # package directories node_modules jspm_packages # Serverless directories .serverless ================================================ FILE: aws-node-fullstack/backend/index.js ================================================ /** * Form Submit */ const submit = (event, context, callback) => { if (event['queryStringParameters'] && event['queryStringParameters']['error']) { let r = Math.random().toString(36).substring(7); throw new Error(`Random error ${r}`) } callback(null, { statusCode: 200, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Credentials': true, }, body: JSON.stringify({ message: 'form submission received' }), }) } module.exports = { submit } ================================================ FILE: aws-node-fullstack/backend/package.json ================================================ { "name": "sfe-demo-leadcapture", "version": "1.0.0", "description": "", "main": "index.js", "scripts": {}, "author": "Serverless.com", "license": "MIT", "dependencies": { "js-yaml": "^3.13.0", "serverless-finch": "^2.3.2" }, "devDependencies": { "@serverless/enterprise-plugin": "latest" } } ================================================ FILE: aws-node-fullstack/backend/serverless.yml ================================================ tenant: ac360 # Enter your tenant name here app: enterprise # Enter your application name here service: demo-email-form frameworkVersion: '>=1.38.0 <2.0.0' provider: name: aws runtime: nodejs10.x # credentials: ${secrets:aws-enterprise} # Enter an AWS Secret like this, after you create it in the Dashboard. functions: formSubmit: handler: index.submit events: - http: path: submit method: post cors: true plugins: # - serverless-finch # If you want to deploy the front-end uncomment this and the "custom" object below # custom: # client: # bucketName: sfe-demo-email-form # If you want to deploy the front-end, change this to a universally unique AWS S3 bucket name # distributionFolder: ../frontend/build ================================================ FILE: aws-node-fullstack/frontend/.gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # production /build # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* ================================================ FILE: aws-node-fullstack/frontend/package.json ================================================ { "name": "frontend", "version": "0.1.0", "private": true, "dependencies": { "react": "^16.8.4", "react-dom": "^16.8.4", "react-scripts": "2.1.8" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "eslintConfig": { "extends": "react-app" }, "browserslist": [ ">0.2%", "not dead", "not ie <= 11", "not op_mini all" ] } ================================================ FILE: aws-node-fullstack/frontend/public/index.html ================================================ Serverless Email Form
================================================ FILE: aws-node-fullstack/frontend/public/manifest.json ================================================ { "short_name": "React App", "name": "Create React App Sample", "icons": [ { "src": "favicon.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" } ], "start_url": ".", "display": "standalone", "theme_color": "#000000", "background_color": "#ffffff" } ================================================ FILE: aws-node-fullstack/frontend/src/DemoApp.css ================================================ * { box-sizing: border-box; font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; font-weight: 300; } html, body { display: flex; flex-direction: column; box-sizing: border-box; height: 100%; width: 100%; background: #000000; } .DemoApp { display: flex; flex-direction: column; height: 100%; width: 100%; min-height: 100vh; } /* Admin Menu */ .DemoApp-admin { z-index: 10; position: absolute; display: flex; flex-direction: column; box-sizing: border-box; height: 100vh; width: 700px; padding: 1em 1em 1em 1em; margin-left: -750px; background: #000000; font-size: calc(10px + 2vmin); color: white; box-shadow: 5px 0 2px 0px rgba(0,0,0,0.2); -webkit-transition:all 0.3s ease-in-out; -moz-transition:all 0.3s ease-in-out; -o-transition:all 0.3s ease-in-out; transition:all 0.3s ease-in-out; } .DemoApp-admin.visible { margin-left: 0; } .DemoApp-admin .admin-close { position: absolute; top: 0px; right: 0px; padding: 0.4em 0.8em; color: #ffffff; opacity: 0.5; font-size: 38px; user-select: none; transform: scaleX(1.1) scaleY(0.9); -webkit-transition:all 0.2s ease-in-out; -moz-transition:all 0.2s ease-in-out; -o-transition:all 0.2s ease-in-out; transition:all 0.2s ease-in-out; } .DemoApp-admin .admin-close:hover { opacity: 1; cursor: pointer; } .DemoApp-admin .admin-section { display: flex; flex-direction: column; margin: 0 0 0.8em 0; padding: 0.8em 0 0 0; border-top: 1px solid rgba(255,255,255,0.2); } .DemoApp-admin .admin-section-general { display: flex; flex-direction: row; } .DemoApp-admin .admin-section-general-description { display: flex; flex-direction: column; height: auto; width: 85%; font-size: 15px; line-height: 22px; font-weight: 200; color: rgba(255,255,255,0.55); letter-spacing: 0.5px; padding: 0 0.3em 0.3em 0; box-sizing: border-box; } .DemoApp-admin .admin-section-general-button { display: flex; height: auto; width: 15%; } .DemoApp-admin .admin-label { display: flex; height: auto; width: 100%; font-size: 16px; font-weight: 600; color: #FFFFFF; margin: 0em 0 0.6em 0; } .DemoApp-admin .admin-input-description { display: flex; height: auto; width: 100%; font-size: 15px; line-height: 22px; font-weight: 200; color: rgba(255,255,255,0.45); margin: 0.4em 0 0 0; letter-spacing: 0.5px; } .DemoApp-admin .admin-section-field { display: flex; flex-direction: row; align-items: center; width: 100%; height: 50px; } .DemoApp-admin .admin-section-field .admin-button { width: 15%; } .DemoApp-admin .admin-section-button { display: flex; flex-direction: column; align-items: flex-start; width: 100%; height: auto; margin-bottom: 1em; user-select: none; } .DemoApp-admin .admin-input { display: flex; align-items: center; justify-content: center; box-sizing: border-box; padding: 0.2em 0.7em; width: 85%; height: 100%; border: none; font-size: 18px; font-weight: 300; background-color: rgba(255,255,255,0.2); border-top: 1px solid rgba(255,255,255,0); border-left: 1px solid rgba(255,255,255,0); border-bottom: 1px solid rgba(255,255,255,0); border-right: none; color: #FFFFFF; line-height: normal; outline: none; -webkit-transition:all 0.2s ease-in-out; -moz-transition:all 0.2s ease-in-out; -o-transition:all 0.2s ease-in-out; transition:all 0.2s ease-in-out; } .DemoApp-admin .admin-input:hover { cursor: pointer; } .DemoApp-admin .admin-input:focus { border-top: 1px solid rgba(255,255,255,0.7); border-left: 1px solid rgba(255,255,255,0.7); border-bottom: 1px solid rgba(255,255,255,0.7); border-right: none; } .DemoApp-admin .admin-input::placeholder { color: rgba(255,255,255,0.6); } .DemoApp-admin .admin-button { display: flex; flex-direction: row; align-items: center; justify-content: center; width: auto; max-width: 100px; height: 50px; padding: 0.5em 1em 0.5em 1em; background: #F15953; color: white; font-size: 16px; font-weight: 600; border: none; user-select: none; } .DemoApp-admin .admin-button:focus { outline: none; } .DemoApp-admin .admin-button:hover { cursor: pointer; background: #F15953; color: white; } .DemoApp-admin .admin-button:active { outline: none; background: white; color: #F15953; } .DemoApp-admin .admin-logo { display: flex; flex-direction: row; align-items: center; margin: 0 0 1.5em 0; font-size: 16px; font-weight: 600; } .DemoApp-admin .admin-logo img { height: auto; width: 30px; margin-right: 20px;; } .DemoApp-admin .admin-status { margin: 0; padding: 0; font-size: 24px; } /* Message Thread */ .DemoApp-content { display: flex; flex-direction: column; align-items: center; justify-content: flex-start; height: 100vh; width: 100%; box-sizing: border-box; background: #eaeaea; overflow-y: scroll; padding: 4em 0.75em; font-size: calc(10px + 2vmin); color: white; } .DemoApp-content .admin-link { position: fixed; display: flex; justify-content: flex-end; top: 20px; right: 30px; height: 100px; width: 200px; font-size: 22px; color: rgba(0,0,0,0.4); user-select: none; -webkit-transition:all 0.3s ease-in-out; -moz-transition:all 0.3s ease-in-out; -o-transition:all 0.3s ease-in-out; transition:all 0.3s ease-in-out; } .DemoApp-content .admin-link:hover { cursor: pointer; color: rgba(0,0,0,1); } .DemoApp-content .container { display: flex; flex-direction: column; width: 100%; max-width: 1000px; height: auto; background: #ffffff; padding: 2em; box-shadow: 0px 4px 2px rgba(0,0,0,0.2); } .DemoApp-content .form-header { display: flex; flex-direction: row; justify-content: center; width: 100%; height: auto; margin: 0 0 1.5em 0; color: #000000; } .DemoApp-content .form { display: flex; flex-direction: column; justify-content: center; margin: 0 auto; width: 100%; max-width: 600px; height: auto; color: #000000; } .DemoApp-content .form-field-label { display: flex; flex-direction: row; justify-content: center; width: 100%; height: auto; margin: 0 0 1em 0; color: #000000; font-size: 18px; } .DemoApp-content .form-field-input { display: flex; flex-direction: row; justify-content: center; align-content: center; text-align: center; height: 60px; width: 100%; margin: 0 0 1em 0; color: #000000; font-size: 26px; box-shadow: none; border: none; background: rgba(0,0,0,0.05); } .DemoApp-content .form-field-input::placeholder { color: rgba(0,0,0,0.2); } .DemoApp-content .form-field-input:focus { outline: none; } .DemoApp-content .form-field-button { display: flex; flex-direction: row; align-items: center; justify-content: center; width: 200px; height: 50px; padding: 0.5em 1em 0.5em 1em; background: #F15953; color: white; font-size: 16px; font-weight: 600; border: none; user-select: none; } .DemoApp-content .form-field-button-container { display: flex; flex-direction: row; justify-content: center; width: 100%; height: auto; margin-top: 0.75em; } .DemoApp-content .form-field-button:focus { outline: none; user-select: none; } .DemoApp-content .form-field-button:hover { cursor: pointer; } .DemoApp-content .success { display: flex; flex-direction: column; align-items: center; font-size: 32px; margin: 5em 0 6.4em 0; color: #000; } /* * Animations */ @charset "UTF-8"; /*! * animate.css -http://daneden.me/animate * Version - 3.7.0 * Licensed under the MIT license - http://opensource.org/licenses/MIT * * Copyright (c) 2018 Daniel Eden */ @-webkit-keyframes bounce { from, 20%, 53%, 80%, to { -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } 40%, 43% { -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); -webkit-transform: translate3d(0, -30px, 0); transform: translate3d(0, -30px, 0); } 70% { -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); -webkit-transform: translate3d(0, -15px, 0); transform: translate3d(0, -15px, 0); } 90% { -webkit-transform: translate3d(0, -4px, 0); transform: translate3d(0, -4px, 0); } } @keyframes bounce { from, 20%, 53%, 80%, to { -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } 40%, 43% { -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); -webkit-transform: translate3d(0, -30px, 0); transform: translate3d(0, -30px, 0); } 70% { -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); -webkit-transform: translate3d(0, -15px, 0); transform: translate3d(0, -15px, 0); } 90% { -webkit-transform: translate3d(0, -4px, 0); transform: translate3d(0, -4px, 0); } } .bounce { -webkit-animation-name: bounce; animation-name: bounce; -webkit-transform-origin: center bottom; transform-origin: center bottom; } @-webkit-keyframes flash { from, 50%, to { opacity: 1; } 25%, 75% { opacity: 0; } } @keyframes flash { from, 50%, to { opacity: 1; } 25%, 75% { opacity: 0; } } .flash { -webkit-animation-name: flash; animation-name: flash; } /* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ @-webkit-keyframes pulse { from { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } 50% { -webkit-transform: scale3d(1.05, 1.05, 1.05); transform: scale3d(1.05, 1.05, 1.05); } to { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } } @keyframes pulse { from { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } 50% { -webkit-transform: scale3d(1.05, 1.05, 1.05); transform: scale3d(1.05, 1.05, 1.05); } to { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } } .pulse { -webkit-animation-name: pulse; animation-name: pulse; } @-webkit-keyframes rubberBand { from { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } 30% { -webkit-transform: scale3d(1.25, 0.75, 1); transform: scale3d(1.25, 0.75, 1); } 40% { -webkit-transform: scale3d(0.75, 1.25, 1); transform: scale3d(0.75, 1.25, 1); } 50% { -webkit-transform: scale3d(1.15, 0.85, 1); transform: scale3d(1.15, 0.85, 1); } 65% { -webkit-transform: scale3d(0.95, 1.05, 1); transform: scale3d(0.95, 1.05, 1); } 75% { -webkit-transform: scale3d(1.05, 0.95, 1); transform: scale3d(1.05, 0.95, 1); } to { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } } @keyframes rubberBand { from { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } 30% { -webkit-transform: scale3d(1.25, 0.75, 1); transform: scale3d(1.25, 0.75, 1); } 40% { -webkit-transform: scale3d(0.75, 1.25, 1); transform: scale3d(0.75, 1.25, 1); } 50% { -webkit-transform: scale3d(1.15, 0.85, 1); transform: scale3d(1.15, 0.85, 1); } 65% { -webkit-transform: scale3d(0.95, 1.05, 1); transform: scale3d(0.95, 1.05, 1); } 75% { -webkit-transform: scale3d(1.05, 0.95, 1); transform: scale3d(1.05, 0.95, 1); } to { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } } .rubberBand { -webkit-animation-name: rubberBand; animation-name: rubberBand; } @-webkit-keyframes shake { from, to { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } 10%, 30%, 50%, 70%, 90% { -webkit-transform: translate3d(-10px, 0, 0); transform: translate3d(-10px, 0, 0); } 20%, 40%, 60%, 80% { -webkit-transform: translate3d(10px, 0, 0); transform: translate3d(10px, 0, 0); } } @keyframes shake { from, to { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } 10%, 30%, 50%, 70%, 90% { -webkit-transform: translate3d(-10px, 0, 0); transform: translate3d(-10px, 0, 0); } 20%, 40%, 60%, 80% { -webkit-transform: translate3d(10px, 0, 0); transform: translate3d(10px, 0, 0); } } .shake { -webkit-animation-name: shake; animation-name: shake; } @-webkit-keyframes headShake { 0% { -webkit-transform: translateX(0); transform: translateX(0); } 6.5% { -webkit-transform: translateX(-6px) rotateY(-9deg); transform: translateX(-6px) rotateY(-9deg); } 18.5% { -webkit-transform: translateX(5px) rotateY(7deg); transform: translateX(5px) rotateY(7deg); } 31.5% { -webkit-transform: translateX(-3px) rotateY(-5deg); transform: translateX(-3px) rotateY(-5deg); } 43.5% { -webkit-transform: translateX(2px) rotateY(3deg); transform: translateX(2px) rotateY(3deg); } 50% { -webkit-transform: translateX(0); transform: translateX(0); } } @keyframes headShake { 0% { -webkit-transform: translateX(0); transform: translateX(0); } 6.5% { -webkit-transform: translateX(-6px) rotateY(-9deg); transform: translateX(-6px) rotateY(-9deg); } 18.5% { -webkit-transform: translateX(5px) rotateY(7deg); transform: translateX(5px) rotateY(7deg); } 31.5% { -webkit-transform: translateX(-3px) rotateY(-5deg); transform: translateX(-3px) rotateY(-5deg); } 43.5% { -webkit-transform: translateX(2px) rotateY(3deg); transform: translateX(2px) rotateY(3deg); } 50% { -webkit-transform: translateX(0); transform: translateX(0); } } .headShake { -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; -webkit-animation-name: headShake; animation-name: headShake; } @-webkit-keyframes swing { 20% { -webkit-transform: rotate3d(0, 0, 1, 15deg); transform: rotate3d(0, 0, 1, 15deg); } 40% { -webkit-transform: rotate3d(0, 0, 1, -10deg); transform: rotate3d(0, 0, 1, -10deg); } 60% { -webkit-transform: rotate3d(0, 0, 1, 5deg); transform: rotate3d(0, 0, 1, 5deg); } 80% { -webkit-transform: rotate3d(0, 0, 1, -5deg); transform: rotate3d(0, 0, 1, -5deg); } to { -webkit-transform: rotate3d(0, 0, 1, 0deg); transform: rotate3d(0, 0, 1, 0deg); } } @keyframes swing { 20% { -webkit-transform: rotate3d(0, 0, 1, 15deg); transform: rotate3d(0, 0, 1, 15deg); } 40% { -webkit-transform: rotate3d(0, 0, 1, -10deg); transform: rotate3d(0, 0, 1, -10deg); } 60% { -webkit-transform: rotate3d(0, 0, 1, 5deg); transform: rotate3d(0, 0, 1, 5deg); } 80% { -webkit-transform: rotate3d(0, 0, 1, -5deg); transform: rotate3d(0, 0, 1, -5deg); } to { -webkit-transform: rotate3d(0, 0, 1, 0deg); transform: rotate3d(0, 0, 1, 0deg); } } .swing { -webkit-transform-origin: top center; transform-origin: top center; -webkit-animation-name: swing; animation-name: swing; } @-webkit-keyframes tada { from { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } 10%, 20% { -webkit-transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg); transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg); } 30%, 50%, 70%, 90% { -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); } 40%, 60%, 80% { -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); } to { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } } @keyframes tada { from { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } 10%, 20% { -webkit-transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg); transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg); } 30%, 50%, 70%, 90% { -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); } 40%, 60%, 80% { -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); } to { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } } .tada { -webkit-animation-name: tada; animation-name: tada; } /* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ @-webkit-keyframes wobble { from { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } 15% { -webkit-transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); } 30% { -webkit-transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); } 45% { -webkit-transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); } 60% { -webkit-transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); } 75% { -webkit-transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); } to { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } @keyframes wobble { from { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } 15% { -webkit-transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); } 30% { -webkit-transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); } 45% { -webkit-transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); } 60% { -webkit-transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); } 75% { -webkit-transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); } to { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } .wobble { -webkit-animation-name: wobble; animation-name: wobble; } @-webkit-keyframes jello { from, 11.1%, to { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } 22.2% { -webkit-transform: skewX(-12.5deg) skewY(-12.5deg); transform: skewX(-12.5deg) skewY(-12.5deg); } 33.3% { -webkit-transform: skewX(6.25deg) skewY(6.25deg); transform: skewX(6.25deg) skewY(6.25deg); } 44.4% { -webkit-transform: skewX(-3.125deg) skewY(-3.125deg); transform: skewX(-3.125deg) skewY(-3.125deg); } 55.5% { -webkit-transform: skewX(1.5625deg) skewY(1.5625deg); transform: skewX(1.5625deg) skewY(1.5625deg); } 66.6% { -webkit-transform: skewX(-0.78125deg) skewY(-0.78125deg); transform: skewX(-0.78125deg) skewY(-0.78125deg); } 77.7% { -webkit-transform: skewX(0.390625deg) skewY(0.390625deg); transform: skewX(0.390625deg) skewY(0.390625deg); } 88.8% { -webkit-transform: skewX(-0.1953125deg) skewY(-0.1953125deg); transform: skewX(-0.1953125deg) skewY(-0.1953125deg); } } @keyframes jello { from, 11.1%, to { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } 22.2% { -webkit-transform: skewX(-12.5deg) skewY(-12.5deg); transform: skewX(-12.5deg) skewY(-12.5deg); } 33.3% { -webkit-transform: skewX(6.25deg) skewY(6.25deg); transform: skewX(6.25deg) skewY(6.25deg); } 44.4% { -webkit-transform: skewX(-3.125deg) skewY(-3.125deg); transform: skewX(-3.125deg) skewY(-3.125deg); } 55.5% { -webkit-transform: skewX(1.5625deg) skewY(1.5625deg); transform: skewX(1.5625deg) skewY(1.5625deg); } 66.6% { -webkit-transform: skewX(-0.78125deg) skewY(-0.78125deg); transform: skewX(-0.78125deg) skewY(-0.78125deg); } 77.7% { -webkit-transform: skewX(0.390625deg) skewY(0.390625deg); transform: skewX(0.390625deg) skewY(0.390625deg); } 88.8% { -webkit-transform: skewX(-0.1953125deg) skewY(-0.1953125deg); transform: skewX(-0.1953125deg) skewY(-0.1953125deg); } } .jello { -webkit-animation-name: jello; animation-name: jello; -webkit-transform-origin: center; transform-origin: center; } @-webkit-keyframes heartBeat { 0% { -webkit-transform: scale(1); transform: scale(1); } 14% { -webkit-transform: scale(1.3); transform: scale(1.3); } 28% { -webkit-transform: scale(1); transform: scale(1); } 42% { -webkit-transform: scale(1.3); transform: scale(1.3); } 70% { -webkit-transform: scale(1); transform: scale(1); } } @keyframes heartBeat { 0% { -webkit-transform: scale(1); transform: scale(1); } 14% { -webkit-transform: scale(1.3); transform: scale(1.3); } 28% { -webkit-transform: scale(1); transform: scale(1); } 42% { -webkit-transform: scale(1.3); transform: scale(1.3); } 70% { -webkit-transform: scale(1); transform: scale(1); } } .heartBeat { -webkit-animation-name: heartBeat; animation-name: heartBeat; -webkit-animation-duration: 1.3s; animation-duration: 1.3s; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; } @-webkit-keyframes bounceIn { from, 20%, 40%, 60%, 80%, to { -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); } 0% { opacity: 0; -webkit-transform: scale3d(0.3, 0.3, 0.3); transform: scale3d(0.3, 0.3, 0.3); } 20% { -webkit-transform: scale3d(1.1, 1.1, 1.1); transform: scale3d(1.1, 1.1, 1.1); } 40% { -webkit-transform: scale3d(0.9, 0.9, 0.9); transform: scale3d(0.9, 0.9, 0.9); } 60% { opacity: 1; -webkit-transform: scale3d(1.03, 1.03, 1.03); transform: scale3d(1.03, 1.03, 1.03); } 80% { -webkit-transform: scale3d(0.97, 0.97, 0.97); transform: scale3d(0.97, 0.97, 0.97); } to { opacity: 1; -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } } @keyframes bounceIn { from, 20%, 40%, 60%, 80%, to { -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); } 0% { opacity: 0; -webkit-transform: scale3d(0.3, 0.3, 0.3); transform: scale3d(0.3, 0.3, 0.3); } 20% { -webkit-transform: scale3d(1.1, 1.1, 1.1); transform: scale3d(1.1, 1.1, 1.1); } 40% { -webkit-transform: scale3d(0.9, 0.9, 0.9); transform: scale3d(0.9, 0.9, 0.9); } 60% { opacity: 1; -webkit-transform: scale3d(1.03, 1.03, 1.03); transform: scale3d(1.03, 1.03, 1.03); } 80% { -webkit-transform: scale3d(0.97, 0.97, 0.97); transform: scale3d(0.97, 0.97, 0.97); } to { opacity: 1; -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } } .bounceIn { -webkit-animation-duration: 0.75s; animation-duration: 0.75s; -webkit-animation-name: bounceIn; animation-name: bounceIn; } @-webkit-keyframes bounceInDown { from, 60%, 75%, 90%, to { -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); } 0% { opacity: 0; -webkit-transform: translate3d(0, -3000px, 0); transform: translate3d(0, -3000px, 0); } 60% { opacity: 1; -webkit-transform: translate3d(0, 25px, 0); transform: translate3d(0, 25px, 0); } 75% { -webkit-transform: translate3d(0, -10px, 0); transform: translate3d(0, -10px, 0); } 90% { -webkit-transform: translate3d(0, 5px, 0); transform: translate3d(0, 5px, 0); } to { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } @keyframes bounceInDown { from, 60%, 75%, 90%, to { -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); } 0% { opacity: 0; -webkit-transform: translate3d(0, -3000px, 0); transform: translate3d(0, -3000px, 0); } 60% { opacity: 1; -webkit-transform: translate3d(0, 25px, 0); transform: translate3d(0, 25px, 0); } 75% { -webkit-transform: translate3d(0, -10px, 0); transform: translate3d(0, -10px, 0); } 90% { -webkit-transform: translate3d(0, 5px, 0); transform: translate3d(0, 5px, 0); } to { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } .bounceInDown { -webkit-animation-name: bounceInDown; animation-name: bounceInDown; } @-webkit-keyframes bounceInLeft { from, 60%, 75%, 90%, to { -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); } 0% { opacity: 0; -webkit-transform: translate3d(-3000px, 0, 0); transform: translate3d(-3000px, 0, 0); } 60% { opacity: 1; -webkit-transform: translate3d(25px, 0, 0); transform: translate3d(25px, 0, 0); } 75% { -webkit-transform: translate3d(-10px, 0, 0); transform: translate3d(-10px, 0, 0); } 90% { -webkit-transform: translate3d(5px, 0, 0); transform: translate3d(5px, 0, 0); } to { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } @keyframes bounceInLeft { from, 60%, 75%, 90%, to { -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); } 0% { opacity: 0; -webkit-transform: translate3d(-3000px, 0, 0); transform: translate3d(-3000px, 0, 0); } 60% { opacity: 1; -webkit-transform: translate3d(25px, 0, 0); transform: translate3d(25px, 0, 0); } 75% { -webkit-transform: translate3d(-10px, 0, 0); transform: translate3d(-10px, 0, 0); } 90% { -webkit-transform: translate3d(5px, 0, 0); transform: translate3d(5px, 0, 0); } to { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } .bounceInLeft { -webkit-animation-name: bounceInLeft; animation-name: bounceInLeft; } @-webkit-keyframes bounceInRight { from, 60%, 75%, 90%, to { -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); } from { opacity: 0; -webkit-transform: translate3d(3000px, 0, 0); transform: translate3d(3000px, 0, 0); } 60% { opacity: 1; -webkit-transform: translate3d(-25px, 0, 0); transform: translate3d(-25px, 0, 0); } 75% { -webkit-transform: translate3d(10px, 0, 0); transform: translate3d(10px, 0, 0); } 90% { -webkit-transform: translate3d(-5px, 0, 0); transform: translate3d(-5px, 0, 0); } to { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } @keyframes bounceInRight { from, 60%, 75%, 90%, to { -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); } from { opacity: 0; -webkit-transform: translate3d(3000px, 0, 0); transform: translate3d(3000px, 0, 0); } 60% { opacity: 1; -webkit-transform: translate3d(-25px, 0, 0); transform: translate3d(-25px, 0, 0); } 75% { -webkit-transform: translate3d(10px, 0, 0); transform: translate3d(10px, 0, 0); } 90% { -webkit-transform: translate3d(-5px, 0, 0); transform: translate3d(-5px, 0, 0); } to { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } .bounceInRight { -webkit-animation-name: bounceInRight; animation-name: bounceInRight; } @-webkit-keyframes bounceInUp { from, 60%, 75%, 90%, to { -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); } from { opacity: 0; -webkit-transform: translate3d(0, 3000px, 0); transform: translate3d(0, 3000px, 0); } 60% { opacity: 1; -webkit-transform: translate3d(0, -20px, 0); transform: translate3d(0, -20px, 0); } 75% { -webkit-transform: translate3d(0, 10px, 0); transform: translate3d(0, 10px, 0); } 90% { -webkit-transform: translate3d(0, -5px, 0); transform: translate3d(0, -5px, 0); } to { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } @keyframes bounceInUp { from, 60%, 75%, 90%, to { -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); } from { opacity: 0; -webkit-transform: translate3d(0, 3000px, 0); transform: translate3d(0, 3000px, 0); } 60% { opacity: 1; -webkit-transform: translate3d(0, -20px, 0); transform: translate3d(0, -20px, 0); } 75% { -webkit-transform: translate3d(0, 10px, 0); transform: translate3d(0, 10px, 0); } 90% { -webkit-transform: translate3d(0, -5px, 0); transform: translate3d(0, -5px, 0); } to { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } .bounceInUp { -webkit-animation-name: bounceInUp; animation-name: bounceInUp; } @-webkit-keyframes bounceOut { 20% { -webkit-transform: scale3d(0.9, 0.9, 0.9); transform: scale3d(0.9, 0.9, 0.9); } 50%, 55% { opacity: 1; -webkit-transform: scale3d(1.1, 1.1, 1.1); transform: scale3d(1.1, 1.1, 1.1); } to { opacity: 0; -webkit-transform: scale3d(0.3, 0.3, 0.3); transform: scale3d(0.3, 0.3, 0.3); } } @keyframes bounceOut { 20% { -webkit-transform: scale3d(0.9, 0.9, 0.9); transform: scale3d(0.9, 0.9, 0.9); } 50%, 55% { opacity: 1; -webkit-transform: scale3d(1.1, 1.1, 1.1); transform: scale3d(1.1, 1.1, 1.1); } to { opacity: 0; -webkit-transform: scale3d(0.3, 0.3, 0.3); transform: scale3d(0.3, 0.3, 0.3); } } .bounceOut { -webkit-animation-duration: 0.75s; animation-duration: 0.75s; -webkit-animation-name: bounceOut; animation-name: bounceOut; } @-webkit-keyframes bounceOutDown { 20% { -webkit-transform: translate3d(0, 10px, 0); transform: translate3d(0, 10px, 0); } 40%, 45% { opacity: 1; -webkit-transform: translate3d(0, -20px, 0); transform: translate3d(0, -20px, 0); } to { opacity: 0; -webkit-transform: translate3d(0, 2000px, 0); transform: translate3d(0, 2000px, 0); } } @keyframes bounceOutDown { 20% { -webkit-transform: translate3d(0, 10px, 0); transform: translate3d(0, 10px, 0); } 40%, 45% { opacity: 1; -webkit-transform: translate3d(0, -20px, 0); transform: translate3d(0, -20px, 0); } to { opacity: 0; -webkit-transform: translate3d(0, 2000px, 0); transform: translate3d(0, 2000px, 0); } } .bounceOutDown { -webkit-animation-name: bounceOutDown; animation-name: bounceOutDown; } @-webkit-keyframes bounceOutLeft { 20% { opacity: 1; -webkit-transform: translate3d(20px, 0, 0); transform: translate3d(20px, 0, 0); } to { opacity: 0; -webkit-transform: translate3d(-2000px, 0, 0); transform: translate3d(-2000px, 0, 0); } } @keyframes bounceOutLeft { 20% { opacity: 1; -webkit-transform: translate3d(20px, 0, 0); transform: translate3d(20px, 0, 0); } to { opacity: 0; -webkit-transform: translate3d(-2000px, 0, 0); transform: translate3d(-2000px, 0, 0); } } .bounceOutLeft { -webkit-animation-name: bounceOutLeft; animation-name: bounceOutLeft; } @-webkit-keyframes bounceOutRight { 20% { opacity: 1; -webkit-transform: translate3d(-20px, 0, 0); transform: translate3d(-20px, 0, 0); } to { opacity: 0; -webkit-transform: translate3d(2000px, 0, 0); transform: translate3d(2000px, 0, 0); } } @keyframes bounceOutRight { 20% { opacity: 1; -webkit-transform: translate3d(-20px, 0, 0); transform: translate3d(-20px, 0, 0); } to { opacity: 0; -webkit-transform: translate3d(2000px, 0, 0); transform: translate3d(2000px, 0, 0); } } .bounceOutRight { -webkit-animation-name: bounceOutRight; animation-name: bounceOutRight; } @-webkit-keyframes bounceOutUp { 20% { -webkit-transform: translate3d(0, -10px, 0); transform: translate3d(0, -10px, 0); } 40%, 45% { opacity: 1; -webkit-transform: translate3d(0, 20px, 0); transform: translate3d(0, 20px, 0); } to { opacity: 0; -webkit-transform: translate3d(0, -2000px, 0); transform: translate3d(0, -2000px, 0); } } @keyframes bounceOutUp { 20% { -webkit-transform: translate3d(0, -10px, 0); transform: translate3d(0, -10px, 0); } 40%, 45% { opacity: 1; -webkit-transform: translate3d(0, 20px, 0); transform: translate3d(0, 20px, 0); } to { opacity: 0; -webkit-transform: translate3d(0, -2000px, 0); transform: translate3d(0, -2000px, 0); } } .bounceOutUp { -webkit-animation-name: bounceOutUp; animation-name: bounceOutUp; } @-webkit-keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .fadeIn { -webkit-animation-name: fadeIn; animation-name: fadeIn; } @-webkit-keyframes fadeInDown { from { opacity: 0; -webkit-transform: translate3d(0, -100%, 0); transform: translate3d(0, -100%, 0); } to { opacity: 1; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } @keyframes fadeInDown { from { opacity: 0; -webkit-transform: translate3d(0, -100%, 0); transform: translate3d(0, -100%, 0); } to { opacity: 1; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } .fadeInDown { -webkit-animation-name: fadeInDown; animation-name: fadeInDown; } @-webkit-keyframes fadeInDownBig { from { opacity: 0; -webkit-transform: translate3d(0, -2000px, 0); transform: translate3d(0, -2000px, 0); } to { opacity: 1; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } @keyframes fadeInDownBig { from { opacity: 0; -webkit-transform: translate3d(0, -2000px, 0); transform: translate3d(0, -2000px, 0); } to { opacity: 1; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } .fadeInDownBig { -webkit-animation-name: fadeInDownBig; animation-name: fadeInDownBig; } @-webkit-keyframes fadeInLeft { from { opacity: 0; -webkit-transform: translate3d(-100%, 0, 0); transform: translate3d(-100%, 0, 0); } to { opacity: 1; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } @keyframes fadeInLeft { from { opacity: 0; -webkit-transform: translate3d(-100%, 0, 0); transform: translate3d(-100%, 0, 0); } to { opacity: 1; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } .fadeInLeft { -webkit-animation-name: fadeInLeft; animation-name: fadeInLeft; } @-webkit-keyframes fadeInLeftBig { from { opacity: 0; -webkit-transform: translate3d(-2000px, 0, 0); transform: translate3d(-2000px, 0, 0); } to { opacity: 1; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } @keyframes fadeInLeftBig { from { opacity: 0; -webkit-transform: translate3d(-2000px, 0, 0); transform: translate3d(-2000px, 0, 0); } to { opacity: 1; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } .fadeInLeftBig { -webkit-animation-name: fadeInLeftBig; animation-name: fadeInLeftBig; } @-webkit-keyframes fadeInRight { from { opacity: 0; -webkit-transform: translate3d(100%, 0, 0); transform: translate3d(100%, 0, 0); } to { opacity: 1; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } @keyframes fadeInRight { from { opacity: 0; -webkit-transform: translate3d(100%, 0, 0); transform: translate3d(100%, 0, 0); } to { opacity: 1; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } .fadeInRight { -webkit-animation-name: fadeInRight; animation-name: fadeInRight; } @-webkit-keyframes fadeInRightBig { from { opacity: 0; -webkit-transform: translate3d(2000px, 0, 0); transform: translate3d(2000px, 0, 0); } to { opacity: 1; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } @keyframes fadeInRightBig { from { opacity: 0; -webkit-transform: translate3d(2000px, 0, 0); transform: translate3d(2000px, 0, 0); } to { opacity: 1; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } .fadeInRightBig { -webkit-animation-name: fadeInRightBig; animation-name: fadeInRightBig; } @-webkit-keyframes fadeInUp { from { opacity: 0; -webkit-transform: translate3d(0, 100%, 0); transform: translate3d(0, 100%, 0); } to { opacity: 1; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } @keyframes fadeInUp { from { opacity: 0; -webkit-transform: translate3d(0, 100%, 0); transform: translate3d(0, 100%, 0); } to { opacity: 1; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } .fadeInUp { -webkit-animation-name: fadeInUp; animation-name: fadeInUp; } @-webkit-keyframes fadeInUpBig { from { opacity: 0; -webkit-transform: translate3d(0, 2000px, 0); transform: translate3d(0, 2000px, 0); } to { opacity: 1; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } @keyframes fadeInUpBig { from { opacity: 0; -webkit-transform: translate3d(0, 2000px, 0); transform: translate3d(0, 2000px, 0); } to { opacity: 1; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } .fadeInUpBig { -webkit-animation-name: fadeInUpBig; animation-name: fadeInUpBig; } @-webkit-keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } .fadeOut { -webkit-animation-name: fadeOut; animation-name: fadeOut; } @-webkit-keyframes fadeOutDown { from { opacity: 1; } to { opacity: 0; -webkit-transform: translate3d(0, 100%, 0); transform: translate3d(0, 100%, 0); } } @keyframes fadeOutDown { from { opacity: 1; } to { opacity: 0; -webkit-transform: translate3d(0, 100%, 0); transform: translate3d(0, 100%, 0); } } .fadeOutDown { -webkit-animation-name: fadeOutDown; animation-name: fadeOutDown; } @-webkit-keyframes fadeOutDownBig { from { opacity: 1; } to { opacity: 0; -webkit-transform: translate3d(0, 2000px, 0); transform: translate3d(0, 2000px, 0); } } @keyframes fadeOutDownBig { from { opacity: 1; } to { opacity: 0; -webkit-transform: translate3d(0, 2000px, 0); transform: translate3d(0, 2000px, 0); } } .fadeOutDownBig { -webkit-animation-name: fadeOutDownBig; animation-name: fadeOutDownBig; } @-webkit-keyframes fadeOutLeft { from { opacity: 1; } to { opacity: 0; -webkit-transform: translate3d(-100%, 0, 0); transform: translate3d(-100%, 0, 0); } } @keyframes fadeOutLeft { from { opacity: 1; } to { opacity: 0; -webkit-transform: translate3d(-100%, 0, 0); transform: translate3d(-100%, 0, 0); } } .fadeOutLeft { -webkit-animation-name: fadeOutLeft; animation-name: fadeOutLeft; } @-webkit-keyframes fadeOutLeftBig { from { opacity: 1; } to { opacity: 0; -webkit-transform: translate3d(-2000px, 0, 0); transform: translate3d(-2000px, 0, 0); } } @keyframes fadeOutLeftBig { from { opacity: 1; } to { opacity: 0; -webkit-transform: translate3d(-2000px, 0, 0); transform: translate3d(-2000px, 0, 0); } } .fadeOutLeftBig { -webkit-animation-name: fadeOutLeftBig; animation-name: fadeOutLeftBig; } @-webkit-keyframes fadeOutRight { from { opacity: 1; } to { opacity: 0; -webkit-transform: translate3d(100%, 0, 0); transform: translate3d(100%, 0, 0); } } @keyframes fadeOutRight { from { opacity: 1; } to { opacity: 0; -webkit-transform: translate3d(100%, 0, 0); transform: translate3d(100%, 0, 0); } } .fadeOutRight { -webkit-animation-name: fadeOutRight; animation-name: fadeOutRight; } @-webkit-keyframes fadeOutRightBig { from { opacity: 1; } to { opacity: 0; -webkit-transform: translate3d(2000px, 0, 0); transform: translate3d(2000px, 0, 0); } } @keyframes fadeOutRightBig { from { opacity: 1; } to { opacity: 0; -webkit-transform: translate3d(2000px, 0, 0); transform: translate3d(2000px, 0, 0); } } .fadeOutRightBig { -webkit-animation-name: fadeOutRightBig; animation-name: fadeOutRightBig; } @-webkit-keyframes fadeOutUp { from { opacity: 1; } to { opacity: 0; -webkit-transform: translate3d(0, -100%, 0); transform: translate3d(0, -100%, 0); } } @keyframes fadeOutUp { from { opacity: 1; } to { opacity: 0; -webkit-transform: translate3d(0, -100%, 0); transform: translate3d(0, -100%, 0); } } .fadeOutUp { -webkit-animation-name: fadeOutUp; animation-name: fadeOutUp; } @-webkit-keyframes fadeOutUpBig { from { opacity: 1; } to { opacity: 0; -webkit-transform: translate3d(0, -2000px, 0); transform: translate3d(0, -2000px, 0); } } @keyframes fadeOutUpBig { from { opacity: 1; } to { opacity: 0; -webkit-transform: translate3d(0, -2000px, 0); transform: translate3d(0, -2000px, 0); } } .fadeOutUpBig { -webkit-animation-name: fadeOutUpBig; animation-name: fadeOutUpBig; } @-webkit-keyframes flip { from { -webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 1, 0, -360deg); transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 1, 0, -360deg); -webkit-animation-timing-function: ease-out; animation-timing-function: ease-out; } 40% { -webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg); transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg); -webkit-animation-timing-function: ease-out; animation-timing-function: ease-out; } 50% { -webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg); transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } 80% { -webkit-transform: perspective(400px) scale3d(0.95, 0.95, 0.95) translate3d(0, 0, 0) rotate3d(0, 1, 0, 0deg); transform: perspective(400px) scale3d(0.95, 0.95, 0.95) translate3d(0, 0, 0) rotate3d(0, 1, 0, 0deg); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } to { -webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 1, 0, 0deg); transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 1, 0, 0deg); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } } @keyframes flip { from { -webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 1, 0, -360deg); transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 1, 0, -360deg); -webkit-animation-timing-function: ease-out; animation-timing-function: ease-out; } 40% { -webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg); transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg); -webkit-animation-timing-function: ease-out; animation-timing-function: ease-out; } 50% { -webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg); transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } 80% { -webkit-transform: perspective(400px) scale3d(0.95, 0.95, 0.95) translate3d(0, 0, 0) rotate3d(0, 1, 0, 0deg); transform: perspective(400px) scale3d(0.95, 0.95, 0.95) translate3d(0, 0, 0) rotate3d(0, 1, 0, 0deg); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } to { -webkit-transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 1, 0, 0deg); transform: perspective(400px) scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 1, 0, 0deg); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } } .animated.flip { -webkit-backface-visibility: visible; backface-visibility: visible; -webkit-animation-name: flip; animation-name: flip; } @-webkit-keyframes flipInX { from { -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); transform: perspective(400px) rotate3d(1, 0, 0, 90deg); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; opacity: 0; } 40% { -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); transform: perspective(400px) rotate3d(1, 0, 0, -20deg); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } 60% { -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 10deg); transform: perspective(400px) rotate3d(1, 0, 0, 10deg); opacity: 1; } 80% { -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -5deg); transform: perspective(400px) rotate3d(1, 0, 0, -5deg); } to { -webkit-transform: perspective(400px); transform: perspective(400px); } } @keyframes flipInX { from { -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); transform: perspective(400px) rotate3d(1, 0, 0, 90deg); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; opacity: 0; } 40% { -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); transform: perspective(400px) rotate3d(1, 0, 0, -20deg); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } 60% { -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 10deg); transform: perspective(400px) rotate3d(1, 0, 0, 10deg); opacity: 1; } 80% { -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -5deg); transform: perspective(400px) rotate3d(1, 0, 0, -5deg); } to { -webkit-transform: perspective(400px); transform: perspective(400px); } } .flipInX { -webkit-backface-visibility: visible !important; backface-visibility: visible !important; -webkit-animation-name: flipInX; animation-name: flipInX; } @-webkit-keyframes flipInY { from { -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); transform: perspective(400px) rotate3d(0, 1, 0, 90deg); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; opacity: 0; } 40% { -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -20deg); transform: perspective(400px) rotate3d(0, 1, 0, -20deg); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } 60% { -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 10deg); transform: perspective(400px) rotate3d(0, 1, 0, 10deg); opacity: 1; } 80% { -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -5deg); transform: perspective(400px) rotate3d(0, 1, 0, -5deg); } to { -webkit-transform: perspective(400px); transform: perspective(400px); } } @keyframes flipInY { from { -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); transform: perspective(400px) rotate3d(0, 1, 0, 90deg); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; opacity: 0; } 40% { -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -20deg); transform: perspective(400px) rotate3d(0, 1, 0, -20deg); -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } 60% { -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 10deg); transform: perspective(400px) rotate3d(0, 1, 0, 10deg); opacity: 1; } 80% { -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -5deg); transform: perspective(400px) rotate3d(0, 1, 0, -5deg); } to { -webkit-transform: perspective(400px); transform: perspective(400px); } } .flipInY { -webkit-backface-visibility: visible !important; backface-visibility: visible !important; -webkit-animation-name: flipInY; animation-name: flipInY; } @-webkit-keyframes flipOutX { from { -webkit-transform: perspective(400px); transform: perspective(400px); } 30% { -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); transform: perspective(400px) rotate3d(1, 0, 0, -20deg); opacity: 1; } to { -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); transform: perspective(400px) rotate3d(1, 0, 0, 90deg); opacity: 0; } } @keyframes flipOutX { from { -webkit-transform: perspective(400px); transform: perspective(400px); } 30% { -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); transform: perspective(400px) rotate3d(1, 0, 0, -20deg); opacity: 1; } to { -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); transform: perspective(400px) rotate3d(1, 0, 0, 90deg); opacity: 0; } } .flipOutX { -webkit-animation-duration: 0.75s; animation-duration: 0.75s; -webkit-animation-name: flipOutX; animation-name: flipOutX; -webkit-backface-visibility: visible !important; backface-visibility: visible !important; } @-webkit-keyframes flipOutY { from { -webkit-transform: perspective(400px); transform: perspective(400px); } 30% { -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -15deg); transform: perspective(400px) rotate3d(0, 1, 0, -15deg); opacity: 1; } to { -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); transform: perspective(400px) rotate3d(0, 1, 0, 90deg); opacity: 0; } } @keyframes flipOutY { from { -webkit-transform: perspective(400px); transform: perspective(400px); } 30% { -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -15deg); transform: perspective(400px) rotate3d(0, 1, 0, -15deg); opacity: 1; } to { -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); transform: perspective(400px) rotate3d(0, 1, 0, 90deg); opacity: 0; } } .flipOutY { -webkit-animation-duration: 0.75s; animation-duration: 0.75s; -webkit-backface-visibility: visible !important; backface-visibility: visible !important; -webkit-animation-name: flipOutY; animation-name: flipOutY; } @-webkit-keyframes lightSpeedIn { from { -webkit-transform: translate3d(100%, 0, 0) skewX(-30deg); transform: translate3d(100%, 0, 0) skewX(-30deg); opacity: 0; } 60% { -webkit-transform: skewX(20deg); transform: skewX(20deg); opacity: 1; } 80% { -webkit-transform: skewX(-5deg); transform: skewX(-5deg); } to { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } @keyframes lightSpeedIn { from { -webkit-transform: translate3d(100%, 0, 0) skewX(-30deg); transform: translate3d(100%, 0, 0) skewX(-30deg); opacity: 0; } 60% { -webkit-transform: skewX(20deg); transform: skewX(20deg); opacity: 1; } 80% { -webkit-transform: skewX(-5deg); transform: skewX(-5deg); } to { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } .lightSpeedIn { -webkit-animation-name: lightSpeedIn; animation-name: lightSpeedIn; -webkit-animation-timing-function: ease-out; animation-timing-function: ease-out; } @-webkit-keyframes lightSpeedOut { from { opacity: 1; } to { -webkit-transform: translate3d(100%, 0, 0) skewX(30deg); transform: translate3d(100%, 0, 0) skewX(30deg); opacity: 0; } } @keyframes lightSpeedOut { from { opacity: 1; } to { -webkit-transform: translate3d(100%, 0, 0) skewX(30deg); transform: translate3d(100%, 0, 0) skewX(30deg); opacity: 0; } } .lightSpeedOut { -webkit-animation-name: lightSpeedOut; animation-name: lightSpeedOut; -webkit-animation-timing-function: ease-in; animation-timing-function: ease-in; } @-webkit-keyframes rotateIn { from { -webkit-transform-origin: center; transform-origin: center; -webkit-transform: rotate3d(0, 0, 1, -200deg); transform: rotate3d(0, 0, 1, -200deg); opacity: 0; } to { -webkit-transform-origin: center; transform-origin: center; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); opacity: 1; } } @keyframes rotateIn { from { -webkit-transform-origin: center; transform-origin: center; -webkit-transform: rotate3d(0, 0, 1, -200deg); transform: rotate3d(0, 0, 1, -200deg); opacity: 0; } to { -webkit-transform-origin: center; transform-origin: center; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); opacity: 1; } } .rotateIn { -webkit-animation-name: rotateIn; animation-name: rotateIn; } @-webkit-keyframes rotateInDownLeft { from { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate3d(0, 0, 1, -45deg); transform: rotate3d(0, 0, 1, -45deg); opacity: 0; } to { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); opacity: 1; } } @keyframes rotateInDownLeft { from { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate3d(0, 0, 1, -45deg); transform: rotate3d(0, 0, 1, -45deg); opacity: 0; } to { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); opacity: 1; } } .rotateInDownLeft { -webkit-animation-name: rotateInDownLeft; animation-name: rotateInDownLeft; } @-webkit-keyframes rotateInDownRight { from { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate3d(0, 0, 1, 45deg); transform: rotate3d(0, 0, 1, 45deg); opacity: 0; } to { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); opacity: 1; } } @keyframes rotateInDownRight { from { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate3d(0, 0, 1, 45deg); transform: rotate3d(0, 0, 1, 45deg); opacity: 0; } to { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); opacity: 1; } } .rotateInDownRight { -webkit-animation-name: rotateInDownRight; animation-name: rotateInDownRight; } @-webkit-keyframes rotateInUpLeft { from { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate3d(0, 0, 1, 45deg); transform: rotate3d(0, 0, 1, 45deg); opacity: 0; } to { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); opacity: 1; } } @keyframes rotateInUpLeft { from { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate3d(0, 0, 1, 45deg); transform: rotate3d(0, 0, 1, 45deg); opacity: 0; } to { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); opacity: 1; } } .rotateInUpLeft { -webkit-animation-name: rotateInUpLeft; animation-name: rotateInUpLeft; } @-webkit-keyframes rotateInUpRight { from { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate3d(0, 0, 1, -90deg); transform: rotate3d(0, 0, 1, -90deg); opacity: 0; } to { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); opacity: 1; } } @keyframes rotateInUpRight { from { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate3d(0, 0, 1, -90deg); transform: rotate3d(0, 0, 1, -90deg); opacity: 0; } to { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); opacity: 1; } } .rotateInUpRight { -webkit-animation-name: rotateInUpRight; animation-name: rotateInUpRight; } @-webkit-keyframes rotateOut { from { -webkit-transform-origin: center; transform-origin: center; opacity: 1; } to { -webkit-transform-origin: center; transform-origin: center; -webkit-transform: rotate3d(0, 0, 1, 200deg); transform: rotate3d(0, 0, 1, 200deg); opacity: 0; } } @keyframes rotateOut { from { -webkit-transform-origin: center; transform-origin: center; opacity: 1; } to { -webkit-transform-origin: center; transform-origin: center; -webkit-transform: rotate3d(0, 0, 1, 200deg); transform: rotate3d(0, 0, 1, 200deg); opacity: 0; } } .rotateOut { -webkit-animation-name: rotateOut; animation-name: rotateOut; } @-webkit-keyframes rotateOutDownLeft { from { -webkit-transform-origin: left bottom; transform-origin: left bottom; opacity: 1; } to { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate3d(0, 0, 1, 45deg); transform: rotate3d(0, 0, 1, 45deg); opacity: 0; } } @keyframes rotateOutDownLeft { from { -webkit-transform-origin: left bottom; transform-origin: left bottom; opacity: 1; } to { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate3d(0, 0, 1, 45deg); transform: rotate3d(0, 0, 1, 45deg); opacity: 0; } } .rotateOutDownLeft { -webkit-animation-name: rotateOutDownLeft; animation-name: rotateOutDownLeft; } @-webkit-keyframes rotateOutDownRight { from { -webkit-transform-origin: right bottom; transform-origin: right bottom; opacity: 1; } to { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate3d(0, 0, 1, -45deg); transform: rotate3d(0, 0, 1, -45deg); opacity: 0; } } @keyframes rotateOutDownRight { from { -webkit-transform-origin: right bottom; transform-origin: right bottom; opacity: 1; } to { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate3d(0, 0, 1, -45deg); transform: rotate3d(0, 0, 1, -45deg); opacity: 0; } } .rotateOutDownRight { -webkit-animation-name: rotateOutDownRight; animation-name: rotateOutDownRight; } @-webkit-keyframes rotateOutUpLeft { from { -webkit-transform-origin: left bottom; transform-origin: left bottom; opacity: 1; } to { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate3d(0, 0, 1, -45deg); transform: rotate3d(0, 0, 1, -45deg); opacity: 0; } } @keyframes rotateOutUpLeft { from { -webkit-transform-origin: left bottom; transform-origin: left bottom; opacity: 1; } to { -webkit-transform-origin: left bottom; transform-origin: left bottom; -webkit-transform: rotate3d(0, 0, 1, -45deg); transform: rotate3d(0, 0, 1, -45deg); opacity: 0; } } .rotateOutUpLeft { -webkit-animation-name: rotateOutUpLeft; animation-name: rotateOutUpLeft; } @-webkit-keyframes rotateOutUpRight { from { -webkit-transform-origin: right bottom; transform-origin: right bottom; opacity: 1; } to { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate3d(0, 0, 1, 90deg); transform: rotate3d(0, 0, 1, 90deg); opacity: 0; } } @keyframes rotateOutUpRight { from { -webkit-transform-origin: right bottom; transform-origin: right bottom; opacity: 1; } to { -webkit-transform-origin: right bottom; transform-origin: right bottom; -webkit-transform: rotate3d(0, 0, 1, 90deg); transform: rotate3d(0, 0, 1, 90deg); opacity: 0; } } .rotateOutUpRight { -webkit-animation-name: rotateOutUpRight; animation-name: rotateOutUpRight; } @-webkit-keyframes hinge { 0% { -webkit-transform-origin: top left; transform-origin: top left; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; } 20%, 60% { -webkit-transform: rotate3d(0, 0, 1, 80deg); transform: rotate3d(0, 0, 1, 80deg); -webkit-transform-origin: top left; transform-origin: top left; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; } 40%, 80% { -webkit-transform: rotate3d(0, 0, 1, 60deg); transform: rotate3d(0, 0, 1, 60deg); -webkit-transform-origin: top left; transform-origin: top left; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; opacity: 1; } to { -webkit-transform: translate3d(0, 700px, 0); transform: translate3d(0, 700px, 0); opacity: 0; } } @keyframes hinge { 0% { -webkit-transform-origin: top left; transform-origin: top left; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; } 20%, 60% { -webkit-transform: rotate3d(0, 0, 1, 80deg); transform: rotate3d(0, 0, 1, 80deg); -webkit-transform-origin: top left; transform-origin: top left; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; } 40%, 80% { -webkit-transform: rotate3d(0, 0, 1, 60deg); transform: rotate3d(0, 0, 1, 60deg); -webkit-transform-origin: top left; transform-origin: top left; -webkit-animation-timing-function: ease-in-out; animation-timing-function: ease-in-out; opacity: 1; } to { -webkit-transform: translate3d(0, 700px, 0); transform: translate3d(0, 700px, 0); opacity: 0; } } .hinge { -webkit-animation-duration: 2s; animation-duration: 2s; -webkit-animation-name: hinge; animation-name: hinge; } @-webkit-keyframes jackInTheBox { from { opacity: 0; -webkit-transform: scale(0.1) rotate(30deg); transform: scale(0.1) rotate(30deg); -webkit-transform-origin: center bottom; transform-origin: center bottom; } 50% { -webkit-transform: rotate(-10deg); transform: rotate(-10deg); } 70% { -webkit-transform: rotate(3deg); transform: rotate(3deg); } to { opacity: 1; -webkit-transform: scale(1); transform: scale(1); } } @keyframes jackInTheBox { from { opacity: 0; -webkit-transform: scale(0.1) rotate(30deg); transform: scale(0.1) rotate(30deg); -webkit-transform-origin: center bottom; transform-origin: center bottom; } 50% { -webkit-transform: rotate(-10deg); transform: rotate(-10deg); } 70% { -webkit-transform: rotate(3deg); transform: rotate(3deg); } to { opacity: 1; -webkit-transform: scale(1); transform: scale(1); } } .jackInTheBox { -webkit-animation-name: jackInTheBox; animation-name: jackInTheBox; } /* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ @-webkit-keyframes rollIn { from { opacity: 0; -webkit-transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); } to { opacity: 1; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } @keyframes rollIn { from { opacity: 0; -webkit-transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); } to { opacity: 1; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } .rollIn { -webkit-animation-name: rollIn; animation-name: rollIn; } /* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ @-webkit-keyframes rollOut { from { opacity: 1; } to { opacity: 0; -webkit-transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); } } @keyframes rollOut { from { opacity: 1; } to { opacity: 0; -webkit-transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); } } .rollOut { -webkit-animation-name: rollOut; animation-name: rollOut; } @-webkit-keyframes zoomIn { from { opacity: 0; -webkit-transform: scale3d(0.3, 0.3, 0.3); transform: scale3d(0.3, 0.3, 0.3); } 50% { opacity: 1; } } @keyframes zoomIn { from { opacity: 0; -webkit-transform: scale3d(0.3, 0.3, 0.3); transform: scale3d(0.3, 0.3, 0.3); } 50% { opacity: 1; } } .zoomIn { -webkit-animation-name: zoomIn; animation-name: zoomIn; } @-webkit-keyframes zoomInDown { from { opacity: 0; -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -1000px, 0); transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -1000px, 0); -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); } 60% { opacity: 1; -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0); transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0); -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); } } @keyframes zoomInDown { from { opacity: 0; -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -1000px, 0); transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -1000px, 0); -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); } 60% { opacity: 1; -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0); transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0); -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); } } .zoomInDown { -webkit-animation-name: zoomInDown; animation-name: zoomInDown; } @-webkit-keyframes zoomInLeft { from { opacity: 0; -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(-1000px, 0, 0); transform: scale3d(0.1, 0.1, 0.1) translate3d(-1000px, 0, 0); -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); } 60% { opacity: 1; -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(10px, 0, 0); transform: scale3d(0.475, 0.475, 0.475) translate3d(10px, 0, 0); -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); } } @keyframes zoomInLeft { from { opacity: 0; -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(-1000px, 0, 0); transform: scale3d(0.1, 0.1, 0.1) translate3d(-1000px, 0, 0); -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); } 60% { opacity: 1; -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(10px, 0, 0); transform: scale3d(0.475, 0.475, 0.475) translate3d(10px, 0, 0); -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); } } .zoomInLeft { -webkit-animation-name: zoomInLeft; animation-name: zoomInLeft; } @-webkit-keyframes zoomInRight { from { opacity: 0; -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(1000px, 0, 0); transform: scale3d(0.1, 0.1, 0.1) translate3d(1000px, 0, 0); -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); } 60% { opacity: 1; -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(-10px, 0, 0); transform: scale3d(0.475, 0.475, 0.475) translate3d(-10px, 0, 0); -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); } } @keyframes zoomInRight { from { opacity: 0; -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(1000px, 0, 0); transform: scale3d(0.1, 0.1, 0.1) translate3d(1000px, 0, 0); -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); } 60% { opacity: 1; -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(-10px, 0, 0); transform: scale3d(0.475, 0.475, 0.475) translate3d(-10px, 0, 0); -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); } } .zoomInRight { -webkit-animation-name: zoomInRight; animation-name: zoomInRight; } @-webkit-keyframes zoomInUp { from { opacity: 0; -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 1000px, 0); transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 1000px, 0); -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); } 60% { opacity: 1; -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0); transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0); -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); } } @keyframes zoomInUp { from { opacity: 0; -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 1000px, 0); transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 1000px, 0); -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); } 60% { opacity: 1; -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0); transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0); -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); } } .zoomInUp { -webkit-animation-name: zoomInUp; animation-name: zoomInUp; } @-webkit-keyframes zoomOut { from { opacity: 1; } 50% { opacity: 0; -webkit-transform: scale3d(0.3, 0.3, 0.3); transform: scale3d(0.3, 0.3, 0.3); } to { opacity: 0; } } @keyframes zoomOut { from { opacity: 1; } 50% { opacity: 0; -webkit-transform: scale3d(0.3, 0.3, 0.3); transform: scale3d(0.3, 0.3, 0.3); } to { opacity: 0; } } .zoomOut { -webkit-animation-name: zoomOut; animation-name: zoomOut; } @-webkit-keyframes zoomOutDown { 40% { opacity: 1; -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0); transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0); -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); } to { opacity: 0; -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 2000px, 0); transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 2000px, 0); -webkit-transform-origin: center bottom; transform-origin: center bottom; -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); } } @keyframes zoomOutDown { 40% { opacity: 1; -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0); transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0); -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); } to { opacity: 0; -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 2000px, 0); transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 2000px, 0); -webkit-transform-origin: center bottom; transform-origin: center bottom; -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); } } .zoomOutDown { -webkit-animation-name: zoomOutDown; animation-name: zoomOutDown; } @-webkit-keyframes zoomOutLeft { 40% { opacity: 1; -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(42px, 0, 0); transform: scale3d(0.475, 0.475, 0.475) translate3d(42px, 0, 0); } to { opacity: 0; -webkit-transform: scale(0.1) translate3d(-2000px, 0, 0); transform: scale(0.1) translate3d(-2000px, 0, 0); -webkit-transform-origin: left center; transform-origin: left center; } } @keyframes zoomOutLeft { 40% { opacity: 1; -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(42px, 0, 0); transform: scale3d(0.475, 0.475, 0.475) translate3d(42px, 0, 0); } to { opacity: 0; -webkit-transform: scale(0.1) translate3d(-2000px, 0, 0); transform: scale(0.1) translate3d(-2000px, 0, 0); -webkit-transform-origin: left center; transform-origin: left center; } } .zoomOutLeft { -webkit-animation-name: zoomOutLeft; animation-name: zoomOutLeft; } @-webkit-keyframes zoomOutRight { 40% { opacity: 1; -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(-42px, 0, 0); transform: scale3d(0.475, 0.475, 0.475) translate3d(-42px, 0, 0); } to { opacity: 0; -webkit-transform: scale(0.1) translate3d(2000px, 0, 0); transform: scale(0.1) translate3d(2000px, 0, 0); -webkit-transform-origin: right center; transform-origin: right center; } } @keyframes zoomOutRight { 40% { opacity: 1; -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(-42px, 0, 0); transform: scale3d(0.475, 0.475, 0.475) translate3d(-42px, 0, 0); } to { opacity: 0; -webkit-transform: scale(0.1) translate3d(2000px, 0, 0); transform: scale(0.1) translate3d(2000px, 0, 0); -webkit-transform-origin: right center; transform-origin: right center; } } .zoomOutRight { -webkit-animation-name: zoomOutRight; animation-name: zoomOutRight; } @-webkit-keyframes zoomOutUp { 40% { opacity: 1; -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0); transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0); -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); } to { opacity: 0; -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -2000px, 0); transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -2000px, 0); -webkit-transform-origin: center bottom; transform-origin: center bottom; -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); } } @keyframes zoomOutUp { 40% { opacity: 1; -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0); transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0); -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); } to { opacity: 0; -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -2000px, 0); transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -2000px, 0); -webkit-transform-origin: center bottom; transform-origin: center bottom; -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1); } } .zoomOutUp { -webkit-animation-name: zoomOutUp; animation-name: zoomOutUp; } @-webkit-keyframes slideInDown { from { -webkit-transform: translate3d(0, -100%, 0); transform: translate3d(0, -100%, 0); visibility: visible; } to { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } @keyframes slideInDown { from { -webkit-transform: translate3d(0, -100%, 0); transform: translate3d(0, -100%, 0); visibility: visible; } to { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } .slideInDown { -webkit-animation-name: slideInDown; animation-name: slideInDown; } @-webkit-keyframes slideInLeft { from { -webkit-transform: translate3d(-100%, 0, 0); transform: translate3d(-100%, 0, 0); visibility: visible; } to { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } @keyframes slideInLeft { from { -webkit-transform: translate3d(-100%, 0, 0); transform: translate3d(-100%, 0, 0); visibility: visible; } to { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } .slideInLeft { -webkit-animation-name: slideInLeft; animation-name: slideInLeft; } @-webkit-keyframes slideInRight { from { -webkit-transform: translate3d(100%, 0, 0); transform: translate3d(100%, 0, 0); visibility: visible; } to { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } @keyframes slideInRight { from { -webkit-transform: translate3d(100%, 0, 0); transform: translate3d(100%, 0, 0); visibility: visible; } to { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } .slideInRight { -webkit-animation-name: slideInRight; animation-name: slideInRight; } @-webkit-keyframes slideInUp { from { -webkit-transform: translate3d(0, 100%, 0); transform: translate3d(0, 100%, 0); visibility: visible; } to { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } @keyframes slideInUp { from { -webkit-transform: translate3d(0, 100%, 0); transform: translate3d(0, 100%, 0); visibility: visible; } to { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } .slideInUp { -webkit-animation-name: slideInUp; animation-name: slideInUp; } @-webkit-keyframes slideOutDown { from { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } to { visibility: hidden; -webkit-transform: translate3d(0, 100%, 0); transform: translate3d(0, 100%, 0); } } @keyframes slideOutDown { from { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } to { visibility: hidden; -webkit-transform: translate3d(0, 100%, 0); transform: translate3d(0, 100%, 0); } } .slideOutDown { -webkit-animation-name: slideOutDown; animation-name: slideOutDown; } @-webkit-keyframes slideOutLeft { from { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } to { visibility: hidden; -webkit-transform: translate3d(-100%, 0, 0); transform: translate3d(-100%, 0, 0); } } @keyframes slideOutLeft { from { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } to { visibility: hidden; -webkit-transform: translate3d(-100%, 0, 0); transform: translate3d(-100%, 0, 0); } } .slideOutLeft { -webkit-animation-name: slideOutLeft; animation-name: slideOutLeft; } @-webkit-keyframes slideOutRight { from { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } to { visibility: hidden; -webkit-transform: translate3d(100%, 0, 0); transform: translate3d(100%, 0, 0); } } @keyframes slideOutRight { from { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } to { visibility: hidden; -webkit-transform: translate3d(100%, 0, 0); transform: translate3d(100%, 0, 0); } } .slideOutRight { -webkit-animation-name: slideOutRight; animation-name: slideOutRight; } @-webkit-keyframes slideOutUp { from { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } to { visibility: hidden; -webkit-transform: translate3d(0, -100%, 0); transform: translate3d(0, -100%, 0); } } @keyframes slideOutUp { from { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } to { visibility: hidden; -webkit-transform: translate3d(0, -100%, 0); transform: translate3d(0, -100%, 0); } } .slideOutUp { -webkit-animation-name: slideOutUp; animation-name: slideOutUp; } .animated { -webkit-animation-duration: 1s; animation-duration: 1s; -webkit-animation-fill-mode: both; animation-fill-mode: both; } .animated.infinite { -webkit-animation-iteration-count: infinite; animation-iteration-count: infinite; } .animated.delay-1s { -webkit-animation-delay: 1s; animation-delay: 1s; } .animated.delay-2s { -webkit-animation-delay: 2s; animation-delay: 2s; } .animated.delay-3s { -webkit-animation-delay: 3s; animation-delay: 3s; } .animated.delay-4s { -webkit-animation-delay: 4s; animation-delay: 4s; } .animated.delay-5s { -webkit-animation-delay: 5s; animation-delay: 5s; } .animated.fast { -webkit-animation-duration: 800ms; animation-duration: 800ms; } .animated.faster { -webkit-animation-duration: 500ms; animation-duration: 500ms; } .animated.slow { -webkit-animation-duration: 2s; animation-duration: 2s; } .animated.slower { -webkit-animation-duration: 3s; animation-duration: 3s; } @media (print), (prefers-reduced-motion) { .animated { -webkit-animation: unset !important; animation: unset !important; -webkit-transition: none !important; transition: none !important; } } ================================================ FILE: aws-node-fullstack/frontend/src/DemoApp.js ================================================ /* * Demo App */ import React, { Component } from 'react' import logo from './images/logo.png' import './DemoApp.css' const INITIAL_API_REQUESTS = 50 const INITIAL_STATUS = 'Ready for testing!' /* * Class – DemoApp */ class DemoApp extends Component { /* * Constructor */ constructor(props) { super(props) this.state = {} this.state.visible = {} this.state.visible.admin = false this.state.visible.success = false this.state.admin = {} this.state.admin.status = INITIAL_STATUS this.state.admin.invocations = INITIAL_API_REQUESTS this.state.admin.url = null // Refs this.refFormName = React.createRef() this.refFormEmail = React.createRef() this.refInputUrl = React.createRef() this.refInputInvocations = React.createRef() // Binders this.submitForm = this.submitForm.bind(this) this.toggleAdmin = this.toggleAdmin.bind(this) this.updateApi = this.updateApi.bind(this) this.generateInvocations = this.generateInvocations.bind(this) this.generateRandomError = this.generateRandomError.bind(this) } /** * Component Did Mount */ componentDidMount() { const self = this // Get Global State from local storage let data = localStorage.getItem('demoapp') data = data ? JSON.parse(data) : {} this.setState({ admin: { ...this.state.admin, ...{ url: data.url }}}, () => { console.log('Serverless Enterprise Demo App Initialized') console.log(this.state) // Initial Session Status let newState = { status: INITIAL_STATUS, invocations: INITIAL_API_REQUESTS, } if (!this.state.admin.url) { newState.status = 'Please insert the URL of your Form\'s API in the field below.' } this.setState({ admin: { ...self.state.admin, ...newState }}) }) } /** * Toggle Admin */ toggleAdmin() { let newState = { admin: !this.state.visible.admin } this.setState({ visible: { ...this.state.visible, ...newState }}) } /** * Set Status */ setStatus(status) { const self = this clearTimeout(this.timeout) this.setState({ admin: { ...this.state.admin, ...{ status }}}) this.timeout = setTimeout(() =>{ self.setState({ admin: { ...this.state.admin, ...{ status: `Ready for testing!` }}}) }, 6000) } /** * Submit Form */ submitForm(event) { event.preventDefault() const self = this const name = this.refFormName.current.value const email = this.refFormEmail.current.value if (!name || name === '' || !email || email === '') { alert('Form fields must be filled in') return } const callApi = () => { return fetch(this.state.admin.url, { method: 'POST', mode: 'cors', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name, email }), }) } return callApi() .then(() => { self.setState({ visible: { ...this.state.visible, ...{ success: true }}}) }) } /** * Update API */ updateApi(event) { event.preventDefault() const self = this let url = this.refInputUrl.current.value url = url.trim() || null function validURL(str) { var pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path '(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string '(\\#[-a-z\\d_]*)?$','i'); // fragment locator return !!pattern.test(str); } if (!validURL(url)) { alert(`This is not a valid URL: ${url || '(Field is blank)'}`) return } const newState = { url: url } this.setState({ admin: { ...this.state.admin, ...newState } }, () => { localStorage.setItem('demoapp', JSON.stringify({ url })) self.setStatus('API URL successfully updated!') }) } /** * Generate Invocations */ generateInvocations(event) { event.preventDefault() const self = this let invocations = this.refInputInvocations.current.value || this.state.admin.invocations invocations = parseInt(invocations) || 0 if (invocations > 999) { //eslint-disable-next-line const r = confirm(`\nThis will generate ${invocations} API requests. Are you sure you want to do this?\n`) if (!r) return } // Call API Function const callApi = () => { return fetch(this.state.admin.url, { method: 'POST', mode: 'cors', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Sample Invocation', email: 'sample@invocations.com' }), }) } // Runner Function const runner = async (cb) => { let array = [] for (let i = 0; i <= invocations; i++) { array.push(i) } for (const item of array) { await callApi() self.setStatus(`Performing API request ${item}/${invocations}...`) } if (cb) return cb() } runner(() => { self.setStatus(`Successfully completed ${invocations} requests!`) }) } /** * Generate Random Error */ generateRandomError(event) { event.preventDefault() const self = this // Call API Function const callApi = () => { return fetch(`${this.state.admin.url}?error=true`, { method: 'POST', mode: 'cors', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Sample Error Invocation', email: 'sampleerror@invocations.com' }), }) } // Runner Function const runner = async (cb) => { try { await callApi() } catch (error) { console.log(error) } self.setStatus(`Generating a random function error...`) if (cb) return cb() } runner(() => { self.setStatus(`Successfully generated a random function error!`) }) } /* * Render */ render() { return (
{ /* * DemoApp Admin Menu */ }
x
logo Serverless Framework Enterprise - Demo Utilities
Status

{this.state.admin.status}

Form Submit URL
Enter the formSubmit function's API URL returned upon deployment with the Framework.
Generate A Sample Number Of API Requests
This generates fake form submissions to give you sample invocation data.
Generate A New Function Code Error
This generates a new Function Code Error.
Generate
{ //
//
//
//
// Generate A Long Running Function //
//
// This generates an unusually long function duration. //
//
//
//
Generate
//
//
//
}
{ /* * DemoApp Content */ }
Demo Utilities
Serverless Email Sign-Up Form
Full Name
Email
Thank you for your submission!
) } } export default DemoApp ================================================ FILE: aws-node-fullstack/frontend/src/index.css ================================================ body { margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; } ================================================ FILE: aws-node-fullstack/frontend/src/index.js ================================================ import React from 'react' import ReactDOM from 'react-dom' import DemoApp from './DemoApp' import './index.css' window.demoapp = {} ReactDOM.render(, document.getElementById('root')) ================================================ FILE: aws-node-function-compiled-with-babel/.gitignore ================================================ node_modules .serverless ================================================ FILE: aws-node-function-compiled-with-babel/README.md ================================================ # Function compiled with Babel This example demonstrates how to compile your JavaScript code with Babel. In order to do so the `serverless-babel-plugin` is leveraged. ## Use Cases - Using the latest JavaScript language features without waiting for AWS to provide new Node environments. ## Setup ```bash npm install ``` ## Deploy ```bash serverless deploy ``` ```bash Serverless: Deprecation Notice: Starting with the next update, we will drop support for Lambda to implicitly create LogGroups. Please remove your log groups and set "provider.cfLogs: true", for CloudFormation to explicitly create them for you. Serverless: Packaging service… Serverless: Babel compilation: tmpBabelDirectory/createResponse.js -> tmpBabelDirectory/createResponse.js tmpBabelDirectory/handler.js -> tmpBabelDirectory/handler.js Serverless: Packaging service with compiled files... Serverless: Uploading CloudFormation file to S3… Serverless: Uploading service .zip file to S3… Serverless: Updating Stack… Serverless: Checking Stack update progress… ............ Serverless: Stack update finished… Service Information service: function-compiled-with-babel stage: dev region: us-east-1 api keys: None endpoints: None functions: function-compiled-with-babel-dev-hello: arn:aws:lambda:us-east-1:377024778620:function:function-compiled-with-babel-dev-hello ``` ## Usage You can now invoke the Lambda directly and even see the resulting log via ```bash serverless invoke --function hello --log ``` The expected result should be similar to: ```bash { "statusCode": 200, "body": { "message": "Success!" } } -------------------------------------------------------------------- START RequestId: 4388eeaffe-11e6-9e1bde31ed2e43 Version: $LATEST 2021 16:22:07.748 (+01:00) 4388eeaffe-11e6-9e1bde31ed2e43 { response: { statusCode: 200, body: { message: 'Success!' } } } END RequestId: 4388eeaffe-11e6-9e1bde31ed2e43 REPORT RequestId: 4388eeaffe-11e6-9e1bde31ed2e43 Duration: 23.13 ms Billed Duration: 100 ms Memory Size: 1024 MB Max Memory Used: 17 MB ``` ================================================ FILE: aws-node-function-compiled-with-babel/createResponse.js ================================================ module.exports = ({ body = {}, statusCode = 200 }) => { const response = { statusCode, body, }; return response; }; ================================================ FILE: aws-node-function-compiled-with-babel/event.json ================================================ { "key3": "value3", "key2": "value2", "key1": "value1" } ================================================ FILE: aws-node-function-compiled-with-babel/handler.js ================================================ const createResponse = require('./createResponse'); module.exports.hello = (event, context, callback) => { const response = createResponse({ body: { message: 'Success!' } }); console.log({ response }); callback(null, response); }; ================================================ FILE: aws-node-function-compiled-with-babel/package.json ================================================ { "name": "aws-function-compiled-with-babel", "version": "1.0.0", "description": "Demonstrating how to compile all your code with Babel", "repository": "", "author": "", "license": "MIT", "devDependencies": { "babel-cli": "~6.18.0", "babel-preset-latest": "^6.16.0", "serverless-babel-plugin": "^0.1.3" }, "dependencies": {} } ================================================ FILE: aws-node-function-compiled-with-babel/serverless.yml ================================================ service: function-compiled-with-babel frameworkVersion: ">=1.1.0 <2.0.0" custom: babelPresets: - latest plugins: - serverless-babel-plugin provider: name: aws runtime: nodejs12.x functions: hello: handler: handler.hello ================================================ FILE: aws-node-github-check/.babelrc ================================================ { "plugins": ["source-map-support", "transform-runtime"], "presets": [ ["env", { "node": "8.10" }], "stage-3" ] } ================================================ FILE: aws-node-github-check/.gitignore ================================================ node_modules .serverless .webpack ================================================ FILE: aws-node-github-check/README.md ================================================ # Serverless Github Check Serverless Github Check. This repo is part of the [Serverless November Challenge](https://serverless.com/blog/no-server-november-challenge/) ### Use Case The idea is to validate that all Pull Requests are related to a specific trello card. Check rules: * To pass the check, the Pull Request body should start with: "Related trello card: https://trello.com/" ### Setup * Set your github token in [AWS Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-paramstore.html). The project is already configured to take the token from there if you call it `githubToken`. More info about adding parameters to AWS Parameter Store and why it's good to use it [here](https://serverless.com/blog/serverless-secrets-api-keys/) * Deploy the service ``` serverless deploy ``` After the deploy has finished, you should see something like: ``` Service Information service: serverless-github-check stage: dev region: eu-west-1 stack: serverless-github-check-dev api keys: None endpoints: POST - https://abcdefghij.execute-api.eu-west-1.amazonaws.com/dev/webhook functions: githubCheck: serverless-github-check-dev-githubCheck ``` * Configure the webhook in the github repository settings. This [link](https://developer.github.com/webhooks/creating/#setting-up-a-webhook) can help you. * In the Payload URL, set the API POST endpoint of your function. * In the types of events to trigger the webhook, select "Let me select individual events". Once there, select at least `Pull Requests`. * Apply the github check for Trello cards or change the rule to apply a new one! :) ================================================ FILE: aws-node-github-check/handler.js ================================================ import { client } from 'octonode'; // eslint-disable-line import/extensions import { success, failure, githubSuccessPayload, githubFailurePayload } from './libs/response-lib'; import { isAValidPullRequest, eventIsAPullRequest, updatePullRequestStatus } from './libs/github-service'; /* eslint-disable import/prefer-default-export */ export async function githubCheck(event, context, callback) { const githubClient = client(process.env.GITHUB_TOKEN); const body = JSON.parse(event.body); if (!eventIsAPullRequest(body)) return callback(null, success('Event is not a Pull Request')); const payload = isAValidPullRequest(body) ? githubSuccessPayload() : githubFailurePayload(); try { await updatePullRequestStatus(githubClient, payload, body.repository, body.pull_request); return callback(null, success(`Process finished with state: ${payload.state}`)); } catch (e) { return callback(null, failure('Process finished with error')); } } ================================================ FILE: aws-node-github-check/libs/github-service.js ================================================ export function isAValidPullRequest(body) { return body.pull_request.body.startsWith('Related trello card: https://trello.com'); } export function eventIsAPullRequest(body) { return body && ('pull_request' in body); } export function updatePullRequestStatus(githubClient, payload, repository, pullRequest) { return new Promise((resolve, reject) => { githubClient.post(`/repos/${repository.full_name}/statuses/${pullRequest.head.sha}`, payload, {}, (err) => { if (err) { reject(err); } else { resolve(); } }); }); } ================================================ FILE: aws-node-github-check/libs/response-lib.js ================================================ function buildResponse(statusCode, body) { return { statusCode, body: JSON.stringify(body), }; } function buildGithubPayload(state, description) { return { state, description, context: 'serverless-webhook/pr-body', }; } export function success(body) { return buildResponse(204, body); } export function failure(body) { return buildResponse(500, body); } export function githubSuccessPayload() { return buildGithubPayload('success', 'PR body according to format'); } export function githubFailurePayload() { return buildGithubPayload('failure', 'PR body should start with the related trello card'); } ================================================ FILE: aws-node-github-check/package.json ================================================ { "name": "serverless-github-check", "version": "0.0.1", "main": "handler.js", "scripts": { "deploy": "serverless deploy", "logs": "serverless logs -f githubCheck -t" }, "author": "Fran Ortiz ", "license": "MIT", "devDependencies": { "babel-core": "^6.26.3", "babel-loader": "^7.1.4", "babel-plugin-source-map-support": "^1.0.0", "babel-plugin-transform-runtime": "^6.23.0", "babel-preset-env": "^1.7.0", "babel-preset-stage-3": "^6.24.1", "serverless-offline": "^3.25.6", "serverless-webpack": "^5.1.0", "webpack": "^4.16.2", "webpack-node-externals": "^1.6.0" }, "dependencies": { "octonode": "^0.9.5", "babel-runtime": "^6.26.0" } } ================================================ FILE: aws-node-github-check/serverless.yaml ================================================ service: serverless-github-check plugins: - serverless-webpack - serverless-offline custom: webpack: webpackConfig: ./webpack.config.js includeModules: true provider: name: aws runtime: nodejs12.x profile: personal region: eu-west-1 environment: GITHUB_TOKEN: ${ssm:githubToken} functions: githubCheck: handler: handler.githubCheck events: - http: path: webhook method: post cors: true ================================================ FILE: aws-node-github-check/webpack.config.js ================================================ const slsw = require('serverless-webpack'); const nodeExternals = require('webpack-node-externals'); module.exports = { entry: slsw.lib.entries, target: 'node', devtool: 'source-map', externals: [nodeExternals()], mode: slsw.lib.webpack.isLocal ? 'development' : 'production', optimization: { minimize: false, }, performance: { hints: false, }, module: { rules: [ { test: /\.js$/, loader: 'babel-loader', include: __dirname, exclude: /node_modules/, }, ], }, }; ================================================ FILE: aws-node-github-webhook-listener/.gitignore ================================================ node_modules .serverless ================================================ FILE: aws-node-github-webhook-listener/README.md ================================================ # Serverless Github webhook listener This service will listen to github webhooks fired by a given repository. ## Use Cases * Custom github notifications * Automatically tagging github issues * Pinging slack on new Pull requests * Welcoming new stargazers * etc. ## How it works ``` ┌───────────────┐ ┌───────────┐ │ │ │ │ │ Github repo │ │ Github │ │ activity │────Trigger───▶│ Webhook │ │ │ │ │ └───────────────┘ └───────────┘ │ ┌────POST────────┘ │ ┌──────────▼─────────┐ │ ┌────────────────┐ │ │ │ API Gateway │ │ │ │ Endpoint │ │ │ └────────────────┘ │ └─────────┬──────────┘ │ │ ┌──────────▼──────────┐ │ ┌────────────────┐ │ │ │ │ │ │ │ Lambda │ │ │ │ Function │ │ │ │ │ │ │ └────────────────┘ │ └─────────────────────┘ │ │ ▼ ┌────────────────────┐ │ │ │ Do stuff │ │ │ └────────────────────┘ ``` ## Setup 1. Set your webhook secret token in `serverless.yml` by replacing `REPLACE-WITH-YOUR-SECRET-HERE` in the environment variables `GITHUB_WEBHOOK_SECRET`. ```yml provider: name: aws runtime: nodejs12.x environment: GITHUB_WEBHOOK_SECRET: REPLACE-WITH-YOUR-SECRET-HERE ``` 2. Deploy the service ```yaml serverless deploy ``` After the deploy has finished you should see something like: ```bash Service Information service: github-webhook-listener stage: dev region: us-east-1 api keys: None endpoints: POST - https://abcdefg.execute-api.us-east-1.amazonaws.com/dev/webhook functions: github-webhook-.....github-webhook-listener-dev-githubWebhookListener ``` 3. Configure your webhook in your github repository settings. [Setting up a Webhook](https://developer.github.com/webhooks/creating/#setting-up-a-webhook) **(1.)** Plugin your API POST endpoint. (`https://abcdefg.execute-api.us-east-1.amazonaws.com/dev/webhook` in this example). Run `sls info` to grab your endpoint if you don't have it handy. **(2.)** Plugin your secret from `GITHUB_WEBHOOK_SECRET` environment variable **(3.)** Choose the types of events you want the github webhook to fire on ![webhook-steps](https://cloud.githubusercontent.com/assets/532272/21461773/db7cecd2-c911e6-936bbf4661fe14.jpg) 4. Manually trigger/test the webhook from settings or do something in your github repo to trigger a webhook. You can tail the logs of the lambda function with the below command to see it running. ```bash serverless logs -f githubWebhookListener -t ``` You should see the event from github in the lambda functions logs. 5. Use your imagination and do whatever you want with your new github webhook listener! 🎉 Let us know if you come up with a cool use case for this service =) ================================================ FILE: aws-node-github-webhook-listener/handler.js ================================================ const crypto = require('crypto'); function signRequestBody(key, body) { return `sha1=${crypto.createHmac('sha1', key).update(body, 'utf-8').digest('hex')}`; } module.exports.githubWebhookListener = (event, context, callback) => { var errMsg; // eslint-disable-line const token = process.env.GITHUB_WEBHOOK_SECRET; const headers = event.headers; const sig = headers['X-Hub-Signature']; const githubEvent = headers['X-GitHub-Event']; const id = headers['X-GitHub-Delivery']; const calculatedSig = signRequestBody(token, event.body); if (typeof token !== 'string') { errMsg = 'Must provide a \'GITHUB_WEBHOOK_SECRET\' env variable'; return callback(null, { statusCode: 401, headers: { 'Content-Type': 'text/plain' }, body: errMsg, }); } if (!sig) { errMsg = 'No X-Hub-Signature found on request'; return callback(null, { statusCode: 401, headers: { 'Content-Type': 'text/plain' }, body: errMsg, }); } if (!githubEvent) { errMsg = 'No X-Github-Event found on request'; return callback(null, { statusCode: 422, headers: { 'Content-Type': 'text/plain' }, body: errMsg, }); } if (!id) { errMsg = 'No X-Github-Delivery found on request'; return callback(null, { statusCode: 401, headers: { 'Content-Type': 'text/plain' }, body: errMsg, }); } if (sig !== calculatedSig) { errMsg = 'X-Hub-Signature incorrect. Github webhook token doesn\'t match'; return callback(null, { statusCode: 401, headers: { 'Content-Type': 'text/plain' }, body: errMsg, }); } /* eslint-disable */ console.log('---------------------------------'); console.log(`Github-Event: "${githubEvent}" with action: "${event.body.action}"`); console.log('---------------------------------'); console.log('Payload', event.body); /* eslint-enable */ // Do custom stuff here with github event data // For more on events see https://developer.github.com/v3/activity/events/types/ const response = { statusCode: 200, body: JSON.stringify({ input: event, }), }; return callback(null, response); }; ================================================ FILE: aws-node-github-webhook-listener/package.json ================================================ { "name": "aws-github-webhook-listener", "version": "1.0.0", "description": "Extend your github repositories with this github webhook listener", "main": "handler.js", "scripts": { "deploy": "serverless deploy", "logs": "serverless logs -f githubWebhookListener -t" }, "author": "David Wells", "license": "MIT" } ================================================ FILE: aws-node-github-webhook-listener/serverless.yml ================================================ service: github-webhook-listener provider: name: aws runtime: nodejs12.x environment: GITHUB_WEBHOOK_SECRET: REPLACE-WITH-YOUR-SECRET-HERE functions: githubWebhookListener: handler: handler.githubWebhookListener events: - http: path: webhook method: post cors: true ================================================ FILE: aws-node-graphql-and-rds/README.md ================================================ # A Simple Serverless GraphQL API for MySQL, Postgres and Aurora This is an example project using the [Serverless framework](https://serverless.com/framework/), Node.js and [Amazon RDS](https://aws.amazon.com/rds/). This project uses 3 RDS databases to illustrate the differences between using each of them: * MySQL * PostgreSQL * MySQL-compatible Amazon Aurora ## How to Deploy This Project ### Pre-Requisites To deploy this GraphQL API, you’ll need the following: * An AWS account. * [AWS CLI](https://aws.amazon.com/cli/) installed locally. * API credentials for your AWS account configured in your AWS CLI locally by running `aws configure`. * Serverless framework installed locally via `npm -g install serverless`. ### Steps to Deploy Once all pre-requisite items are ready, follow these steps to deploy this example GraphQL API: 1. Run `npm install` to install all the necessary dependencies. 2. Run `npm run deploy` to deploy the stack. ### Steps to Remove All Resources After you’ve finished working with this example, remove all resources to make sure you’re not getting billed for unused RDS databases. Run `npm run remove` to remove all resources. ================================================ FILE: aws-node-graphql-and-rds/handler.js ================================================ const { GraphQLServerLambda } = require("graphql-yoga"); var fs = require("fs") const typeDefs = fs.readFileSync("./schema.gql").toString('utf-8'); const resolvers = { Query: { mysql_getUser: require("./resolver/Query/mysql_getUser").func, postgresql_getUser: require("./resolver/Query/postgresql_getUser").func, aurora_getUser: require("./resolver/Query/aurora_getUser").func }, Mutation: { mysql_createUser: require("./resolver/Mutation/mysql_createUser").func, postgresql_createUser: require("./resolver/Mutation/postgresql_createUser").func, aurora_createUser: require("./resolver/Mutation/aurora_createUser").func } }; const lambda = new GraphQLServerLambda({ typeDefs, resolvers }); exports.server = lambda.graphqlHandler; exports.playground = lambda.playgroundHandler; ================================================ FILE: aws-node-graphql-and-rds/package.json ================================================ { "name": "graphql-api-and-serverless", "version": "1.0.0", "description": "", "main": "handler.js", "scripts": { "func": "serverless deploy function --function graphql", "deploy": "serverless deploy", "remove": "serverless remove" }, "dependencies": { "graphql-yoga": "^1.17.4", "pg": "^7.11.0", "serverless-mysql": "^1.4.0", "uuid": "^3.3.2" }, "devDependencies": { "serverless-pseudo-parameters": "^2.4.0" } } ================================================ FILE: aws-node-graphql-and-rds/resolver/Common/aurora.js ================================================ exports.init = async (client) => { await client.query(` CREATE TABLE IF NOT EXISTS users ( id MEDIUMINT UNSIGNED not null AUTO_INCREMENT, created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, uuid char(36) not null, name varchar(100) not null, PRIMARY KEY (id) ); `) await client.query(` CREATE TABLE IF NOT EXISTS posts ( id MEDIUMINT UNSIGNED not null AUTO_INCREMENT, created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, uuid char(36) not null, text varchar(100) not null, user_id MEDIUMINT UNSIGNED not null, PRIMARY KEY (id) ); `) } exports.getUser = async (client, uuid) => { var user = {}; var userFromDb = await client.query(` select id, uuid, name from users where uuid = ? `, [uuid]) if (userFromDb.length == 0) { return null; } var postsFromDb = await client.query(` select uuid, text from posts where user_id = ? `, [userFromDb[0].id]) user.UUID = userFromDb[0].uuid; user.Name = userFromDb[0].name; if (postsFromDb.length > 0) { user.Posts = postsFromDb.map(function (x) { return { UUID: x.uuid, Text: x.text } }); } return user; } ================================================ FILE: aws-node-graphql-and-rds/resolver/Common/mysql.js ================================================ exports.init = async (client) => { await client.query(` CREATE TABLE IF NOT EXISTS users ( id MEDIUMINT UNSIGNED not null AUTO_INCREMENT, created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, uuid char(36) not null, name varchar(100) not null, PRIMARY KEY (id) ); `) await client.query(` CREATE TABLE IF NOT EXISTS posts ( id MEDIUMINT UNSIGNED not null AUTO_INCREMENT, created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, uuid char(36) not null, text varchar(100) not null, user_id MEDIUMINT UNSIGNED not null, PRIMARY KEY (id) ); `) } exports.getUser = async (client, uuid) => { var user = {}; var userFromDb = await client.query(` select id, uuid, name from users where uuid = ? `, [uuid]) if (userFromDb.length == 0) { return null; } var postsFromDb = await client.query(` select uuid, text from posts where user_id = ? `, [userFromDb[0].id]) user.UUID = userFromDb[0].uuid; user.Name = userFromDb[0].name; if (postsFromDb.length > 0) { user.Posts = postsFromDb.map(function (x) { return { UUID: x.uuid, Text: x.text } }); } return user; } ================================================ FILE: aws-node-graphql-and-rds/resolver/Common/postgresql.js ================================================ exports.init = async (client) => { var res = await client.query(` CREATE TABLE IF NOT EXISTS users ( id serial not null PRIMARY KEY, created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, uuid char(36) not null, name varchar(100) not null ); `) await client.query(` CREATE TABLE IF NOT EXISTS posts ( id serial not null PRIMARY KEY, created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, uuid char(36) not null, text varchar(100) not null, user_id INT not null ); `) } exports.getUser = async (client, uuid) => { var user = {}; var userFromDb = await client.query(` select id, uuid, name from users where uuid = $1 `, [uuid]) if (userFromDb.rows.length == 0) { return null; } var postsFromDb = await client.query(` select uuid, text from posts where user_id = $1 `, [userFromDb.rows[0].id]) user.UUID = userFromDb.rows[0].uuid; user.Name = userFromDb.rows[0].name; if (postsFromDb.rows.length > 0) { user.Posts = postsFromDb.rows.map(function (x) { return { UUID: x.uuid, Text: x.text } }); } return user; } ================================================ FILE: aws-node-graphql-and-rds/resolver/Mutation/aurora_createUser.js ================================================ const uuidv4 = require('uuid/v4'); var common = require('../Common/aurora') const Client = require('serverless-mysql') exports.func = async (_, obj) => { var client = Client({ config: { host: process.env.MYSQL_HOST, database: process.env.DB_NAME, user: process.env.USERNAME, password: process.env.PASSWORD } }) await common.init(client) var userUUID = uuidv4(); let user = await client.query('INSERT INTO users (uuid, name) VALUES(?,?)', [userUUID, obj.input.Name]); for (let index = 0; index < obj.input.Posts.length; index++) { const element = obj.input.Posts[index]; await client.query('INSERT INTO posts (uuid, text, user_id) VALUES(?, ?, ?)', [uuidv4(), element.Text, user.insertId]); } var resp = await common.getUser(client, userUUID); client.quit() return resp; } ================================================ FILE: aws-node-graphql-and-rds/resolver/Mutation/mysql_createUser.js ================================================ const uuidv4 = require('uuid/v4'); var common = require('../Common/mysql') const Client = require('serverless-mysql') exports.func = async (_, obj) => { var client = Client({ config: { host: process.env.MYSQL_HOST, database: process.env.DB_NAME, user: process.env.USERNAME, password: process.env.PASSWORD } }) await common.init(client) var userUUID = uuidv4(); let user = await client.query('INSERT INTO users (uuid, name) VALUES(?,?)', [userUUID, obj.input.Name]); for (let index = 0; index < obj.input.Posts.length; index++) { const element = obj.input.Posts[index]; await client.query('INSERT INTO posts (uuid, text, user_id) VALUES(?, ?, ?)', [uuidv4(), element.Text, user.insertId]); } var resp = await common.getUser(client, userUUID); client.quit() return resp; } ================================================ FILE: aws-node-graphql-and-rds/resolver/Mutation/postgresql_createUser.js ================================================ const uuidv4 = require('uuid/v4'); const { Client } = require('pg') var common = require('../Common/postgresql') exports.func = async (_, obj) => { var client = new Client({ host: process.env.POSTGRESQL_HOST, port: process.env.POSTGRESQL_PORT, database: process.env.DB_NAME, user: process.env.USERNAME, password: process.env.PASSWORD }) client.connect() await common.init(client) var userUUID = uuidv4(); let userInserted = await client.query('INSERT INTO users (uuid, name) VALUES($1, $2) RETURNING id', [userUUID, obj.input.Name]); var userId = userInserted.rows[0].id for (let index = 0; index < obj.input.Posts.length; index++) { const element = obj.input.Posts[index]; await client.query('INSERT INTO posts (uuid, text, user_id) VALUES($1, $2, $3)', [uuidv4(), element.Text, userId]); } var resp = await common.getUser(client, userUUID); client.end() return resp; } ================================================ FILE: aws-node-graphql-and-rds/resolver/Query/aurora_getUser.js ================================================ var common = require('../Common/aurora') const Client = require('serverless-mysql') exports.func = async (_, { uuid }) => { var client = Client({ config: { host: process.env.MYSQL_HOST, database: process.env.DB_NAME, user: process.env.USERNAME, password: process.env.PASSWORD } }) await common.init(client) var resp = await common.getUser(client, uuid); client.quit() return resp; } ================================================ FILE: aws-node-graphql-and-rds/resolver/Query/mysql_getUser.js ================================================ var common = require('../Common/mysql') const Client = require('serverless-mysql') exports.func = async (_, { uuid }) => { var client = Client({ config: { host: process.env.MYSQL_HOST, database: process.env.DB_NAME, user: process.env.USERNAME, password: process.env.PASSWORD } }) await common.init(client) var resp = await common.getUser(client, uuid); client.quit() return resp; } ================================================ FILE: aws-node-graphql-and-rds/resolver/Query/postgresql_getUser.js ================================================ const { Client } = require('pg') var common = require('../Common/postgresql') exports.func = async (_, { uuid }) => { var client = new Client({ host: process.env.POSTGRESQL_HOST, port: process.env.POSTGRESQL_PORT, database: process.env.DB_NAME, user: process.env.USERNAME, password: process.env.PASSWORD }) client.connect() await common.init(client) var resp = await common.getUser(client, uuid); client.end() return resp; } ================================================ FILE: aws-node-graphql-and-rds/resource/AuroraRDSCluster.yml ================================================ Type: AWS::RDS::DBCluster Properties: MasterUsername: ${self:custom.USERNAME} MasterUserPassword: ${self:custom.PASSWORD} DBSubnetGroupName: Ref: ServerlessSubnetGroup Engine: aurora EngineVersion: "5.6" DatabaseName: ${self:custom.DB_NAME} BackupRetentionPeriod: 3 DBClusterParameterGroupName: Ref: AuroraRDSClusterParameter VpcSecurityGroupIds: - !Ref 'ServerlessSecurityGroup' ================================================ FILE: aws-node-graphql-and-rds/resource/AuroraRDSClusterParameter.yml ================================================ Type: AWS::RDS::DBClusterParameterGroup Properties: Description: Parameter group for the Serverless Aurora RDS DB. Family: aurora5.6 Parameters: character_set_database: "utf32" ================================================ FILE: aws-node-graphql-and-rds/resource/AuroraRDSInstance.yml ================================================ DependsOn: ServerlessVPCGA Type: AWS::RDS::DBInstance Properties: DBInstanceClass: db.t2.small DBSubnetGroupName: Ref: ServerlessSubnetGroup Engine: aurora EngineVersion: "5.6" PubliclyAccessible: true DBParameterGroupName: Ref: AuroraRDSInstanceParameter DBClusterIdentifier: Ref: AuroraRDSCluster ================================================ FILE: aws-node-graphql-and-rds/resource/AuroraRDSInstanceParameter.yml ================================================ Type: AWS::RDS::DBParameterGroup Properties: Description: Parameter group for the Serverless Aurora RDS DB. Family: aurora5.6 Parameters: sql_mode: IGNORE_SPACE max_connections: 100 wait_timeout: 900 interactive_timeout: 900 ================================================ FILE: aws-node-graphql-and-rds/resource/LambdaRole.yml ================================================ Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Effect: Allow Action: sts:AssumeRole Principal: Service: lambda.amazonaws.com Version: '2012-10-17' Policies: - PolicyName: CanLog PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - logs:CreateLogStream - logs:CreateLogGroup - logs:PutLogEvents Resource: arn:aws:logs:*:*:* ================================================ FILE: aws-node-graphql-and-rds/resource/MySqlRDSInstance.yml ================================================ DependsOn: ServerlessVPCGA Type: AWS::RDS::DBInstance Properties: MasterUsername: ${self:custom.USERNAME} MasterUserPassword: ${self:custom.PASSWORD} AllocatedStorage: 20 DBName: ${self:custom.DB_NAME} DBInstanceClass: db.t2.micro VPCSecurityGroups: - !GetAtt ServerlessSecurityGroup.GroupId DBSubnetGroupName: Ref: ServerlessSubnetGroup Engine: mysql EngineVersion: "5.6.41" PubliclyAccessible: true ================================================ FILE: aws-node-graphql-and-rds/resource/PostgreSqlRDSInstance.yml ================================================ DependsOn: ServerlessVPCGA Type: AWS::RDS::DBInstance Properties: MasterUsername: ${self:custom.USERNAME} MasterUserPassword: ${self:custom.PASSWORD} AllocatedStorage: 20 DBName: ${self:custom.DB_NAME} DBInstanceClass: db.t4g.small VPCSecurityGroups: - !GetAtt ServerlessSecurityGroup.GroupId DBSubnetGroupName: Ref: ServerlessSubnetGroup Engine: postgres PubliclyAccessible: true ================================================ FILE: aws-node-graphql-and-rds/resource/RoutePublic.yml ================================================ Type: AWS::EC2::Route Properties: DestinationCidrBlock: 0.0.0.0/0 GatewayId: Ref: ServerlessInternetGateway RouteTableId: Ref: RouteTablePublic ================================================ FILE: aws-node-graphql-and-rds/resource/RouteTableAssociationSubnetA.yml ================================================ Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: Ref: RouteTablePublic SubnetId: Ref: ServerlessSubnetA ================================================ FILE: aws-node-graphql-and-rds/resource/RouteTableAssociationSubnetB.yml ================================================ Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: Ref: RouteTablePublic SubnetId: Ref: ServerlessSubnetB ================================================ FILE: aws-node-graphql-and-rds/resource/RouteTableAssociationSubnetC.yml ================================================ Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: Ref: RouteTablePublic SubnetId: Ref: ServerlessSubnetC ================================================ FILE: aws-node-graphql-and-rds/resource/RouteTablePublic.yml ================================================ Type: AWS::EC2::RouteTable Properties: VpcId: Ref: ServerlessVPC Tags: - Key: Name Value: public-route ================================================ FILE: aws-node-graphql-and-rds/resource/ServerlessInternetGateway.yml ================================================ Type: AWS::EC2::InternetGateway Properties: Tags: - Key: "Name" Value: "ServerlessInternetGateway" ================================================ FILE: aws-node-graphql-and-rds/resource/ServerlessSecurityGroup.yml ================================================ DependsOn: ServerlessVPC Type: AWS::EC2::SecurityGroup Properties: GroupDescription: SecurityGroup for Serverless Functions VpcId: Ref: ServerlessVPC SecurityGroupIngress: - IpProtocol: tcp FromPort: '0' ToPort: '65535' CidrIp: "0.0.0.0/0" Tags: - Key: "Name" Value: "ServerlessSecurityGroup" ================================================ FILE: aws-node-graphql-and-rds/resource/ServerlessSubnetA.yml ================================================ DependsOn: ServerlessVPC Type: AWS::EC2::Subnet Properties: VpcId: Ref: ServerlessVPC AvailabilityZone: ${self:provider.region}a CidrBlock: ${self:custom.AURORA.VPC_CIDR}.0.0.0/24 Tags: - Key: "Name" Value: "ServerlessSubnetA" ================================================ FILE: aws-node-graphql-and-rds/resource/ServerlessSubnetB.yml ================================================ DependsOn: ServerlessVPC Type: AWS::EC2::Subnet Properties: VpcId: Ref: ServerlessVPC AvailabilityZone: ${self:provider.region}b CidrBlock: ${self:custom.AURORA.VPC_CIDR}.0.1.0/24 Tags: - Key: "Name" Value: "ServerlessSubnetB" ================================================ FILE: aws-node-graphql-and-rds/resource/ServerlessSubnetC.yml ================================================ DependsOn: ServerlessVPC Type: AWS::EC2::Subnet Properties: VpcId: Ref: ServerlessVPC AvailabilityZone: ${self:provider.region}c CidrBlock: ${self:custom.AURORA.VPC_CIDR}.0.2.0/24 Tags: - Key: "Name" Value: "ServerlessSubnetC" ================================================ FILE: aws-node-graphql-and-rds/resource/ServerlessSubnetGroup.yml ================================================ Type: AWS::RDS::DBSubnetGroup Properties: DBSubnetGroupDescription: "RDS Subnet Group" SubnetIds: - Ref: ServerlessSubnetA - Ref: ServerlessSubnetB - Ref: ServerlessSubnetC Tags: - Key: "Name" Value: "ServerlessSubnetGroup" ================================================ FILE: aws-node-graphql-and-rds/resource/ServerlessVPC.yml ================================================ Type: AWS::EC2::VPC Properties: CidrBlock: ${self:custom.AURORA.VPC_CIDR}.0.0.0/16 EnableDnsSupport: true EnableDnsHostnames: true InstanceTenancy: default Tags: - Key: "Name" Value: "ServerlessVPC" ================================================ FILE: aws-node-graphql-and-rds/resource/ServerlessVPCGA.yml ================================================ Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: Ref: ServerlessVPC InternetGatewayId: Ref: ServerlessInternetGateway ================================================ FILE: aws-node-graphql-and-rds/schema.gql ================================================ type User { UUID: String Name: String Posts: [Post] } type Post { UUID: String Text: String } input UserInput { Name: String Posts: [PostInput] } input PostInput{ Text: String } type Mutation { mysql_createUser(input: UserInput!): User postgresql_createUser(input: UserInput!): User aurora_createUser(input: UserInput!): User } type Query { mysql_getUser(uuid: String!): User postgresql_getUser(uuid: String!): User aurora_getUser(uuid: String!): User } schema { query: Query mutation: Mutation } ================================================ FILE: aws-node-graphql-and-rds/secrets.json ================================================ { "ApiName": "graphql-api-and-serverless", "DefaultRegion": "us-east-1" } ================================================ FILE: aws-node-graphql-and-rds/serverless.yml ================================================ service: ${file(./secrets.json):ApiName} provider: name: aws region: us-east-1 stage: dev memorySize: 256 runtime: nodejs12.x iam: role: LambdaRole environment: #aurora AURORA_HOST: ${self:custom.AURORA.HOST} AURORA_PORT: ${self:custom.AURORA.PORT} #mysql MYSQL_HOST: ${self:custom.MYSQL.HOST} MYSQL_PORT: ${self:custom.MYSQL.PORT} #postgresql POSTGRESQL_HOST: ${self:custom.POSTGRESQL.HOST} POSTGRESQL_PORT: ${self:custom.POSTGRESQL.PORT} #common DB_NAME: ${self:custom.DB_NAME} USERNAME: ${self:custom.USERNAME} PASSWORD: ${self:custom.PASSWORD} custom: DB_NAME: graphql USERNAME: master PASSWORD: password AURORA: HOST: Fn::GetAtt: [AuroraRDSCluster, Endpoint.Address] PORT: Fn::GetAtt: [AuroraRDSCluster, Endpoint.Port] VPC_CIDR: 10 MYSQL: HOST: Fn::GetAtt: [MySqlRDSInstance, Endpoint.Address] PORT: Fn::GetAtt: [MySqlRDSInstance, Endpoint.Port] POSTGRESQL: HOST: Fn::GetAtt: [PostgreSqlRDSInstance, Endpoint.Address] PORT: Fn::GetAtt: [PostgreSqlRDSInstance, Endpoint.Port] plugins: - serverless-pseudo-parameters resources: Resources: LambdaRole: ${file(./resource/LambdaRole.yml)} ServerlessInternetGateway: ${file(./resource/ServerlessInternetGateway.yml)} ServerlessVPC: ${file(./resource/ServerlessVPC.yml)} ServerlessVPCGA: ${file(./resource/ServerlessVPCGA.yml)} ServerlessSubnetA: ${file(./resource/ServerlessSubnetA.yml)} ServerlessSubnetB: ${file(./resource/ServerlessSubnetB.yml)} ServerlessSubnetC: ${file(./resource/ServerlessSubnetC.yml)} ServerlessSubnetGroup: ${file(./resource/ServerlessSubnetGroup.yml)} ServerlessSecurityGroup: ${file(./resource/ServerlessSecurityGroup.yml)} RouteTablePublic: ${file(./resource/RouteTablePublic.yml)} RoutePublic: ${file(./resource/RoutePublic.yml)} RouteTableAssociationSubnetA: ${file(./resource/RouteTableAssociationSubnetA.yml)} RouteTableAssociationSubnetB: ${file(./resource/RouteTableAssociationSubnetB.yml)} RouteTableAssociationSubnetC: ${file(./resource/RouteTableAssociationSubnetC.yml)} AuroraRDSClusterParameter: ${file(./resource/AuroraRDSClusterParameter.yml)} AuroraRDSInstanceParameter: ${file(./resource/AuroraRDSInstanceParameter.yml)} AuroraRDSCluster: ${file(./resource/AuroraRDSCluster.yml)} AuroraRDSInstance: ${file(./resource/AuroraRDSInstance.yml)} MySqlRDSInstance: ${file(./resource/MySqlRDSInstance.yml)} PostgreSqlRDSInstance: ${file(./resource/PostgreSqlRDSInstance.yml)} functions: graphql: handler: handler.server events: - http: path: / method: post cors: true playground: handler: handler.playground events: - http: path: / method: get cors: true ================================================ FILE: aws-node-graphql-api-with-dynamodb/.gitignore ================================================ # package directories node_modules jspm_packages # Serverless directories .serverless ================================================ FILE: aws-node-graphql-api-with-dynamodb/README.md ================================================ # GraphQL query endpoint in NodeJS on AWS with DynamoDB GraphQL is cool, and the `graphql` module makes it easy to rapidly create a GraphQL service that validates queries. We use GraphQL at Serverless to query our backend services, and we love how well it fits into the serverless paradigm. Let's see how easy it is to use GraphQL with the Serverless Framework. In this example, I'll be targeting AWS. Let's build a simplistic version of an API that might be used by the front-end to retrieve a dynamic message to display in the UI, in this case greeting the user by name. Start by initializing a project and installing the [graphql](https://www.npmjs.com/package/graphql) module. ```sh $ npm init $ npm install --save graphql ``` Now we can use it in `handler.js`, where we declare a schema and then use it to serve query requests. ```js /* handler.js */ const { graphql, GraphQLSchema, GraphQLObjectType, GraphQLString, GraphQLNonNull } = require('graphql') // This method just inserts the user's first name into the greeting message. const getGreeting = firstName => `Hello, ${firstName}.` // Here we declare the schema and resolvers for the query const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'RootQueryType', // an arbitrary name fields: { // the query has a field called 'greeting' greeting: { // we need to know the user's name to greet them args: { firstName: { name: 'firstName', type: new GraphQLNonNull(GraphQLString) } }, // the greeting message is a string type: GraphQLString, // resolve to a greeting message resolve: (parent, args) => getGreeting(args.firstName) } } }), }) // We want to make a GET request with ?query= // The event properties are specific to AWS. Other providers will differ. module.exports.query = (event, context, callback) => graphql(schema, event.queryStringParameters.query) .then( result => callback(null, {statusCode: 200, body: JSON.stringify(result)}), err => callback(err) ) ``` Pretty simple! To deploy it, define a service in `serverless.yml`, and set the handler to service HTTP requests. ```yml # serverless.yml service: graphql-api functions: query: handler: handler.query events: - http: path: query method: get ``` Now we can bring it to life: ```sh $ serverless deploy # Serverless: Packaging service... # Serverless: Excluding development dependencies... # Serverless: Uploading CloudFormation file to S3... # Serverless: Uploading artifacts... # Serverless: Uploading service .zip file to S3 (357.34 KB)... # Serverless: Validating template... # Serverless: Updating Stack... # Serverless: Checking Stack update progress... # .............. # Serverless: Stack update finished... # Service Information # service: graphql-api # stage: dev # region: us-east-1 # stack: graphql-api-dev # api keys: # None # endpoints: # GET - https://9qdmq5nvql.execute-api.us-east-1.amazonaws.com/dev/query # functions: # query: graphql-api-dev-query $ curl -G 'https://9qdmq5nvql.execute-api.us-east-1.amazonaws.com/dev/query' --data-urlencode 'query={greeting(firstName: "Jeremy")}' # {"data":{"greeting":"Hello, Jeremy."}} ``` In the real world, virtually any service that does something valuable has a data store behind it. For example, suppose users have nicknames that should appear in the greeting message. We need a database to store the nicknames, and we can expand our GraphQL API to update them. Let's start by adding a database to the resource definitions in `serverless.yml`. We need a table keyed on the user's first name, which we define using CloudFormation, as well as some provider configuration to allow our function to access it. ```yml # add to serverless.yml provider: name: aws runtime: nodejs6.10 environment: DYNAMODB_TABLE: ${self:service}-${self:provider.stage} iamRoleStatements: - Effect: Allow Action: - dynamodb:GetItem - dynamodb:UpdateItem Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}" resources: Resources: NicknamesTable: Type: 'AWS::DynamoDB::Table' Properties: AttributeDefinitions: - AttributeName: firstName AttributeType: S KeySchema: - AttributeName: firstName KeyType: HASH ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 TableName: ${self:provider.environment.DYNAMODB_TABLE} ``` We need to run `serverless deploy` again to update the changes made in `serverless.yml`: ``` $ serverless deploy ``` To use it we need the [aws-sdk](https://www.npmjs.com/package/aws-sdk), In this example, I use the SDK's vanilla DocumentClient to access DynamoDB records. ```sh $ npm install --save aws-sdk ``` Include these in our handler, and then we can get to work. ```js // add to handler.js const AWS = require('aws-sdk'); const dynamoDb = new AWS.DynamoDB.DocumentClient(); ``` Before, we defined a method that just returned a string value for the greeting message. However, the GraphQL library can also use Promises as resolvers. Since the DocumentClient uses a callback pattern, we'll wrap these in promises and use the DynamoDB `get` method to check the database for a nickname for the user. ```js // add to handler.js const promisify = foo => new Promise((resolve, reject) => { foo((error, result) => { if(error) { reject(error) } else { resolve(result) } }) }) // replace previous implementation of getGreeting const getGreeting = firstName => promisify(callback => dynamoDb.get({ TableName: process.env.DYNAMODB_TABLE, Key: { firstName }, }, callback)) .then(result => { if(!result.Item) { return firstName } return result.Item.nickname }) .then(name => `Hello, ${name}.`) // add method for updates const changeNickname = (firstName, nickname) => promisify(callback => dynamoDb.update({ TableName: process.env.DYNAMODB_TABLE, Key: { firstName }, UpdateExpression: 'SET nickname = :nickname', ExpressionAttributeValues: { ':nickname': nickname } }, callback)) .then(() => nickname) ``` You can see here that we added a method `changeNickname`, but the GraphQL API is not yet using it. We need to declare a mutation that the front-end can use to perform updates. We previously only added a `query` declaration to the schema. Now we need a `mutation` as well. ```js // alter schema const schema = new GraphQLSchema({ query: new GraphQLObjectType({ /* unchanged */ }), mutation: new GraphQLObjectType({ name: 'RootMutationType', // an arbitrary name fields: { changeNickname: { args: { // we need the user's first name as well as a preferred nickname firstName: { name: 'firstName', type: new GraphQLNonNull(GraphQLString) }, nickname: { name: 'nickname', type: new GraphQLNonNull(GraphQLString) } }, type: GraphQLString, // update the nickname resolve: (parent, args) => changeNickname(args.firstName, args.nickname) } } }) }) ``` After these changes, we can make the greeting request again and receive the same result as before. ```sh $ curl -G 'https://9qdmq5nvql.execute-api.us-east-1.amazonaws.com/dev/query' --data-urlencode 'query={greeting(firstName: "Jeremy")}' # {"data":{"greeting":"Hello, Jeremy."}} ``` But if I want the API to call me "Jer", I can update the nickname for "Jeremy". ```sh $ curl -G 'https://9qdmq5nvql.execute-api.us-east-1.amazonaws.com/dev/query' --data-urlencode 'query=mutation {changeNickname(firstName: "Jeremy", nickname: "Jer")}' $ curl -G 'https://9qdmq5nvql.execute-api.us-east-1.amazonaws.com/dev/query' --data-urlencode 'query={greeting(firstName: "Jeremy")}' # {"data":{"greeting":"Hello, Jer."}} ``` The API will now call anyone named "Jeremy" by the nickname "Jer". This kind of separation of concerns lets you build front-ends and services that offload logic into back-ends that use abstract data access and processing behind one, strongly typed, validated, uniform contract that comes with rich versioning and deprecation strategies. To deploy this service yourself, download the [source code](#todo) and deploy it with the Serverless Framework. Happy building! ================================================ FILE: aws-node-graphql-api-with-dynamodb/handler.js ================================================ const { graphql, GraphQLSchema, GraphQLObjectType, GraphQLString, GraphQLNonNull, } = require('graphql'); const AWS = require('aws-sdk'); const dynamoDb = new AWS.DynamoDB.DocumentClient(); const promisify = foo => new Promise((resolve, reject) => { foo((error, result) => { if (error) { reject(error); } else { resolve(result); } }); }); const getGreeting = firstName => promisify(callback => dynamoDb.get({ TableName: process.env.DYNAMODB_TABLE, Key: { firstName }, }, callback)) .then((result) => { if (!result.Item) { return firstName; } return result.Item.nickname; }) .then(name => `Hello, ${name}.`); const changeNickname = (firstName, nickname) => promisify(callback => dynamoDb.update({ TableName: process.env.DYNAMODB_TABLE, Key: { firstName }, UpdateExpression: 'SET nickname = :nickname', ExpressionAttributeValues: { ':nickname': nickname, }, }, callback)) .then(() => nickname); const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'RootQueryType', // an arbitrary name fields: { // the query has a field called 'greeting' greeting: { // we need to know the user's name to greet them args: { firstName: { name: 'firstName', type: new GraphQLNonNull(GraphQLString) } }, // the greeting message is a string type: GraphQLString, // resolve to a greeting message resolve: (parent, args) => getGreeting(args.firstName), }, }, }), mutation: new GraphQLObjectType({ name: 'RootMutationType', // an arbitrary name fields: { changeNickname: { args: { firstName: { name: 'firstName', type: new GraphQLNonNull(GraphQLString) }, nickname: { name: 'nickname', type: new GraphQLNonNull(GraphQLString) }, }, type: GraphQLString, resolve: (parent, args) => changeNickname(args.firstName, args.nickname), }, }, }), }); // We want to make a GET request with ?query= // The event properties are specific to AWS. Other providers will differ. module.exports.query = (event, context, callback) => graphql(schema, event.queryStringParameters.query) .then( result => callback(null, { statusCode: 200, body: JSON.stringify(result) }), err => callback(err) ); ================================================ FILE: aws-node-graphql-api-with-dynamodb/package.json ================================================ { "name": "aws-node-graphql-api-with-dynamodb", "version": "1.0.0", "description": "A single-module GraphQL endpoint with query and mutation functionality.", "main": "handler.js", "dependencies": { "aws-sdk": "^2.136.0", "eslint-plugin-react": "^7.4.0", "graphql": "^0.11.7" }, "devDependencies": {}, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC" } ================================================ FILE: aws-node-graphql-api-with-dynamodb/serverless.yml ================================================ service: graphql-api provider: name: aws runtime: nodejs10.x environment: DYNAMODB_TABLE: ${self:service}-${self:provider.stage} iam: role: statements: - Effect: Allow Action: - dynamodb:GetItem - dynamodb:UpdateItem Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}" functions: query: handler: handler.query events: - http: path: query method: get resources: Resources: NicknamesTable: Type: 'AWS::DynamoDB::Table' Properties: AttributeDefinitions: - AttributeName: firstName AttributeType: S KeySchema: - AttributeName: firstName KeyType: HASH ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 TableName: ${self:provider.environment.DYNAMODB_TABLE} ================================================ FILE: aws-node-heroku-postgres/.gitignore ================================================ # package directories node_modules jspm_packages # Serverless directories .serverless ================================================ FILE: aws-node-heroku-postgres/README.md ================================================ # aws-node-heroku-postgres An example app, created in this [blog post](https://mattwelke.com/2019/01/06/free-tier-managed-sql-with-aws-lambda-and-heroku-postgres.html), showing how to connect AWS Lambda to Heroku Postgres. Uses an `api:release` Heroku webhook and the Heroku API to handle automatic Heroku Postgres credential rotation. ================================================ FILE: aws-node-heroku-postgres/handler.js ================================================ // handler.js 'use strict'; const express = require('express'); const serverless = require('serverless-http'); const pg = require('pg'); const axios = require('axios'); const parsePgConnStr = require('pg-connection-string').parse; // Replace with your token or API key and your Heroku Postgres resource name const herokuApiKey = ''; const herokuPostgresId = ''; const herokuClient = axios.create({ baseURL: 'https://api.heroku.com/', headers: { 'Authorization': `Bearer ${herokuApiKey}`, 'Accept': 'application/vnd.heroku+json; version=3', }, }); let pgConfig; let pgPool; const app = express(); const createConn = async () => { console.log('Creating PG connection.'); const credsResponse = await herokuClient.get(`addons/${herokuPostgresId}/config`); const pgConnStr = credsResponse.data[0]['value']; pgConfig = { ...parsePgConnStr(pgConnStr), ...{ max: 1, ssl: true, }, }; pgPool = new pg.Pool(pgConfig); }; const performQuery = async () => { const client = await pgPool.connect(); const result = await client.query('SELECT now()'); client.release(); return result; }; app.get('/hello', async function (req, res) { if (!pgPool) { // Cold start. Get Heroku Postgres creds and create pool. await createConn(); } else { console.log('Using existing PG connection.'); } try { const result = await performQuery(); res.json({ result: `According to PostgreSQL, the time is: ${result.rows[0].now}`, pgConfigUsed: pgConfig, }); return; } catch (e) { res.json({ error: e.message, }); return; } }); app.post('/onrelease', async function (req, res) { // Get Heroku Postgres creds and replace pool with new one. await createConn(); // Response with 2xx response so Heroku knows webhook was successful. // Response body doesn't matter. res.status(204).send(); }); module.exports = { app, hello: serverless(app), }; ================================================ FILE: aws-node-heroku-postgres/index.js ================================================ // index.js 'use strict'; const { app } = require('./handler'); app.listen(3000, () => { console.info(`Listening on port 3000.`); }); ================================================ FILE: aws-node-heroku-postgres/package.json ================================================ { "name": "aws-lambda-and-heroku-postgres", "version": "1.0.0", "description": "Shows how to connect AWS Lambda to Heroku Postgres. Uses an api:release Heroku webhook and the Heroku API to handle automatic Heroku Postgres credential rotation.", "main": "handler.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "dependencies": { "axios": "^0.18.0", "express": "^4.16.4", "pg": "^7.7.1", "pg-connection-string": "^2.0.0", "serverless-http": "^1.8.0" } } ================================================ FILE: aws-node-heroku-postgres/serverless.yml ================================================ service: aws-lambda-and-heroku-postgres # NOTE: update this with your service name provider: name: aws runtime: nodejs12.x stage: dev functions: hello: handler: handler.hello events: - http: GET hello - http: POST onrelease ================================================ FILE: aws-node-http-api/.gitignore ================================================ node_modules .serverless ================================================ FILE: aws-node-http-api/README.md ================================================ # Serverless Framework Node HTTP API on AWS This template demonstrates how to make a simple HTTP API with Node.js running on AWS Lambda and API Gateway using the Serverless Framework. This template does not include any kind of persistence (database). For more advanced examples, check out the [serverless/examples repository](https://github.com/serverless/examples/) which includes Typescript, Mongo, DynamoDB and other examples. ## Usage ### Deployment In order to deploy the example, you need to run the following command: ``` serverless deploy ``` After running deploy, you should see output similar to: ``` Deploying "serverless-http-api" to stage "dev" (us-east-1) ✔ Service deployed to stack serverless-http-api-dev (91s) endpoint: GET - https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/ functions: hello: serverless-http-api-dev-hello (1.6 kB) ``` _Note_: In current form, after deployment, your API is public and can be invoked by anyone. For production deployments, you might want to configure an authorizer. For details on how to do that, refer to [HTTP API (API Gateway V2) event docs](https://www.serverless.com/framework/docs/providers/aws/events/http-api). ### Invocation After successful deployment, you can call the created application via HTTP: ``` curl https://xxxxxxx.execute-api.us-east-1.amazonaws.com/ ``` Which should result in response similar to: ```json { "message": "Go Serverless v4! Your function executed successfully!" } ``` ### Local development The easiest way to develop and test your function is to use the `dev` command: ``` serverless dev ``` This will start a local emulator of AWS Lambda and tunnel your requests to and from AWS Lambda, allowing you to interact with your function as if it were running in the cloud. Now you can invoke the function as before, but this time the function will be executed locally. Now you can develop your function locally, invoke it, and see the results immediately without having to re-deploy. When you are done developing, don't forget to run `serverless deploy` to deploy the function to the cloud. ================================================ FILE: aws-node-http-api/handler.js ================================================ exports.hello = async (event) => { return { statusCode: 200, body: JSON.stringify({ message: "Go Serverless v4! Your function executed successfully!", }), }; }; ================================================ FILE: aws-node-http-api/serverless.yml ================================================ service: serverless-http-api frameworkVersion: "4" provider: name: aws runtime: nodejs20.x # Uncomment to easily set up a custom domain. Read the docs for more details: # https://www.serverless.com/framework/docs/providers/aws/guide/domains # domain: api.example.com functions: hello: handler: handler.hello events: - httpApi: path: / method: get ================================================ FILE: aws-node-http-api-dynamodb/.gitignore ================================================ node_modules .serverless ================================================ FILE: aws-node-http-api-dynamodb/README.md ================================================ # Serverless HTTP API This example demonstrates how to setup a [RESTful Web Services](https://en.wikipedia.org/wiki/Representational_state_transfer#Applied_to_web_services) allowing you to create, list, get, update and delete Todos. DynamoDB is used to store the data. This is just an example and of course you could use any data storage as a backend. ## Structure This service has a separate directory for all the todo operations. For each operation exactly one file exists e.g. `todos/delete.js`. In each of these files there is exactly one function which is directly attached to `module.exports`. The idea behind the `todos` directory is that in case you want to create a service containing multiple resources e.g. users, notes, comments you could do so in the same service. While this is certainly possible you might consider creating a separate service for each resource. It depends on the use-case and your preference. ## Use-cases - API for a Web Application - API for a Mobile Application ## Setup ```bash npm install ``` ## Deploy In order to deploy the endpoint simply run ```bash serverless deploy ``` The expected result should be similar to: ```bash Serverless: Packaging service… Serverless: Uploading CloudFormation file to S3… Serverless: Uploading service .zip file to S3… Serverless: Updating Stack… Serverless: Checking Stack update progress… Serverless: Stack update finished… Service Information service: serverless-http-api-dynamodb stage: dev region: us-east-1 api keys: None endpoints: POST - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos GET - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos GET - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos/{id} PUT - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos/{id} DELETE - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos/{id} functions: serverless-http-api-dynamodb-dev-update: arn:aws:lambda:us-east-1:488110005556:function:serverless-http-api-dynamodb-dev-update serverless-http-api-dynamodb-dev-get: arn:aws:lambda:us-east-1:488110005556:function:serverless-http-api-dynamodb-dev-get serverless-http-api-dynamodb-dev-list: arn:aws:lambda:us-east-1:488110005556:function:serverless-http-api-dynamodb-dev-list serverless-http-api-dynamodb-dev-create: arn:aws:lambda:us-east-1:488110005556:function:serverless-http-api-dynamodb-dev-create serverless-http-api-dynamodb-dev-delete: arn:aws:lambda:us-east-1:488110005556:function:serverless-http-api-dynamodb-dev-delete ``` ## Usage You can create, retrieve, update, or delete todos with the following commands: ### Create a Todo ```bash curl -X POST https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos --data '{ "text": "Learn Serverless" }' ``` Example Result: ```bash {"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":false,"updatedAt":1479138570824}% ``` ### List all Todos ```bash curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos ``` Example output: ```bash [{"text":"Deploy my first service","id":"ac90feaa11e6-9ede-afdfa051af86","checked":true,"updatedAt":1479139961304},{"text":"Learn Serverless","id":"206793aa11e6-9ede-afdfa051af86","createdAt":1479139943241,"checked":false,"updatedAt":1479139943241}]% ``` ### Get one Todo ```bash # Replace the part with a real id from your todos table curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos/ ``` Example Result: ```bash {"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":false,"updatedAt":1479138570824}% ``` ### Update a Todo ```bash # Replace the part with a real id from your todos table curl -X PUT https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos/ --data '{ "text": "Learn Serverless", "checked": true }' ``` Example Result: ```bash {"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":true,"updatedAt":1479138570824}% ``` ### Delete a Todo ```bash # Replace the part with a real id from your todos table curl -X DELETE https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos/ ``` No output ## Scaling ### AWS Lambda By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 100. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit). ### DynamoDB When you create a table, you specify how much provisioned throughput capacity you want to reserve for reads and writes. DynamoDB will reserve the necessary resources to meet your throughput needs while ensuring consistent, low-latency performance. You can change the provisioned throughput and increasing or decreasing capacity as needed. This is can be done via settings in the `serverless.yml`. ```yaml ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 ``` In case you expect a lot of traffic fluctuation we recommend to checkout this guide on how to auto scale DynamoDB [https://aws.amazon.com/blogs/aws/auto-scale-dynamodb-with-dynamic-dynamodb/](https://aws.amazon.com/blogs/aws/auto-scale-dynamodb-with-dynamic-dynamodb/) ================================================ FILE: aws-node-http-api-dynamodb/package.json ================================================ { "name": "serverless-http-api-dynamodb", "version": "1.0.0", "description": "Serverless CRUD service exposing a REST HTTP interface", "author": "", "license": "MIT", "dependencies": { "uuid": "^2.0.3" } } ================================================ FILE: aws-node-http-api-dynamodb/serverless.yml ================================================ service: serverless-http-api-dynamodb frameworkVersion: '2' provider: name: aws runtime: nodejs12.x lambdaHashingVersion: '20201221' environment: DYNAMODB_TABLE: ${self:service}-${sls:stage} httpApi: cors: true iam: role: statements: - Effect: Allow Action: - dynamodb:Query - dynamodb:Scan - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem Resource: "arn:aws:dynamodb:${aws:region}:*:table/${self:provider.environment.DYNAMODB_TABLE}" functions: create: handler: todos/create.create events: - httpApi: path: /todos method: post list: handler: todos/list.list events: - httpApi: path: /todos method: get get: handler: todos/get.get events: - httpApi: path: /todos/{id} method: get update: handler: todos/update.update events: - httpApi: path: /todos/{id} method: put delete: handler: todos/delete.delete events: - httpApi: path: /todos/{id} method: delete resources: Resources: TodosDynamoDbTable: Type: 'AWS::DynamoDB::Table' DeletionPolicy: Retain Properties: AttributeDefinitions: - AttributeName: id AttributeType: S KeySchema: - AttributeName: id KeyType: HASH BillingMode: PAY_PER_REQUEST TableName: ${self:provider.environment.DYNAMODB_TABLE} ================================================ FILE: aws-node-http-api-dynamodb/todos/create.js ================================================ 'use strict'; const uuid = require('uuid'); const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies const dynamoDb = new AWS.DynamoDB.DocumentClient(); module.exports.create = (event, context, callback) => { const timestamp = new Date().getTime(); const data = JSON.parse(event.body); if (typeof data.text !== 'string') { console.error('Validation Failed'); callback(null, { statusCode: 400, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t create the todo item.', }); return; } const params = { TableName: process.env.DYNAMODB_TABLE, Item: { id: uuid.v1(), text: data.text, checked: false, createdAt: timestamp, updatedAt: timestamp, }, }; // write the todo to the database dynamoDb.put(params, (error) => { // handle potential errors if (error) { console.error(error); callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t create the todo item.', }); return; } // create a response const response = { statusCode: 200, body: JSON.stringify(params.Item), }; callback(null, response); }); }; ================================================ FILE: aws-node-http-api-dynamodb/todos/delete.js ================================================ 'use strict'; const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies const dynamoDb = new AWS.DynamoDB.DocumentClient(); module.exports.delete = (event, context, callback) => { const params = { TableName: process.env.DYNAMODB_TABLE, Key: { id: event.pathParameters.id, }, }; // delete the todo from the database dynamoDb.delete(params, (error) => { // handle potential errors if (error) { console.error(error); callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t remove the todo item.', }); return; } // create a response const response = { statusCode: 200, body: JSON.stringify({}), }; callback(null, response); }); }; ================================================ FILE: aws-node-http-api-dynamodb/todos/get.js ================================================ 'use strict'; const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies const dynamoDb = new AWS.DynamoDB.DocumentClient(); module.exports.get = (event, context, callback) => { const params = { TableName: process.env.DYNAMODB_TABLE, Key: { id: event.pathParameters.id, }, }; // fetch todo from the database dynamoDb.get(params, (error, result) => { // handle potential errors if (error) { console.error(error); callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t fetch the todo item.', }); return; } // create a response const response = { statusCode: 200, body: JSON.stringify(result.Item), }; callback(null, response); }); }; ================================================ FILE: aws-node-http-api-dynamodb/todos/list.js ================================================ 'use strict'; const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies const dynamoDb = new AWS.DynamoDB.DocumentClient(); const params = { TableName: process.env.DYNAMODB_TABLE, }; module.exports.list = (event, context, callback) => { // fetch all todos from the database dynamoDb.scan(params, (error, result) => { // handle potential errors if (error) { console.error(error); callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t fetch the todos.', }); return; } // create a response const response = { statusCode: 200, body: JSON.stringify(result.Items), }; callback(null, response); }); }; ================================================ FILE: aws-node-http-api-dynamodb/todos/update.js ================================================ 'use strict'; const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies const dynamoDb = new AWS.DynamoDB.DocumentClient(); module.exports.update = (event, context, callback) => { const timestamp = new Date().getTime(); const data = JSON.parse(event.body); // validation if (typeof data.text !== 'string' || typeof data.checked !== 'boolean') { console.error('Validation Failed'); callback(null, { statusCode: 400, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t update the todo item.', }); return; } const params = { TableName: process.env.DYNAMODB_TABLE, Key: { id: event.pathParameters.id, }, ExpressionAttributeNames: { '#todo_text': 'text', }, ExpressionAttributeValues: { ':text': data.text, ':checked': data.checked, ':updatedAt': timestamp, }, UpdateExpression: 'SET #todo_text = :text, checked = :checked, updatedAt = :updatedAt', ReturnValues: 'ALL_NEW', }; // update the todo in the database dynamoDb.update(params, (error, result) => { // handle potential errors if (error) { console.error(error); callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t fetch the todo item.', }); return; } // create a response const response = { statusCode: 200, body: JSON.stringify(result.Attributes), }; callback(null, response); }); }; ================================================ FILE: aws-node-http-api-dynamodb-local/.gitignore ================================================ .serverless node_modules ================================================ FILE: aws-node-http-api-dynamodb-local/README.md ================================================ # Serverless HTTP API with DynamoDB and offline support This example demonstrates how to run a service locally, using the [serverless-offline](https://github.com/dherault/serverless-offline) plugin. It provides an HTTP API to manage Todos stored in a DynamoDB, similar to the [aws-node-http-api-dynamodb](https://github.com/serverless/examples/tree/master/aws-node-http-api-dynamodb) example. A local DynamoDB instance is provided by the [serverless-dynamodb-local](https://github.com/99xt/serverless-dynamodb-local) plugin. ## Use-case Test your service locally, without having to deploy it first. ## Setup ```bash npm install serverless dynamodb install (or to use a persistent docker dynamodb instead, open a new terminal: cd ./dynamodb && docker-compose up -d) serverless offline start serverless dynamodb migrate (this imports schema) ``` ## Run service offline ```bash serverless offline start ``` ## Usage You can create, retrieve, update, or delete todos with the following commands: ### Create a Todo ```bash curl -X POST -H "Content-Type:application/json" http://localhost:3000/todos --data '{ "text": "Learn Serverless" }' ``` Example Result: ```bash {"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":false,"updatedAt":1479138570824}% ``` ### List all Todos ```bash curl -H "Content-Type:application/json" http://localhost:3000/todos ``` Example output: ```bash [{"text":"Deploy my first service","id":"ac90feaa11e6-9ede-afdfa051af86","checked":true,"updatedAt":1479139961304},{"text":"Learn Serverless","id":"206793aa11e6-9ede-afdfa051af86","createdAt":1479139943241,"checked":false,"updatedAt":1479139943241}]% ``` ### Get one Todo ```bash # Replace the part with a real id from your todos table curl -H "Content-Type:application/json" http://localhost:3000/todos/ ``` Example Result: ```bash {"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":false,"updatedAt":1479138570824}% ``` ### Update a Todo ```bash # Replace the part with a real id from your todos table curl -X PUT -H "Content-Type:application/json" http://localhost:3000/todos/ --data '{ "text": "Learn Serverless", "checked": true }' ``` Example Result: ```bash {"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":true,"updatedAt":1479138570824}% ``` ### Delete a Todo ```bash # Replace the part with a real id from your todos table curl -X DELETE -H "Content-Type:application/json" http://localhost:3000/todos/ ``` No output ================================================ FILE: aws-node-http-api-dynamodb-local/dynamodb/Dockerfile ================================================ FROM amazon/dynamodb-local WORKDIR /home/dynamodblocal RUN mkdir ./db && chown -R 1000 ./db CMD ["-jar", "DynamoDBLocal.jar", "-dbPath", "./db", "-sharedDb"] VOLUME ["./db"] ================================================ FILE: aws-node-http-api-dynamodb-local/dynamodb/docker-compose.yml ================================================ version: "3" services: dynamodb: build: context: . dockerfile: Dockerfile ports: - 8000:8000 volumes: - aws-http-api-dynamodb:/home/dynamodblocal/db volumes: aws-http-api-dynamodb: driver: local ================================================ FILE: aws-node-http-api-dynamodb-local/offline/migrations/todos.json ================================================ { "Table": { "TableName": "serverless-http-api-dynamodb-local-dev", "KeySchema": [ { "AttributeName": "id", "KeyType": "HASH" } ], "AttributeDefinitions": [ { "AttributeName": "id", "AttributeType": "S" } ], "ProvisionedThroughput": { "ReadCapacityUnits": 1, "WriteCapacityUnits": 1 } } } ================================================ FILE: aws-node-http-api-dynamodb-local/package.json ================================================ { "name": "serverless-http-api-dynamodb-local", "version": "1.0.0", "description": "Serverless HTTP API with DynamoDB and offline support", "repository": "", "author": "Christoph Gysin ", "license": "MIT", "dependencies": { "uuid": "^2.0.3" }, "devDependencies": { "aws-sdk": "^2.12.0", "serverless-dynamodb-local": "^0.2.18", "serverless-offline": "^6.8.0" } } ================================================ FILE: aws-node-http-api-dynamodb-local/serverless.yml ================================================ service: serverless-http-api-dynamodb-local frameworkVersion: '2' plugins: - serverless-dynamodb-local - serverless-offline custom: dynamodb: stages: - dev start: port: 8000 inMemory: true migrate: true # Comment if you don't have a DynamoDB running locally noStart: true migration: dir: offline/migrations provider: name: aws runtime: nodejs12.x lambdaHashingVersion: '20201221' environment: DYNAMODB_TABLE: ${self:service}-${sls:stage} httpApi: cors: true iam: role: statements: - Effect: Allow Action: - dynamodb:Query - dynamodb:Scan - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem Resource: "arn:aws:dynamodb:${aws:region}:*:table/${self:provider.environment.DYNAMODB_TABLE}" functions: create: handler: todos/create.create events: - httpApi: path: /todos method: post list: handler: todos/list.list events: - httpApi: path: /todos method: get get: handler: todos/get.get events: - httpApi: path: /todos/{id} method: get update: handler: todos/update.update events: - httpApi: path: /todos/{id} method: put delete: handler: todos/delete.delete events: - httpApi: path: /todos/{id} method: delete resources: Resources: TodosDynamoDbTable: Type: 'AWS::DynamoDB::Table' DeletionPolicy: Retain Properties: AttributeDefinitions: - AttributeName: id AttributeType: S KeySchema: - AttributeName: id KeyType: HASH BillingMode: PAY_PER_REQUEST TableName: ${self:provider.environment.DYNAMODB_TABLE} ================================================ FILE: aws-node-http-api-dynamodb-local/todos/create.js ================================================ 'use strict'; const uuid = require('uuid'); const dynamodb = require('./dynamodb'); module.exports.create = (event, context, callback) => { const timestamp = new Date().getTime(); const data = JSON.parse(event.body); if (typeof data.text !== 'string') { console.error('Validation Failed'); callback(null, { statusCode: 400, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t create the todo item.', }); return; } const params = { TableName: process.env.DYNAMODB_TABLE, Item: { id: uuid.v1(), text: data.text, checked: false, createdAt: timestamp, updatedAt: timestamp, }, }; // write the todo to the database dynamodb.put(params, (error) => { // handle potential errors if (error) { console.error(error); callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t create the todo item.', }); return; } // create a response const response = { statusCode: 200, body: JSON.stringify(params.Item), }; callback(null, response); }); }; ================================================ FILE: aws-node-http-api-dynamodb-local/todos/delete.js ================================================ 'use strict'; const dynamodb = require('./dynamodb'); module.exports.delete = (event, context, callback) => { const params = { TableName: process.env.DYNAMODB_TABLE, Key: { id: event.pathParameters.id, }, }; // delete the todo from the database dynamodb.delete(params, (error) => { // handle potential errors if (error) { console.error(error); callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t remove the todo item.', }); return; } // create a response const response = { statusCode: 200, body: JSON.stringify({}), }; callback(null, response); }); }; ================================================ FILE: aws-node-http-api-dynamodb-local/todos/dynamodb.js ================================================ 'use strict'; const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies let options = {}; // connect to local DB if running offline if (process.env.IS_OFFLINE) { options = { region: 'localhost', endpoint: 'http://localhost:8000', }; } const client = new AWS.DynamoDB.DocumentClient(options); module.exports = client; ================================================ FILE: aws-node-http-api-dynamodb-local/todos/get.js ================================================ 'use strict'; const dynamodb = require('./dynamodb'); module.exports.get = (event, context, callback) => { const params = { TableName: process.env.DYNAMODB_TABLE, Key: { id: event.pathParameters.id, }, }; // fetch todo from the database dynamodb.get(params, (error, result) => { // handle potential errors if (error) { console.error(error); callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t fetch the todo item.', }); return; } // create a response const response = { statusCode: 200, body: JSON.stringify(result.Item), }; callback(null, response); }); }; ================================================ FILE: aws-node-http-api-dynamodb-local/todos/list.js ================================================ 'use strict'; const dynamodb = require('./dynamodb'); module.exports.list = (event, context, callback) => { const params = { TableName: process.env.DYNAMODB_TABLE, }; // fetch all todos from the database dynamodb.scan(params, (error, result) => { // handle potential errors if (error) { console.error(error); callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t fetch the todo item.', }); return; } // create a response const response = { statusCode: 200, body: JSON.stringify(result.Items), }; callback(null, response); }); }; ================================================ FILE: aws-node-http-api-dynamodb-local/todos/update.js ================================================ 'use strict'; const dynamodb = require('./dynamodb'); module.exports.update = (event, context, callback) => { const timestamp = new Date().getTime(); const data = JSON.parse(event.body); // validation if (typeof data.text !== 'string' || typeof data.checked !== 'boolean') { console.error('Validation Failed'); callback(null, { statusCode: 400, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t update the todo item.', }); return; } const params = { TableName: process.env.DYNAMODB_TABLE, Key: { id: event.pathParameters.id, }, ExpressionAttributeNames: { '#todo_text': 'text', }, ExpressionAttributeValues: { ':text': data.text, ':checked': data.checked, ':updatedAt': timestamp, }, UpdateExpression: 'SET #todo_text = :text, checked = :checked, updatedAt = :updatedAt', ReturnValues: 'ALL_NEW', }; // update the todo in the database dynamodb.update(params, (error, result) => { // handle potential errors if (error) { console.error(error); callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t update the todo item.', }); return; } // create a response const response = { statusCode: 200, body: JSON.stringify(result.Attributes), }; callback(null, response); }); }; ================================================ FILE: aws-node-http-api-mongodb/.gitignore ================================================ .serverless node_modules ================================================ FILE: aws-node-http-api-mongodb/README.md ================================================ # Serverless MongoDB HTTP API with Mongoose and Bluebird Promises This example demonstrate how to use a MongoDB database with aws and serverless. Using Mongoose ODM and Bluebird for Promises. ## Use Cases - NoSQL CRUD API ## Setup ``` npm install serverless deploy ``` ## Usage In `handler.js` update the `mongoString` with your mongoDB url. *Create* ```bash curl -XPOST -H "Content-type: application/json" -d '{ "name" : "John", "firstname" : "Doe", "city" : "Toronto", "birth" : "01/01/1990" }' 'https://2c8cx5whk0.execute-api.us-east-1.amazonaws.com/user/' ``` ```json {"id": "590b52ff086041000142cedd"} ``` *READ* ```bash curl -XGET -H "Content-type: application/json" 'https://2c8cx5whk0.execute-api.us-east-1.amazonaws.com/user/590b52ff086041000142cedd' ``` ```json [ { "_id": "5905e2fbdb55f20001334b3e", "name": "John", "firstname": "Doe", "birth": null, "city": "Toronto", "ip": "01/01/1990", "__v": 0 } ] ``` *UPDATE* ```bash curl -XPUT -H "Content-type: application/json" -d '{ "name" : "William", "firstname" : "Smith", "city" : "Miami", "birth" : "01/01/2000" }' 'https://2c8cx5whk0.execute-api.us-east-1.amazonaws.com/user/590b52ff086041000142cedd' ``` ```json "Ok" ``` *DELETE* ```bash curl -XDELETE -H "Content-type: application/json" 'https://2c8cx5whk0.execute-api.us-east-1.amazonaws.com/user/590b52ff086041000142cedd' ``` ```json "Ok" ``` ================================================ FILE: aws-node-http-api-mongodb/handler.js ================================================ const mongoose = require('mongoose'); const Promise = require('bluebird'); const validator = require('validator'); const UserModel = require('./model/User.js'); mongoose.Promise = Promise; const mongoString = ''; // MongoDB Url const createErrorResponse = (statusCode, message) => ({ statusCode: statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: message || 'Incorrect id', }); const dbExecute = (db, fn) => db.then(fn).finally(() => db.close()); function dbConnectAndExecute(dbUrl, fn) { return dbExecute(mongoose.connect(dbUrl, { useMongoClient: true }), fn); } module.exports.user = (event, context, callback) => { if (!validator.isAlphanumeric(event.pathParameters.id)) { callback(null, createErrorResponse(400, 'Incorrect id')); return; } dbConnectAndExecute(mongoString, () => ( UserModel .find({ _id: event.pathParameters.id }) .then(user => callback(null, { statusCode: 200, body: JSON.stringify(user) })) .catch(err => callback(null, createErrorResponse(err.statusCode, err.message))) )); }; module.exports.createUser = (event, context, callback) => { const data = JSON.parse(event.body); const user = new UserModel({ name: data.name, firstname: data.firstname, birth: data.birth, city: data.city, ip: event.requestContext.identity.sourceIp, }); if (user.validateSync()) { callback(null, createErrorResponse(400, 'Incorrect user data')); return; } dbConnectAndExecute(mongoString, () => ( user .save() .then(() => callback(null, { statusCode: 200, body: JSON.stringify({ id: user.id }), })) .catch(err => callback(null, createErrorResponse(err.statusCode, err.message))) )); }; module.exports.deleteUser = (event, context, callback) => { if (!validator.isAlphanumeric(event.pathParameters.id)) { callback(null, createErrorResponse(400, 'Incorrect id')); return; } dbConnectAndExecute(mongoString, () => ( UserModel .remove({ _id: event.pathParameters.id }) .then(() => callback(null, { statusCode: 200, body: JSON.stringify('Ok') })) .catch(err => callback(null, createErrorResponse(err.statusCode, err.message))) )); }; module.exports.updateUser = (event, context, callback) => { const data = JSON.parse(event.body); const id = event.pathParameters.id; if (!validator.isAlphanumeric(id)) { callback(null, createErrorResponse(400, 'Incorrect id')); return; } const user = new UserModel({ _id: id, name: data.name, firstname: data.firstname, birth: data.birth, city: data.city, ip: event.requestContext.identity.sourceIp, }); if (user.validateSync()) { callback(null, createErrorResponse(400, 'Incorrect parameter')); return; } dbConnectAndExecute(mongoString, () => ( UserModel.findByIdAndUpdate(id, user) .then(() => callback(null, { statusCode: 200, body: JSON.stringify('Ok') })) .catch(err => callback(err, createErrorResponse(err.statusCode, err.message))) )); }; ================================================ FILE: aws-node-http-api-mongodb/model/User.js ================================================ const mongoose = require('mongoose'); const validator = require('validator'); const model = mongoose.model('User', { name: { type: String, required: true, validate: { validator(name) { return validator.isAlphanumeric(name); }, }, }, firstname: { type: String, required: true, validate: { validator(firstname) { return validator.isAlphanumeric(firstname); }, }, }, birth: { type: Date, required: true, }, city: { type: String, required: true, validate: { validator(city) { return validator.isAlphanumeric(city); }, }, }, ip: { type: String, required: true, validate: { validator(ip) { return validator.isIP(ip); }, }, }, }); module.exports = model; ================================================ FILE: aws-node-http-api-mongodb/package.json ================================================ { "name": "serverless-http-api-mongodb", "version": "1.0.0", "description": "Serverless HTTP API with MongoDB using Mongoose and Bluebird", "main": "handler.js", "dependencies": { "bluebird": "^3.5.0", "mongoose": "^4.9.6", "validator": "^7.0.0" }, "author": "Quentin Homareau ", "license": "MIT" } ================================================ FILE: aws-node-http-api-mongodb/serverless.yml ================================================ service: serverless-http-api-mongodb frameworkVersion: '2' provider: name: aws runtime: nodejs12.x lambdaHashingVersion: 20201221 httpApi: cors: true functions: createUser: handler: handler.createUser events: - httpApi: path: /user method: post updateUser: handler: handler.updateUser events: - httpApi: path: /user/{id} method: put deleteUser: handler: handler.deleteUser events: - httpApi: path: /user/{id} method: delete user: handler: handler.user events: - httpApi: path: /user/{id} method: get ================================================ FILE: aws-node-http-api-typescript/.gitignore ================================================ # package directories node_modules jspm_packages # Serverless directories .serverless ================================================ FILE: aws-node-http-api-typescript/README.md ================================================ # Serverless Framework Node with Typescript HTTP API on AWS This template demonstrates how to make a simple HTTP API with Node.js and Typescript running on AWS Lambda and API Gateway using the Serverless Framework v1. This template does not include any kind of persistence (database). For more advanced examples, check out the [serverless/examples repository](https://github.com/serverless/examples) which includes Typescript, Mongo, DynamoDB and other examples. ## Setup Run this command to initialize a new project in a new working directory. ``` npm install ``` ## Usage **Deploy** ``` $ serverless deploy ``` **Invoke the function locally.** ``` serverless invoke local --function hello ``` **Invoke the function** ``` curl https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/ ``` ================================================ FILE: aws-node-http-api-typescript/handler.ts ================================================ import { Handler } from 'aws-lambda'; export const hello: Handler = (event: any) => { const response = { statusCode: 200, body: JSON.stringify( { message: 'Go Serverless v1.0! Your function executed successfully!', input: event, }, null, 2 ), }; return new Promise((resolve) => { resolve(response) }) } ================================================ FILE: aws-node-http-api-typescript/package.json ================================================ { "name": "serverless-http-api-typescript", "description": "", "version": "0.1.0", "dependencies": {}, "devDependencies": { "@types/aws-lambda": "^8.10.61", "serverless": "^1.78.1", "serverless-offline": "^6.5.0", "serverless-plugin-typescript": "^1.1.9", "typescript": "^3.9.7" } } ================================================ FILE: aws-node-http-api-typescript/serverless.template.yml ================================================ name: aws-node-http-api-typescript org: serverlessinc description: Deploys a Node HTTP API service with Serverless Framework and Typescript keywords: aws, serverless, faas, lambda, node, typescript repo: https://github.com/serverless/examples/aws-node-http-api-typescript license: MIT ================================================ FILE: aws-node-http-api-typescript/serverless.yml ================================================ service: serverless-http-api-typescript frameworkVersion: '2' provider: name: aws runtime: nodejs12.x lambdaHashingVersion: '20201221' functions: hello: handler: handler.hello events: - httpApi: path: / method: get plugins: - serverless-plugin-typescript ================================================ FILE: aws-node-http-api-typescript-dynamodb/.gitignore ================================================ *.js ================================================ FILE: aws-node-http-api-typescript-dynamodb/README.md ================================================ # Introduction TypeScript (ts) offers type safety which is helpful when working with the AWS SDK, which comes with ts definitions (d.ts) # compiling You can compile the ts files in this directory by 1st installing typescript via `npm install -g typescript` then `npm i` You can then run the compiler by running `tsc` in this directory. It will pull the settings from .tsconfig and extra @types from package.json. The output create.js file is what will be uploaded by serverless. For brevity, I have just demonstrated this to match with the todos/create.js, todos/list.js, todos/get.js and todos/update.js lambda function ## Usage You can create, retrieve, update, or delete todos with the following commands: ### Create a Todo ```bash curl -X POST https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos --data '{ "text": "Learn Serverless" }' ``` Example Result: ```bash {"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":false,"updatedAt":1479138570824}% ``` ### List all Todos ```bash curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos ``` Example output: ```bash [{"text":"Deploy my first service","id":"ac90feaa11e6-9ede-afdfa051af86","checked":true,"updatedAt":1479139961304},{"text":"Learn Serverless","id":"206793aa11e6-9ede-afdfa051af86","createdAt":1479139943241,"checked":false,"updatedAt":1479139943241}]% ``` ### Get one Todo ```bash # Replace the part with a real id from your todos table curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos/ ``` Example Result: ```bash {"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":false,"updatedAt":1479138570824}% ``` ### Update a Todo ```bash # Replace the part with a real id from your todos table curl -X PUT https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos/ --data '{ "text": "Learn Serverless", "checked": true }' ``` Example Result: ```bash {"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":true,"updatedAt":1479138570824}% ``` ================================================ FILE: aws-node-http-api-typescript-dynamodb/package.json ================================================ { "name": "serverless-http-api-typescript-dynamodb", "version": "0.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "watch": "tsc -w", "lint": "tslint '*.ts'" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "@types/aws-sdk": "0.0.42", "@types/node": "^7.0.5", "tslint": "^5.5.0", "tslint-config-standard": "^6.0.1", "typescript": "^2.4.2" }, "dependencies": { "uuid": "^3.1.0" } } ================================================ FILE: aws-node-http-api-typescript-dynamodb/serverless.yml ================================================ service: serverless-http-api-typescript-dynamodb frameworkVersion: '2' provider: name: aws runtime: nodejs12.x lambdaHashingVersion: '20201221' environment: DYNAMODB_TABLE: ${self:service}-${sls:stage} httpApi: cors: true iam: role: statements: - Effect: Allow Action: - dynamodb:Query - dynamodb:Scan - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem Resource: "arn:aws:dynamodb:${aws:region}:*:table/${self:provider.environment.DYNAMODB_TABLE}" functions: create: handler: todos/create.create events: - httpApi: path: /todos method: post list: handler: todos/list.list events: - httpApi: path: /todos method: get get: handler: todos/get.get events: - httpApi: path: /todos/{id} method: get update: handler: todos/update.update events: - httpApi: path: /todos/{id} method: put resources: Resources: TodosDynamoDbTable: Type: 'AWS::DynamoDB::Table' DeletionPolicy: Retain Properties: AttributeDefinitions: - AttributeName: id AttributeType: S KeySchema: - AttributeName: id KeyType: HASH BillingMode: PAY_PER_REQUEST TableName: ${self:provider.environment.DYNAMODB_TABLE} ================================================ FILE: aws-node-http-api-typescript-dynamodb/todos/create.ts ================================================ 'use strict' import * as uuid from 'uuid' import { DynamoDB } from 'aws-sdk' const dynamoDb = new DynamoDB.DocumentClient() module.exports.create = (event, context, callback) => { const timestamp = new Date().getTime() const data = JSON.parse(event.body) if (typeof data.text !== 'string') { console.error('Validation Failed') callback(new Error('Couldn\'t create the todo item.')) return } const params = { TableName: process.env.DYNAMODB_TABLE, Item: { id: uuid.v1(), text: data.text, checked: false, createdAt: timestamp, updatedAt: timestamp } } // write the todo to the database dynamoDb.put(params, (error, result) => { // handle potential errors if (error) { console.error(error) callback(new Error('Couldn\'t create the todo item.')) return } // create a response const response = { statusCode: 200, body: JSON.stringify(params.Item) } callback(null, response) }) } ================================================ FILE: aws-node-http-api-typescript-dynamodb/todos/get.ts ================================================ 'use strict'; import { DynamoDB } from 'aws-sdk' const dynamoDb = new DynamoDB.DocumentClient() module.exports.get = (event, context, callback) => { const params = { TableName: process.env.DYNAMODB_TABLE, Key: { id: event.pathParameters.id, }, }; // fetch todo from the database dynamoDb.get(params, (error, result) => { // handle potential errors if (error) { console.error(error); callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t fetch the todo item.', }); return; } // create a response const response = { statusCode: 200, body: JSON.stringify(result.Item), }; callback(null, response); }); }; ================================================ FILE: aws-node-http-api-typescript-dynamodb/todos/list.ts ================================================ 'use strict'; import { DynamoDB } from 'aws-sdk' const dynamoDb = new DynamoDB.DocumentClient() const params = { TableName: process.env.DYNAMODB_TABLE, }; module.exports.list = (event, context, callback) => { // fetch all todos from the database // For production workloads you should design your tables and indexes so that your applications can use Query instead of Scan. dynamoDb.scan(params, (error, result) => { // handle potential errors if (error) { console.error(error); callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t fetch the todo items.', }); return; } // create a response const response = { statusCode: 200, body: JSON.stringify(result.Items), }; callback(null, response); }); }; ================================================ FILE: aws-node-http-api-typescript-dynamodb/todos/update.ts ================================================ 'use strict'; const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies const dynamoDb = new AWS.DynamoDB.DocumentClient(); module.exports.update = (event, context, callback) => { const timestamp = new Date().getTime(); const data = JSON.parse(event.body); // validation if (typeof data.text !== 'string' || typeof data.checked !== 'boolean') { console.error('Validation Failed'); callback(null, { statusCode: 400, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t update the todo item.', }); return; } const params = { TableName: process.env.DYNAMODB_TABLE, Key: { id: event.pathParameters.id, }, ExpressionAttributeNames: { '#todo_text': 'text', }, ExpressionAttributeValues: { ':text': data.text, ':checked': data.checked, ':updatedAt': timestamp, }, UpdateExpression: 'SET #todo_text = :text, checked = :checked, updatedAt = :updatedAt', ReturnValues: 'ALL_NEW', }; // update the todo in the database dynamoDb.update(params, (error, result) => { // handle potential errors if (error) { console.error(error); callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t fetch the todo item.', }); return; } // create a response const response = { statusCode: 200, body: JSON.stringify(result.Attributes), }; callback(null, response); }); }; ================================================ FILE: aws-node-http-api-typescript-dynamodb/tsconfig.json ================================================ { "compilerOptions": { "target": "es6", "module": "commonjs" }, "exclude": [ "node_modules" ], "types": [ "node", "aws-sdk" ] } ================================================ FILE: aws-node-http-api-typescript-dynamodb/tslint.json ================================================ { "extends": "tslint-config-standard" } ================================================ FILE: aws-node-iot-event/.gitignore ================================================ node_modules .serverless ================================================ FILE: aws-node-iot-event/README.md ================================================ # Serverless IoT Event This example demonstrates how to setup a AWS IoT Rule to send events to a Lambda function. ## Use-cases - Analytics for IoT events - Reacting on IoT events ## Setup In order to deploy the function simply run ```bash serverless deploy ``` The expected result should be similar to: ```bash Serverless: Packaging service... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading service .zip file to S3 (363 B)... Serverless: Updating Stack... Serverless: Checking Stack update progress... ................ Serverless: Stack update finished... Service Information service: aws-node-iot-event stage: dev region: us-east-1 api keys: None endpoints: None functions: aws-node-iot-event-dev-log: arn:aws:lambda:us-east-1:377024778620:function:aws-node-iot-event-dev-log ``` ## Usage In `serverless.yml` the log-function is configured to receive any event from the IoT Topic `mybutton`. We now can go to the IoT Console and visit the Tab `Test`. There fill `mybutton` into the topic input field in the publish section. Replace existing example data with the following example and press the publish button. ```json { "message": "My first IoT event", "value": 2 } ``` To verify that our event was forwarded to our log-function run ```bash serverless logs --function log ``` The expected result should be similar to: ```bash START RequestId: 241921d10f-11e6-936c-a98ff4127599 Version: $LATEST 2002 18:16:04.768 (+01:00) 241921d10f-11e6-936c-a98ff4127599 { message: 'My first IoT event', value: 2 } END RequestId: 241921d10f-11e6-936c-a98ff4127599 REPORT RequestId: 241921d10f-11e6-936c-a98ff4127599 Duration: 23.53 ms Billed Duration: 100 ms Memory Size: 1024 MB Max Memory Used: 8 MB ``` In the output you can see the IoT event that has been triggered from the test console. ================================================ FILE: aws-node-iot-event/handler.js ================================================ 'use strict'; module.exports.log = (event, context, callback) => { console.log(event); callback(null, {}); }; ================================================ FILE: aws-node-iot-event/package.json ================================================ { "name": "aws-iot-event", "version": "1.0.0", "description": "Example on how to setup a AWS IoT Rule to send events to a Lambda function", "author": "", "license": "MIT" } ================================================ FILE: aws-node-iot-event/serverless.yml ================================================ service: aws-node-iot-event frameworkVersion: ">=1.5.0 <2.0.0" provider: name: aws runtime: nodejs12.x functions: log: handler: handler.log events: - iot: sql: "SELECT * FROM 'mybutton'" ================================================ FILE: aws-node-mongodb-atlas/.gitignore ================================================ ================================================ FILE: aws-node-mongodb-atlas/README.md ================================================ # aws-node-mongodb-atlas An example app, created in this [blog post](https://mattwelke.com/2019/02/18/free-tier-serverless-mongodb-with-aws-lambda-and-mongodb-atlas.html), showing how to connect AWS Lambda to MongoDB Atlas, which must be configured with a user with read/write privileges and an IP whitelist to allow Lambda to connect to it. See blog post for detailed walkthrough setting up MongoDB Atlas. ================================================ FILE: aws-node-mongodb-atlas/handler.js ================================================ // handler.js 'use strict'; const express = require('express'); const serverless = require('serverless-http'); const MongoClient = require('mongodb').MongoClient; const faker = require('faker'); const mongoClusterName = ''; const mongoUser = ''; const mongoDbName = ''; const mongoPass = ''; const mongoConnStr = `mongodb+srv://${mongoUser}:${mongoPass}@${mongoClusterName}-tdoka.mongodb.net/${mongoDbName}?retryWrites=true`; const getPetType = () => { const msNow = Date.now(); if (msNow % 2 === 0) { return 'cat'; } return 'dog'; } const getPet = () => { return { type: getPetType(), name: faker.name.findName(), }; } const client = new MongoClient(mongoConnStr, { useNewUrlParser: true, }); let db; const createConn = async () => { await client.connect(); db = client.db('test'); }; const performQuery = async () => { const pets = db.collection('pets'); const newPet = getPet(); return { insertedPet: newPet, mongoResult: await pets.insertOne(newPet), }; }; const app = express(); app.get('/hello', async function (req, res) { if (!client.isConnected()) { // Cold start or connection timed out. Create new connection. try { await createConn(); } catch (e) { res.json({ error: e.message, }); return; } } // Connection ready. Perform insert and return result. try { res.json(await performQuery()); return; } catch (e) { res.send({ error: e.message, }); return; } }); module.exports = { app, hello: serverless(app), }; ================================================ FILE: aws-node-mongodb-atlas/index.js ================================================ // index.js 'use strict'; const { app } = require('./handler'); app.listen(3000, () => { console.info(`Listening on port 3000.`); }); ================================================ FILE: aws-node-mongodb-atlas/package.json ================================================ { "name": "aws-lambda-and-mongodb-atlas", "version": "1.0.0", "description": "Shows how to connect AWS Lambda to MongoDB Atlas.", "main": "handler.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "express": "^4.16.4", "faker": "^4.1.0", "mongodb": "^3.1.13", "serverless-http": "^1.9.0" } } ================================================ FILE: aws-node-mongodb-atlas/serverless.yml ================================================ service: my-service # NOTE: update this with your service name provider: name: aws runtime: nodejs12.x functions: hello: handler: handler.hello events: - http: GET hello ================================================ FILE: aws-node-oauth-dropbox-api/.gitignore ================================================ node_modules ================================================ FILE: aws-node-oauth-dropbox-api/README.md ================================================ ### #### For Dev 1. Create an app from 'https://www.dropbox.com/developers/apps' 2. Set Redirect URI as 'http://localhost:9999/dropbox/callback' 3. Get ClientId , ClientSecret , CallbackUrl and paste it into /config/default.yml - Change profile in serverless.yml with your respective profile - run `npm install` - run `npm run dev` 4. Go To `http://localhost:9999/dropbox/' 5. Authenticate and Authorize 6. Copy the Access Token 7. Make the final request to dropbox api (To generate a temprory link of a file) ``` curl -X POST \ https://api.dropboxapi.com/2/files/get_temporary_link \ -H 'Authorization: Bearer ' \ -H 'Cache-Control: no-cache' \ -H 'Content-Type: application/json' \ -d '{ "path" : "/temp.rtf" }' P.S :- add your access token in Authorization header after Bearer eg :- [Bearer aklfbakbjkasbcbvkcjba] and make sure there exists the temp.rtf file in your dropbox root directory ### For Dev Test 1. Go to `default_test.yml` 2. Change the EMAIL and PASSWORD with your own dropbox credentials 3. `npm run test` ### For deploying on AWS - Follow the same procedure for deploying it on AWS just make the necessary changes in the following files - `config/stage.yml` - `config/stage_test.yml` - `test/test.js (link to stage_test.yml)` ================================================ FILE: aws-node-oauth-dropbox-api/config/default.yml ================================================ STAGE: 'default' HOME_URL: 'http://localhost:9999/' CLIENT_ID : 'xxxxxx' ##Change with your Dropbox Client Id CLIENT_SECRET : 'xxxxxx' ##Change with your Dropbox Client Secret CALLBACK_URL : 'http://localhost:9999/dropbox/callback' ================================================ FILE: aws-node-oauth-dropbox-api/config/default_test.yml ================================================ URL : 'http://localhost:9999/dropbox' EMAIL : 'xyzxyz@xyz.xyz' EMAIL_INPUT_BOX : 'input[name="login_email"]' PASSWORD : 'xyz@123' PASSWORD_INPUT_BOX : 'input[name="login_password"]' SUBMIT : '.login-button' ALLOW : ".button-primary" ================================================ FILE: aws-node-oauth-dropbox-api/config/stage.yml ================================================ STAGE: 'default' HOME_URL: 'https://xxxxxx.amazonaws.com/stage/dropbox' ##change with your lamba url CLIENT_ID : 'xxxxxx' ##Change with your Dropbox Client Id CLIENT_SECRET : 'xxxxxx' ##Change with your Dropbox Client Secret CALLBACK_URL : 'https://xxxxxx.amazonaws.com/stage/dropbox/callback' ##change with your lamba url ================================================ FILE: aws-node-oauth-dropbox-api/config/stage_test.yml ================================================ URL : 'https://xxxxxx.ap-south-1.amazonaws.com/stage/dropbox/' ##change with your lambda url EMAIL : 'xxxxxyz@xxxx.xyz' #change with your email id EMAIL_INPUT_BOX : 'input[name="login_email"]' PASSWORD : 'xxxx@123' #change with your password PASSWORD_INPUT_BOX : 'input[name="login_password"]' SUBMIT : '.login-button' ALLOW : ".button-primary" ================================================ FILE: aws-node-oauth-dropbox-api/dropbox/handler.js ================================================ import request from 'request'; import btoa from 'btoa'; //User Authorization to get a code export const step1 = (data_ , context , callBack) => { const state = Math.round(Math.random() * 10); const url = `https://www.dropbox.com/1/oauth2/authorize?client_id=${process.env.CLIENT_ID}&response_type=code&redirect_uri=${process.env.CALLBACK_URL}&state=${state}`; callBack(null,{ statusCode : 302 , headers : { location : url } }) } //get a access token to make requests to the api (Make Request From Postman) export const step2 = (data_ , context , callBack ) => { const base64 = btoa(`${process.env.CLIENT_ID}:${process.env.CLIENT_SECRET}`) const options = { method: 'POST', url: 'https://api.dropbox.com/1/oauth2/token', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Cache-Control': 'no-cache', Authorization: `Basic ${base64}`, }, form: { code: data_.queryStringParameters.code, grant_type: 'authorization_code', redirect_uri: process.env.CALLBACK_URL } }; request(options, function (error, response, body) { callBack(null , { statusCode : 200 , body }) }); } ================================================ FILE: aws-node-oauth-dropbox-api/package.json ================================================ { "name": "dropbox", "version": "1.0.0", "description": "dropbox integration", "main": "index.js", "scripts": { "test": "stage=dev IS_OFFLINE=true node_modules/.bin/mocha --compilers js:babel-core/register test/ --recursive --timeout=60000", "lintonce": "node_modules/.bin/esw -f table --color --debug src", "dev": "serverless offline --compilers js:babel-core/register --stage default", "deploy": "SLS_DEBUG=* serverless deploy --verbose --stage stage" }, "keywords": [ "dropbox", "api" ], "author": "Jay Deshmukh", "license": "ISC", "dependencies": { "btoa": "^1.2.1", "request": "^2.87.0", "serverless": "^1.27.3" }, "devDependencies": { "babel-core": "^6.26.0", "babel-eslint": "^8.0.2", "babel-loader": "^6.4.1", "babel-plugin-syntax-async-functions": "^6.13.0", "babel-plugin-transform-runtime": "^6.23.0", "babel-preset-env": "^1.6.1", "babel-preset-es2015": "^6.24.1", "babel-preset-latest": "^6.24.1", "babel-preset-stage-3": "^6.24.1", "chromeless": "^1.3.0", "eslint": "^4.11.0", "eslint-config-airbnb": "^16.1.0", "eslint-config-airbnb-base": "^12.1.0", "eslint-plugin-import": "^2.12.0", "js-yaml": "^3.12.0", "mocha": "^5.2.0", "serverless-offline": "^3.16.0", "serverless-plugin-optimize": "^3.0.3-rc.1", "tv4": "^1.3.0" } } ================================================ FILE: aws-node-oauth-dropbox-api/serverless.yml ================================================ service: dropbox provider: profile: personal runtime: nodejs12.x name: aws region: ap-south-1 timeout: 60 stage: ${opt:stage} environment: CLIENT_SECRET: ${file(./config/${self:provider.stage}.yml):CLIENT_SECRET} CLIENT_ID: ${file(./config/${self:provider.stage}.yml):CLIENT_ID} STAGE: ${file(./config/${opt:stage}.yml):STAGE} CALLBACK_URL: ${file(./config/${self:provider.stage}.yml):CALLBACK_URL} plugins: - serverless-offline - serverless-plugin-optimize custom: serverless-offline: port: 9999 host: 0.0.0.0 babelOptions: presets: ["es2015", "latest"] optimize: minify: true functions: dropbox_step1: handler: dropbox/handler.step1 events: - http: method: get path: dropbox cors: true dropbox_step2: handler: dropbox/handler.step2 events: - http: method: get path: dropbox/callback cors: true ================================================ FILE: aws-node-oauth-dropbox-api/test/test.js ================================================ const { Chromeless } = require('chromeless') const tv4 = require('tv4'); const assert = require('assert'); const yaml = require('js-yaml'); const fs = require('fs'); run = async ()=> { try { const test = yaml.safeLoad(fs.readFileSync("./config/default_test.yml")); const chromeless = new Chromeless() const json = await chromeless .clearCache() .clearCookies() .goto(test.URL) .wait(2000) .type(test.EMAIL , test.EMAIL_INPUT_BOX) .type(test.PASSWORD, test.PASSWORD_INPUT_BOX) .click(test.SUBMIT) .wait(5000) .click(test.ALLOW) .wait(3000) .evaluate(() => document.querySelector('body pre').innerHTML) const schema = { type: 'object' , properties : { "access_token" : { "type" : "string" } }, "required" : ["access_token"] } assert.equal(tv4.validate(JSON.parse(json),schema,true),true) await chromeless.end() } catch (error) { assert.fail(error); } } describe("Test :- Dropbox " , () => { it("Test Dropbox :- User Authorization With OAuth", async () => { const response = await run() }).timeout(15000) }) ================================================ FILE: aws-node-puppeteer/.gitignore ================================================ # package directories node_modules jspm_packages # Serverless directories .serverless ================================================ FILE: aws-node-puppeteer/README.md ================================================ ### Running Puppeteer on AWS Lambda Using Serverless Framework ### Instructions to run locally ``` $ npm install $ sls offline ``` ### To Deploy on AWS - Add your profile in `serverless.yml` and run ``` $ sls deploy ``` ___________ # About Project When it comes to AWS Lambda function , they have their own limits as follows ![AWS Limits](./images/aws_limits.png) So , When you try to use Puppeteer your deployment package size(unzipped) easily go's above 250 mb because When you install Puppeteer, it downloads a recent version of Chromium (~170MB Mac, ~282MB Linux, ~280MB Win) that is guaranteed to work with the API. ## Solution Best solution I found for this problem is using this awesome Serverless-framework Headless Chrome Plugin i.e `serverless-plugin-chrome` # How ?? ## 1. Add the Plugin in your serverless.yml ``` plugins: - serverless-plugin-chrome ``` ## 2. Install Following Dependencies - superagent - @serverless-chrome/lambda - puppeteer ``` $ npm i superagent @serverless-chrome/lambda puppeteer ``` ## 3.Exclude Chromium Dist that comes with puppeteer by default We can do this in package section of our `serverless.yml` ``` package: exclude: - node_modules/puppeteer/.local-chromium/** ``` ## 4. Create a new file and name it chrome-script.js Add the following lines to chrome-script.js ``` const launchChrome = require ("@serverless-chrome/lambda"); const request = require ("superagent"); module.exports.getChrome = async () => { const chrome = await launchChrome(); const response = await request .get(`${chrome.url}/json/version`) .set("Content-Type", "application/json"); const endpoint = response.body.webSocketDebuggerUrl; return { endpoint, instance: chrome }; }; ``` `@serverless-chrome/lambda` provide scaffolding for using Headless Chrome during a serverless function invocation. Serverless Chrome takes care of building and bundling the Chrome binaries and making sure Chrome is running when your serverless function executes. In addition, this project also provides a few example services for common patterns (e.g. taking a screenshot of a page, printing to PDF, some scraping, etc.) ## 5.Connect Puppeteer With Headless Chrome - import chrome in our `handler.js` ``` const {getChrome} = require('./chrome-script') ``` - connect it with puppeteer ``` const browser = await puppeteer.connect({ browserWSEndpoint: chrome.endpoint }); ``` That's all you can now use puppeteer on aws lambda ### To Test It Locally ``` $ npm i serverless-offline $ npm i chrome-launcher ``` - Make the following request (replace `{{URL}}` with the page you want to get content for) ``` curl -X GET \ 'http://localhost:3000?url={{URL}}' \ ``` ### To Deploy on AWS ``` $ sls deploy ``` - Make the following request (replace `{{URL}}` with the page you want to get content for and `{{lambda_url}}` with your lambda url) ``` curl -X GET \ '{{lambda_url}}?url={{URL}}' \ ``` ================================================ FILE: aws-node-puppeteer/chrome-script.js ================================================ const launchChrome = require('@serverless-chrome/lambda'); const request = require('superagent'); module.exports.getChrome = async () => { const chrome = await launchChrome(); const response = await request .get(`${chrome.url}/json/version`) .set('Content-Type', 'application/json'); const endpoint = response.body.webSocketDebuggerUrl; return { endpoint, instance: chrome, }; }; ================================================ FILE: aws-node-puppeteer/handler.js ================================================ const puppeteer = require('puppeteer'); const { getChrome } = require('./chrome-script'); module.exports.hello = async (event) => { const { url } = event.queryStringParameters; const chrome = await getChrome(); const browser = await puppeteer.connect({ browserWSEndpoint: chrome.endpoint, }); const page = await browser.newPage(); await page.goto(url, { waitUntil: 'networkidle0' }); const content = await page.evaluate(() => document.body.innerHTML); return { statusCode: 200, body: JSON.stringify({ content, }), }; }; ================================================ FILE: aws-node-puppeteer/package.json ================================================ { "name": "aws-node-puppeteer", "version": "1.0.0", "description": "When it comes to AWS Lambda function , they have their own limits as follows ![AWS Limits](./images/aws_limits.png) So , When you try to use Puppeteer your deployment package size(unzipped) easily go's above 250 mb because When you install Puppeteer, it downloads a recent version of Chromium (~170MB Mac, ~282MB Linux, ~280MB Win) that is guaranteed to work with the API.", "main": "chrome-script.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "@serverless-chrome/lambda": "^1.0.0-55", "chrome-launcher": "^0.12.0", "puppeteer": "^2.1.0", "serverless-offline": "^5.12.1", "serverless-plugin-chrome": "^1.0.0-55.3", "superagent": "^5.2.1" } } ================================================ FILE: aws-node-puppeteer/serverless.yml ================================================ service: scrapper-lambda provider: name: aws profile: runtime: nodejs12.x plugins: - serverless-offline - serverless-plugin-chrome package: exclude: - node_modules/puppeteer/.local-chromium/** functions: hello: handler: handler.hello memorySize: 1536MB timeout: 30 events: - http: path: / method: get ================================================ FILE: aws-node-recursive-function/.gitignore ================================================ node_modules .serverless ================================================ FILE: aws-node-recursive-function/README.md ================================================ # Recursive Lambda function Invocation This is an example of a function that will recursively call itself. **Warning** It's possible to run into infinite loops with recursive calls. Test your functions [locally](https://serverless.com/framework/docs/providers/aws/cli-reference/invoke#invoke-local) before deploying to production. ## Use cases - Functions that run longer that 5 minutes - [Use recursive function to process SQS messages](http://theburningmonk.com/2016/04/aws-lambda-use-recursive-function-to-process-sqs-messages-part-1/) - [Lamdba chaining](https://github.com/pmuens/serverless-lambda-chaining) Running a function recursively will allow you to pass state information to the next function call. ## Setup #### 1. Deploy the function with `sls deploy` The `sls deploy` command will give you back the function ARN (Amazon Resource Name) needed for the function to recursively call itself. The message should look something like: ```bash Service Information service: recursive-invocation-example stage: dev region: us-east-1 api keys: None endpoints: None functions: recursive-invocation-example-dev-recursiveExample: arn:aws:lambda:us-east-1:488110005556:function:recursive-invocation-example-dev-recursiveExample ``` The ARN in this example is `arn:aws:lambda:us-east-1:488110005556:function:recursive-invocation-example-dev-recursiveExample`. If you need to retrieve this data again run the `serverless info` command. #### 2. Take your newly created function's ARN and replace the custom: functionARN value `yourFunctionARN` value in `serverless.yml` with your ARN. Before: ```yml # in serverless.yml custom: functionARN: yourFunctionARN ``` After: ```yml # in serverless.yml custom: functionARN: arn:aws:lambda:us-east-1:488110005556:function:recursive-invocation-example-dev-recursiveExample ``` #### 3. Uncomment the IAM statement in `serverless.yml` ```yml # in serverless.yml provider: name: aws runtime: nodejs12.x iamRoleStatements: - Effect: "Allow" Action: - "lambda:InvokeFunction" Resource: ${self:custom.functionARN} ``` The `custom: functionARN` value is referenced as a [serverless variable](https://serverless.com/framework/docs/providers/aws/guide/variables/) in the IAM statement the variable syntax `${self:custom.functionARN}` For more information on serverless variables. [Read the variable docs](https://serverless.com/framework/docs/providers/aws/guide/variables/). #### 4. Redeploy the function to enable the new IAM role. Run `sls deploy` again to redeploy the service and apply the new IAM role needed for the function to call itself. ## Invoking **Important** Make sure to set a limit on the number of invocations and test locally first to avoid infinite recursive loops in AWS. **Invoke the function:** ```bash sls invoke -f recursiveExample -p event.json ``` **See the logs of the recursive calls:** ``` sls logs -f recursiveExample ``` The logs output should look something like: ```bash START RequestId: 43a9d5b46c-11e6-b6bc-718f7ec807df Version: $LATEST 2026 22:39:37.769 (-08:00) 43a9d5b46c-11e6-b6bc-718f7ec807df received { numberOfCalls: 5 } 2026 22:39:37.792 (-08:00) 43a9d5b46c-11e6-b6bc-718f7ec807df recursive call END RequestId: 43a9d5b46c-11e6-b6bc-718f7ec807df REPORT RequestId: 43a9d5b46c-11e6-b6bc-718f7ec807df Duration: 270.23 ms Billed Duration: 300 ms Memory Size: 1024 MB Max Memory Used: 32 MB START RequestId: 446bedb46c-11e6-88fd-1bd64622e38d Version: $LATEST 2026 22:39:37.966 (-08:00) 446bedb46c-11e6-88fd-1bd64622e38d received { numberOfCalls: 4 } 2026 22:39:37.966 (-08:00) 446bedb46c-11e6-88fd-1bd64622e38d recursive call END RequestId: 446bedb46c-11e6-88fd-1bd64622e38d REPORT RequestId: 446bedb46c-11e6-88fd-1bd64622e38d Duration: 119.04 ms Billed Duration: 200 ms Memory Size: 1024 MB Max Memory Used: 32 MB START RequestId: 4479f6b46c-11e6-b27d58a248a566 Version: $LATEST 2026 22:39:38.122 (-08:00) 4479f6b46c-11e6-b27d58a248a566 received { numberOfCalls: 3 } 2026 22:39:38.122 (-08:00) 4479f6b46c-11e6-b27d58a248a566 recursive call END RequestId: 4479f6b46c-11e6-b27d58a248a566 REPORT RequestId: 4479f6b46c-11e6-b27d58a248a566 Duration: 40.55 ms Billed Duration: 100 ms Memory Size: 1024 MB Max Memory Used: 32 MB START RequestId: 44914fb46c-11e6-ae2b-65715f0c0d90 Version: $LATEST 2026 22:39:38.196 (-08:00) 44914fb46c-11e6-ae2b-65715f0c0d90 received { numberOfCalls: 2 } 2026 22:39:38.196 (-08:00) 44914fb46c-11e6-ae2b-65715f0c0d90 recursive call END RequestId: 44914fb46c-11e6-ae2b-65715f0c0d90 REPORT RequestId: 44914fb46c-11e6-ae2b-65715f0c0d90 Duration: 32.38 ms Billed Duration: 100 ms Memory Size: 1024 MB Max Memory Used: 32 MB START RequestId: 449c72f5-b46c-11e6-a441cb6f0603cc Version: $LATEST 2026 22:39:38.268 (-08:00) 449c72f5-b46c-11e6-a441cb6f0603cc received { numberOfCalls: 1 } 2026 22:39:38.268 (-08:00) 449c72f5-b46c-11e6-a441cb6f0603cc recursive call END RequestId: 449c72f5-b46c-11e6-a441cb6f0603cc REPORT RequestId: 449c72f5-b46c-11e6-a441cb6f0603cc Duration: 49.82 ms Billed Duration: 100 ms Memory Size: 1024 MB Max Memory Used: 32 MB START RequestId: 44a8f64b-b46c-11e6-b0535b4cab8224 Version: $LATEST 2026 22:39:38.350 (-08:00) 44a8f64b-b46c-11e6-b0535b4cab8224 received { numberOfCalls: 0 } 2026 22:39:38.350 (-08:00) 44a8f64b-b46c-11e6-b0535b4cab8224 recursive call finished END RequestId: 44a8f64b-b46c-11e6-b0535b4cab8224 REPORT RequestId: 44a8f64b-b46c-11e6-b0535b4cab8224 Duration: 0.56 ms Billed Duration: 100 ms Memory Size: 1024 MB Max Memory Used: 32 MB ``` ================================================ FILE: aws-node-recursive-function/event.json ================================================ { "numberOfCalls": 5 } ================================================ FILE: aws-node-recursive-function/handler.js ================================================ /* eslint-disable */ /* aws-sdk automatically included in lambda context */ const aws = require('aws-sdk'); module.exports.recursiveLambda = (event, context, callback) => { const lambda = new aws.Lambda(); console.log('received', event); /* if numberOfCalls still has value, continue recursive operation */ if (event.numberOfCalls > 0) { console.log('recursive call'); /* decrement numberOfCalls so we don't infinitely loop */ event.numberOfCalls = event.numberOfCalls - 1; const params = { FunctionName: context.functionName, InvocationType: 'Event', Payload: JSON.stringify(event), Qualifier: context.functionVersion }; lambda.invoke(params, context.done); } else { console.log('recursive call finished'); context.succeed('finished'); } }; ================================================ FILE: aws-node-recursive-function/serverless.yml ================================================ service: recursive-invocation-example custom: functionARN: yourFunctionARN provider: name: aws runtime: nodejs12.x # iam: # role: # statements: # - Effect: "Allow" # Action: # - "lambda:InvokeFunction" # Resource: ${self:custom.functionARN} functions: recursiveExample: handler: handler.recursiveLambda ================================================ FILE: aws-node-rekognition-analysis-s3-image/.gitignore ================================================ node_modules .serverless ================================================ FILE: aws-node-rekognition-analysis-s3-image/README.md ================================================ # Analyse Image from S3 with Amazon Rekognition Example This example shows how to analyze an image in an S3 bucket with Amazon Rekognition and return a list of labels. ## Use-cases - Determine if there is a cat in an image. ## Setup You need to create an S3 bucket and upload at least one file. Be sure the permissions on the folder and file allow public access and that CORS is configured to allow access. ```bash npm install ``` ## Deploy In order to deploy the function run: ```bash serverless deploy ``` The expected result should be similar to: ```bash Serverless: Packaging service... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading service .zip file to S3 (3.78 MB)... Serverless: Updating Stack... Serverless: Checking Stack update progress... .............. Serverless: Stack update finished... Service Information service: rekognition-analysis-s3-image stage: dev region: us-east-1 api keys: None endpoints: POST - https://6bbhhv5q22.execute-api.us-east-1.amazonaws.com/dev/analysis functions: imageAnalysis: rekognition-analysis-s3-image-dev-imageAnalysis ``` ## Usage You can now send an HTTP POST request directly to the endpoint using a tool like curl ``` { "bucket": "mycatphotos", "imageName": "cat.jpg" } ``` ```bash serverless invoke local -f imageAnalysis -p post.json ``` The expected result should be similar to: ```json { "Labels": [ { "Confidence": 96.59198760986328, "Name": "Animal" }, { "Confidence": 96.59198760986328, "Name": "Cat" }, { "Confidence": 96.59198760986328, "Name": "Pet" }, { "Confidence": 96.59198760986328, "Name": "Siamese" } ] } ``` ## Scaling By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 100. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit). ================================================ FILE: aws-node-rekognition-analysis-s3-image/handler.js ================================================ 'use strict'; const ImageAnalyser = require('./lib/imageAnalyser'); /** Analyse an image on S3 using bucket and image name */ module.exports.imageAnalysis = (event, context, callback) => { const data = JSON.parse(event.body); const s3Config = { bucket: data.bucket, imageName: data.imageName, }; return ImageAnalyser .getImageLabels(s3Config) .then((labels) => { const response = { statusCode: 200, body: JSON.stringify({ Labels: labels }), }; callback(null, response); }) .catch((error) => { callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: error.message || 'Internal server error', }); }); }; ================================================ FILE: aws-node-rekognition-analysis-s3-image/lib/imageAnalyser.js ================================================ 'use strict'; const AWS = require('aws-sdk'); const rek = new AWS.Rekognition(); class ImageAnalyser { static getImageLabels(s3Config) { const params = { Image: { S3Object: { Bucket: s3Config.bucket, Name: s3Config.imageName, }, }, MaxLabels: 10, MinConfidence: 50, }; console.log(`Analyzing file: https://s3.amazonaws.com/${s3Config.bucket}/${s3Config.imageName}`); return new Promise((resolve, reject) => { rek.detectLabels(params, (err, data) => { if (err) { return reject(new Error(err)); } console.log('Analysis labels:', data.Labels); return resolve(data.Labels); }); }); } } module.exports = ImageAnalyser; ================================================ FILE: aws-node-rekognition-analysis-s3-image/package.json ================================================ { "name": "aws-node-rekognition-analysis-s3-image", "description": "Analyse an Image from an S3 Bucket with Amazon Rekognition", "version": "1.0.0", "author": "lynnaloo", "license": "MIT", "dependencies": { "aws-sdk": "^2.32.0" } } ================================================ FILE: aws-node-rekognition-analysis-s3-image/post.json ================================================ { "body": { "bucket": "mycatphotos", "imageName": "cat.jpg" } } ================================================ FILE: aws-node-rekognition-analysis-s3-image/serverless.yml ================================================ service: rekognition-analysis-s3-image frameworkVersion: ">=1.10.0" provider: name: aws runtime: nodejs12.x memorySize: 512 timeout: 10 stage: dev region: us-east-1 iam: role: statements: - Effect: Allow Action: - s3:* Resource: "*" - Effect: "Allow" Action: - "rekognition:*" Resource: "*" functions: imageAnalysis: handler: handler.imageAnalysis events: - http: path: analysis method: post ================================================ FILE: aws-node-rest-api/.gitignore ================================================ # package directories node_modules jspm_packages # Serverless directories .serverless ================================================ FILE: aws-node-rest-api/README.md ================================================ # Serverless Framework Node REST API on AWS This template demonstrates how to make a simple REST API with Node.js running on AWS Lambda and API Gateway using the traditional Serverless Framework. This template does not include any kind of persistence (database). For a more advanced examples check out the [examples repo](https://github.com/serverless/examples/) which includes Typescript, Mongo, DynamoDB and other examples. ## Usage ### Deployment This example is made to work with the Serverless Framework dashboard which includes advanced features like CI/CD, monitoring, metrics, etc. ``` $ serverless login $ serverless deploy ``` To deploy without the dashboard you will need to remove `org` and `app` fields from the `serverless.yml`, and you won’t have to run `sls login` before deploying. After running deploy, you should see output similar to: ```bash Serverless: Packaging service... Serverless: Excluding development dependencies... Serverless: Creating Stack... Serverless: Checking Stack create progress... ........ Serverless: Stack create finished... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading artifacts... Serverless: Uploading service aws-node-rest-api.zip file to S3 (711.23 KB)... Serverless: Validating template... Serverless: Updating Stack... Serverless: Checking Stack update progress... ................................. Serverless: Stack update finished... Service Information service: aws-node-rest-api stage: dev region: us-east-1 stack: aws-node-rest-api-dev resources: 12 api keys: None endpoints: ANY - https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/ functions: api: aws-node-rest-api-dev-hello layers: None ``` _Note_: In current form, after deployment, your API is public and can be invoked by anyone. For production deployments, you might want to configure an authorizer. For details on how to do that, refer to [http event docs](https://www.serverless.com/framework/docs/providers/aws/events/apigateway/). ### Invocation After successful deployment, you can call the created application via HTTP: ```bash curl https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/ ``` Which should result in response similar to the following (removed `input` content for brevity): ```json { "message": "Go Serverless v2.0! Your function executed successfully!", "input": { ... } } ``` ### Local development You can invoke your function locally by using the following command: ```bash serverless invoke local --function hello ``` Which should result in response similar to the following: ``` { "statusCode": 200, "body": "{\n \"message\": \"Go Serverless v2.0! Your function executed successfully!\",\n \"input\": \"\"\n}" } ``` Alternatively, it is also possible to emulate API Gateway and Lambda locally by using `serverless-offline` plugin. In order to do that, execute the following command: ```bash serverless plugin install -n serverless-offline ``` It will add the `serverless-offline` plugin to `devDependencies` in `package.json` file as well as will add it to `plugins` in `serverless.yml`. After installation, you can start local emulation with: ``` serverless offline ``` To learn more about the capabilities of `serverless-offline`, please refer to its [GitHub repository](https://github.com/dherault/serverless-offline). ================================================ FILE: aws-node-rest-api/handler.js ================================================ "use strict"; module.exports.hello = async (event) => { return { statusCode: 200, body: JSON.stringify( { message: "Go Serverless v2.0! Your function executed successfully!", input: event, }, null, 2 ), }; }; ================================================ FILE: aws-node-rest-api/serverless.template.yml ================================================ name: aws-node-rest-api org: serverlessinc description: Deploys a Node REST API service with traditional Serverless Framework keywords: aws, serverless, faas, lambda, node repo: https://github.com/serverless/examples/aws-node-rest-api license: MIT ================================================ FILE: aws-node-rest-api/serverless.yml ================================================ service: aws-node-rest-api frameworkVersion: '2' provider: name: aws runtime: nodejs12.x lambdaHashingVersion: '20201221' functions: hello: handler: handler.hello events: - http: path: / method: get ================================================ FILE: aws-node-rest-api-mongodb/.gitignore ================================================ .serverless node_modules ================================================ FILE: aws-node-rest-api-mongodb/README.md ================================================ # Serverless MongoDB Rest API with Mongoose and Bluebird Promises This example demonstrate how to use a MongoDB database with aws and serverless. Using Mongoose ODM and Bluebird for Promises. ## Use Cases - NoSQL CRUD API ## Setup ``` npm install serverless deploy ``` ## Usage In `handler.js` update the `mongoString` with your mongoDB url. *Create* ```bash curl -XPOST -H "Content-type: application/json" -d '{ "name" : "John", "firstname" : "Doe", "city" : "Toronto", "birth" : "01/01/1990" }' 'https://2c8cx5whk0.execute-api.us-east-1.amazonaws.com/dev/user/' ``` ```json {"id": "590b52ff086041000142cedd"} ``` *READ* ```bash curl -XGET -H "Content-type: application/json" 'https://2c8cx5whk0.execute-api.us-east-1.amazonaws.com/dev/user/590b52ff086041000142cedd' ``` ```json [ { "_id": "5905e2fbdb55f20001334b3e", "name": "John", "firstname": "Doe", "birth": null, "city": "Toronto", "ip": "01/01/1990", "__v": 0 } ] ``` *UPDATE* ```bash curl -XPUT -H "Content-type: application/json" -d '{ "name" : "William", "firstname" : "Smith", "city" : "Miami", "birth" : "01/01/2000" }' 'https://2c8cx5whk0.execute-api.us-east-1.amazonaws.com/dev/user/590b52ff086041000142cedd' ``` ```json "Ok" ``` *DELETE* ```bash curl -XDELETE -H "Content-type: application/json" 'https://2c8cx5whk0.execute-api.us-east-1.amazonaws.com/dev/user/590b52ff086041000142cedd' ``` ```json "Ok" ``` ================================================ FILE: aws-node-rest-api-mongodb/handler.js ================================================ const mongoose = require('mongoose'); const Promise = require('bluebird'); const validator = require('validator'); const UserModel = require('./model/User.js'); mongoose.Promise = Promise; const mongoString = ''; // MongoDB Url const createErrorResponse = (statusCode, message) => ({ statusCode: statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: message || 'Incorrect id', }); const dbExecute = (db, fn) => db.then(fn).finally(() => db.close()); function dbConnectAndExecute(dbUrl, fn) { return dbExecute(mongoose.connect(dbUrl, { useMongoClient: true }), fn); } module.exports.user = (event, context, callback) => { if (!validator.isAlphanumeric(event.pathParameters.id)) { callback(null, createErrorResponse(400, 'Incorrect id')); return; } dbConnectAndExecute(mongoString, () => ( UserModel .find({ _id: event.pathParameters.id }) .then(user => callback(null, { statusCode: 200, body: JSON.stringify(user) })) .catch(err => callback(null, createErrorResponse(err.statusCode, err.message))) )); }; module.exports.createUser = (event, context, callback) => { const data = JSON.parse(event.body); const user = new UserModel({ name: data.name, firstname: data.firstname, birth: data.birth, city: data.city, ip: event.requestContext.identity.sourceIp, }); if (user.validateSync()) { callback(null, createErrorResponse(400, 'Incorrect user data')); return; } dbConnectAndExecute(mongoString, () => ( user .save() .then(() => callback(null, { statusCode: 200, body: JSON.stringify({ id: user.id }), })) .catch(err => callback(null, createErrorResponse(err.statusCode, err.message))) )); }; module.exports.deleteUser = (event, context, callback) => { if (!validator.isAlphanumeric(event.pathParameters.id)) { callback(null, createErrorResponse(400, 'Incorrect id')); return; } dbConnectAndExecute(mongoString, () => ( UserModel .remove({ _id: event.pathParameters.id }) .then(() => callback(null, { statusCode: 200, body: JSON.stringify('Ok') })) .catch(err => callback(null, createErrorResponse(err.statusCode, err.message))) )); }; module.exports.updateUser = (event, context, callback) => { const data = JSON.parse(event.body); const id = event.pathParameters.id; if (!validator.isAlphanumeric(id)) { callback(null, createErrorResponse(400, 'Incorrect id')); return; } const user = new UserModel({ _id: id, name: data.name, firstname: data.firstname, birth: data.birth, city: data.city, ip: event.requestContext.identity.sourceIp, }); if (user.validateSync()) { callback(null, createErrorResponse(400, 'Incorrect parameter')); return; } dbConnectAndExecute(mongoString, () => ( UserModel.findByIdAndUpdate(id, user) .then(() => callback(null, { statusCode: 200, body: JSON.stringify('Ok') })) .catch(err => callback(err, createErrorResponse(err.statusCode, err.message))) )); }; ================================================ FILE: aws-node-rest-api-mongodb/model/User.js ================================================ const mongoose = require('mongoose'); const validator = require('validator'); const model = mongoose.model('User', { name: { type: String, required: true, validate: { validator(name) { return validator.isAlphanumeric(name); }, }, }, firstname: { type: String, required: true, validate: { validator(firstname) { return validator.isAlphanumeric(firstname); }, }, }, birth: { type: Date, required: true, }, city: { type: String, required: true, validate: { validator(city) { return validator.isAlphanumeric(city); }, }, }, ip: { type: String, required: true, validate: { validator(ip) { return validator.isIP(ip); }, }, }, }); module.exports = model; ================================================ FILE: aws-node-rest-api-mongodb/package.json ================================================ { "name": "aws-node-restapi-mongodb", "version": "1.0.0", "description": "Serverless REST API with MongoDB using Mongoose and Bluebird", "main": "handler.js", "dependencies": { "bluebird": "^3.5.0", "mongoose": "^4.9.6", "validator": "^7.0.0" }, "author": "Quentin Homareau ", "license": "MIT" } ================================================ FILE: aws-node-rest-api-mongodb/serverless.yml ================================================ service: aws-node-rest-api-mongodb provider: name: aws runtime: nodejs12.x functions: createUser: handler: handler.createUser events: - http: path: user method: post cors: true updateUser: handler: handler.updateUser events: - http: path: user/{id} method: put cors: true deleteUser: handler: handler.deleteUser events: - http: path: user/{id} method: delete cors: true user: handler: handler.user events: - http: path: user/{id} method: get cors: true ================================================ FILE: aws-node-rest-api-typescript/.editorconfig ================================================ root = true [*] indent_style = space indent_size = 2 ================================================ FILE: aws-node-rest-api-typescript/.gitignore ================================================ # package directories node_modules jspm_packages # Serverless directories .serverless .build .nyc_output coverage package-lock.json out-logger.json .DS_Store ================================================ FILE: aws-node-rest-api-typescript/.nycrc.json ================================================ { "all": true, "report-dir": "./coverage", "extension": [".ts"], "exclude": [ "app/model/dto/**.ts", "app/model/vo/**.ts", "coverage", "tests" ] } ================================================ FILE: aws-node-rest-api-typescript/README.md ================================================ # Serverless Nodejs Rest API with TypeScript And MongoDB Atlas This is simple REST API example for AWS Lambda By Serverless framwork with TypeScript and MongoDB Atlas. ## Use Cases * REST API with typescript * MongoDB Atlas data storage * Multi-environment management under Serverless * Mocha unit tests and lambda-tester interface test * AWS lambda function log view ## Invoke the function locally ```bash serverless invoke local --function find ``` Which should result in: ```bash Serverless: Compiling with Typescript... Serverless: Using local tsconfig.json Serverless: Typescript compiled. { "statusCode": 200, "body": "{\"code\":0,\"message\":\"success\",\"data\":[{\"_id\":\"5dff21f71c9d440000a30dad\",\"createdAt\":\"2020-05-16T09:27:51.219Z\"},{\"_id\":\"5dff22ba1c9d440000a30dae\",\"createdAt\":\"2020-05-16T09:27:51.220Z\"}]}" } ``` ## Deploy ### To Test It Locally * Run ```npm install``` to install all the necessary dependencies. * Run ```npm run local``` use serverless offline to test locally. ### Deploy on AWS, simply run: ``` $ npm run deploy # or $ serverless deploy ``` The expected result should be similar to: ``` Serverless: Compiling with Typescript... Serverless: Using local tsconfig.json Serverless: Typescript compiled. Serverless: Packaging service... Serverless: Excluding development dependencies... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading artifacts... Serverless: Uploading service aws-node-rest-api-typescript.zip file to S3 (1.86 MB)... Serverless: Validating template... Serverless: Updating Stack... Serverless: Checking Stack update progress... ...................................... Serverless: Stack update finished... Service Information service: aws-node-rest-api-typescript stage: dev region: us-east-1 stack: aws-node-rest-api-typescript-dev resources: 32 api keys: None endpoints: POST - https://xxxxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/books PUT - https://xxxxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/books/{id} GET - https://xxxxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/books GET - https://xxxxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/books/{id} DELETE - https://xxxxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/books/{id} functions: create: aws-node-rest-api-typescript-dev-create update: aws-node-rest-api-typescript-dev-update find: aws-node-rest-api-typescript-dev-find findOne: aws-node-rest-api-typescript-dev-findOne deleteOne: aws-node-rest-api-typescript-dev-deleteOne layers: None Serverless: Removing old service artifacts from S3... Serverless: Run the "serverless" command to setup monitoring, troubleshooting and testing. ``` ## Usage send an HTTP request directly to the endpoint using a tool like curl ``` curl https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/books ``` ## Scaling By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 100. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit). ================================================ FILE: aws-node-rest-api-typescript/app/controller/books.ts ================================================ import { Context } from 'aws-lambda'; import { Model } from 'mongoose'; import { MessageUtil } from '../utils/message'; import { BooksService } from '../service/books'; import { CreateBookDTO } from '../model/dto/createBookDTO'; export class BooksController extends BooksService { constructor (books: Model) { super(books); } /** * Create book * @param {*} event */ async create (event: any, context?: Context) { console.log('functionName', context.functionName); const params: CreateBookDTO = JSON.parse(event.body); try { const result = await this.createBook({ name: params.name, id: params.id, }); return MessageUtil.success(result); } catch (err) { console.error(err); return MessageUtil.error(err.code, err.message); } } /** * Update a book by id * @param event */ async update (event: any) { const id: number = Number(event.pathParameters.id); const body: object = JSON.parse(event.body); try { const result = await this.updateBooks(id, body); return MessageUtil.success(result); } catch (err) { console.error(err); return MessageUtil.error(err.code, err.message); } } /** * Find book list */ async find () { try { const result = await this.findBooks(); return MessageUtil.success(result); } catch (err) { console.error(err); return MessageUtil.error(err.code, err.message); } } /** * Query book by id * @param event */ async findOne (event: any, context: Context) { // The amount of memory allocated for the function console.log('memoryLimitInMB: ', context.memoryLimitInMB); const id: number = Number(event.pathParameters.id); try { const result = await this.findOneBookById(id); return MessageUtil.success(result); } catch (err) { console.error(err); return MessageUtil.error(err.code, err.message); } } /** * Delete book by id * @param event */ async deleteOne (event: any) { const id: number = event.pathParameters.id; try { const result = await this.deleteOneBookById(id); if (result.deletedCount === 0) { return MessageUtil.error(1010, 'The data was not found! May have been deleted!'); } return MessageUtil.success(result); } catch (err) { console.error(err); return MessageUtil.error(err.code, err.message); } } } ================================================ FILE: aws-node-rest-api-typescript/app/handler.ts ================================================ import { Handler, Context } from 'aws-lambda'; import dotenv from 'dotenv'; import path from 'path'; const dotenvPath = path.join(__dirname, '../', `config/.env.${process.env.NODE_ENV}`); dotenv.config({ path: dotenvPath, }); import { books } from './model'; import { BooksController } from './controller/books'; const booksController = new BooksController(books); export const create: Handler = (event: any, context: Context) => { return booksController.create(event, context); }; export const update: Handler = (event: any) => booksController.update(event); export const find: Handler = () => booksController.find(); export const findOne: Handler = (event: any, context: Context) => { return booksController.findOne(event, context); }; export const deleteOne: Handler = (event: any) => booksController.deleteOne(event); ================================================ FILE: aws-node-rest-api-typescript/app/model/books.ts ================================================ import mongoose from 'mongoose'; export type BooksDocument = mongoose.Document & { name: string, id: number, description: string, createdAt: Date, }; const booksSchema = new mongoose.Schema({ name: String, id: { type: Number, index: true, unique: true }, description: String, createdAt: { type: Date, default: Date.now }, }); // Note: OverwriteModelError: Cannot overwrite `Books` model once compiled. error export const books = (mongoose.models.books || mongoose.model('books', booksSchema, process.env.DB_BOOKS_COLLECTION) ); ================================================ FILE: aws-node-rest-api-typescript/app/model/dto/createBookDTO.ts ================================================ export class CreateBookDTO { name: string; id: number; description?: string; } ================================================ FILE: aws-node-rest-api-typescript/app/model/index.ts ================================================ export * from './mongoose-db'; export * from './books'; ================================================ FILE: aws-node-rest-api-typescript/app/model/mongoose-db.ts ================================================ import mongoose from 'mongoose'; export default mongoose.connect(process.env.DB_URL, { dbName: process.env.DB_NAME, useUnifiedTopology: true, useNewUrlParser: true, }); ================================================ FILE: aws-node-rest-api-typescript/app/model/vo/responseVo.ts ================================================ export class ResponseBodyVO { code: number; message: string; data?: object; } export class ResponseVO { statusCode: number; body: string; } ================================================ FILE: aws-node-rest-api-typescript/app/service/books.ts ================================================ import { Model } from 'mongoose'; import { CreateBookDTO } from '../model/dto/createBookDTO'; export class BooksService { private books: Model; constructor(books: Model) { this.books = books; } /** * Create book * @param params */ protected async createBook (params: CreateBookDTO): Promise { try { const result = await this.books.create({ name: params.name, id: params.id, }); return result; } catch (err) { console.error(err); throw err; } } /** * Update a book by id * @param id * @param data */ protected updateBooks (id: number, data: object) { return this.books.findOneAndUpdate( { id }, { $set: data }, { new: true }, ); } /** * Find books */ protected findBooks () { return this.books.find(); } /** * Query book by id * @param id */ protected findOneBookById (id: number) { return this.books.findOne({ id }); } /** * Delete book by id * @param id */ protected deleteOneBookById (id: number) { return this.books.deleteOne({ id }); } } ================================================ FILE: aws-node-rest-api-typescript/app/utils/message.ts ================================================ import { ResponseVO } from '../model/vo/responseVo'; enum StatusCode { success = 200, } class Result { private statusCode: number; private code: number; private message: string; private data?: any; constructor(statusCode: number, code: number, message: string, data?: any) { this.statusCode = statusCode; this.code = code; this.message = message; this.data = data; } /** * Serverless: According to the API Gateway specs, the body content must be stringified */ bodyToString () { return { statusCode: this.statusCode, body: JSON.stringify({ code: this.code, message: this.message, data: this.data, }), }; } } export class MessageUtil { static success(data: object): ResponseVO { const result = new Result(StatusCode.success, 0, 'success', data); return result.bodyToString(); } static error(code: number = 1000, message: string) { const result = new Result(StatusCode.success, code, message); console.log(result.bodyToString()); return result.bodyToString(); } } ================================================ FILE: aws-node-rest-api-typescript/package.json ================================================ { "name": "aws-node-typescript-rest-api", "version": "1.0.0", "description": "This is simple REST API example for AWS Lambda By Serverless framwork with TypeScript and MongoDB Atlas.", "main": "index.js", "scripts": { "lint": "tslint -p tsconfig.json -c tslint.json", "local": "serverless offline", "deploy": "serverless deploy", "test": "cross-env NODE_ENV=dev mocha -r ts-node/register tests/*.test.ts --exit", "coverage": "nyc --reporter lcov npm run test" }, "pre-commit": [ "lint" ], "dependencies": { "dotenv": "^8.2.0", "mongoose": "^5.9.10" }, "devDependencies": { "@types/aws-lambda": "^8.10.51", "@types/chai": "^4.2.11", "@types/dotenv-safe": "^8.1.0", "@types/lambda-tester": "^3.6.0", "@types/mocha": "^7.0.2", "@types/mongoose": "^5.7.14", "@types/sinon": "^9.0.0", "@types/supertest": "^2.0.8", "chai": "^4.2.0", "cross-env": "^7.0.2", "istanbul": "^0.4.5", "lambda-tester": "^4.0.1", "mocha": "^7.1.2", "nyc": "^15.0.1", "serverless-offline": "^5.12.1", "serverless-plugin-typescript": "^1.1.9", "sinon": "^9.0.2", "ts-node": "^8.9.1", "tslint": "^6.1.2", "tslint-config-airbnb": "^5.11.2", "typescript": "^3.8.3" }, "repository": { "type": "git", "url": "git+https://github.com/Q-Angelo/aws-node-typescript-rest-api.git" }, "keywords": [ "Nodejs", "TypeScript", "ServerLess", "MongoDB Atlas", "AWS Lambda" ], "author": "May", "license": "ISC", "bugs": { "url": "https://github.com/Q-Angelo/aws-node-typescript-rest-api/issues" }, "homepage": "https://github.com/Q-Angelo/aws-node-typescript-rest-api#readme" } ================================================ FILE: aws-node-rest-api-typescript/serverless.template.yml ================================================ name: aws-node-rest-api-typescript org: serverlessinc description: Deploys a Node REST API service with traditional Serverless Framework and Typescript keywords: aws, serverless, faas, lambda, node, typescript repo: https://github.com/serverless/examples/aws-node-rest-api-typescript license: MIT ================================================ FILE: aws-node-rest-api-typescript/serverless.yml ================================================ service: aws-node-rest-api-typescript provider: name: aws runtime: nodejs12.x environment: NODE_ENV: dev plugins: - serverless-plugin-typescript - serverless-offline package: exclude: - config/.env.stg - config/.env.pro include: - config/.env.dev functions: create: handler: app/handler.create events: - http: path: books method: post update: handler: app/handler.update events: - http: path: books/{id} method: put find: handler: app/handler.find events: - http: path: books method: get findOne: handler: app/handler.findOne events: - http: path: books/{id} method: get deleteOne: handler: app/handler.deleteOne events: - http: path: books/{id} method: delete ================================================ FILE: aws-node-rest-api-typescript/tests/books.mock.ts ================================================ export const findOne = { "_id": "5dff58da85eb210f0aac43af", "name": "深入浅出Node.js", "id": 25768396, "createdAt": "2019-12-22T11:51:54.857Z", "__v": 0 }; export const castError = new Error('Cast to number failed for value "NaN" at path "id" for model "Books"'); export const find = [ { "_id": "5dff58da85eb210f0aac43af", "name": "深入浅出Node.js", "id": 25768396, "createdAt": "2019-12-22T11:51:54.857Z", "__v": 0 }, { "_id": "5e0188f53877986a548aa6f4", "name": "你不知道的JavaScript(上卷)", "id": 26351021, "createdAt": "2019-12-24T03:41:41.791Z", "__v": 0 } ]; export const findError = new Error('test find error'); export const create = { "_id": "5eb0023e6460b01a9461c8fc", "name": "Node.js:来一打 C++ 扩展", "id": 30247892, "createdAt": "2020-05-04T11:53:34.056Z", "__v": 0 } export const createError = new Error('E11000 duplicate key error collection: study1.books index: id_1 dup key: { id: 30247892 }'); export const update = { "_id": "5eb0023e6460b01a9461c8fc", "name": "Node.js:来一打 C++ 扩展", "id": 30247892, "createdAt": "2020-05-04T11:53:34.056Z", "__v": 0, "description": "阅读《Node.js:来一打 C++ 扩展》,相当于同时学习Chrome V8 开发、libuv 开发以及 Node.js 的原生 C++ 扩展开发知识,非常值得!" } export const deleteOne = { "n": 1, "opTime": { "ts": "6822972891668152321", "t": 29 }, "electionId": "7fffffff000000000000001d", "ok": 1, "$clusterTime": { "clusterTime": "6822972891668152321", "signature": { "hash": "ZEDvpLVNn/eA6weZEWboNr0H7o8=", "keyId": "6772591495061962755" } }, "operationTime": "6822972891668152321", "deletedCount": 1 } export const deletedCount = { "n":0, "opTime":{ "ts":"6822975382749184001", "t":29 }, "electionId":"7fffffff000000000000001d", "ok":1, "$clusterTime":{ "clusterTime":"6822975382749184001", "signature":{ "hash":"6s7HFnoM7FGe1esPR/qwh+Et9+0=", "keyId":"6772591495061962755" } }, "operationTime":"6822975382749184001", "deletedCount":0 } ================================================ FILE: aws-node-rest-api-typescript/tests/books.test.ts ================================================ import lambdaTester from 'lambda-tester'; import { expect } from 'chai'; import { findOne, find, create, update, deleteOne } from '../app/handler'; import * as booksMock from './books.mock'; import { books as BooksModel } from '../app/model/books'; import sinon from 'sinon'; describe('FindOne [GET]', () => { it('success', () => { try { const s = sinon .mock(BooksModel); s.expects('findOne') .atLeast(1) .atMost(3) .resolves(booksMock.findOne); return lambdaTester(findOne) .event({ pathParameters: { id: 25768396 } }) .expectResult((result: any) => { expect(result.statusCode).to.equal(200); const body = JSON.parse(result.body); expect(body.code).to.equal(0); s.verify(); s.restore(); }); } catch (err) { console.log(err); } }); it('error', () => { try { const s = sinon .mock(BooksModel); s.expects('findOne') .rejects(booksMock.castError); return lambdaTester(findOne) .event({ pathParameters: { id: 25768396 } }) .expectResult((result: any) => { expect(result.statusCode).to.equal(200); const body = JSON.parse(result.body); expect(body.code).to.equal(1000); s.restore(); }); } catch (err) { console.log(err); } }); }); describe('Find [GET]', () => { it('success', () => { const s = sinon .mock(BooksModel); s.expects('find') .resolves(booksMock.find); return lambdaTester(find) .event({}) .expectResult((result: any) => { expect(result.statusCode).to.equal(200); const body = JSON.parse(result.body); expect(body.code).to.equal(0); s.restore(); }); }); it('error', () => { const s = sinon .mock(BooksModel); s.expects('find').rejects(booksMock.findError); return lambdaTester(find) .event({}) .expectResult((result: any) => { expect(result.statusCode).to.equal(200); const body = JSON.parse(result.body); expect(body.code).to.equal(1000); s.restore(); }); }); }); describe('Create [POST]', () => { it('success', () => { const s = sinon .mock(BooksModel); s.expects('create').resolves(booksMock.create); return lambdaTester(create) .event({ body: JSON.stringify({ name: 'Node.js:来一打 C++ 扩展', id: 30247892, })}) .expectResult((result: any) => { expect(result.statusCode).to.equal(200); const body = JSON.parse(result.body); expect(body.code).to.equal(0); s.restore(); }); }); it('error', () => { const s = sinon .mock(BooksModel); s.expects('create').rejects(booksMock.createError); return lambdaTester(create) .event({ body: JSON.stringify({ name: 'Node.js:来一打 C++ 扩展', id: 30247892, })}) .expectResult((result: any) => { expect(result.statusCode).to.equal(200); const body = JSON.parse(result.body); expect(body.code).to.equal(1000); s.restore(); }); }); }); describe('Update [PUT]', () => { it('success', () => { const s = sinon .mock(BooksModel); s.expects('findOneAndUpdate').resolves(booksMock.update); return lambdaTester(update) .event({ pathParameters: { id: 30247892 }, body: JSON.stringify({ name: 'Node.js:来一打 C++ 扩展', description: '阅读《Node.js:来一打 C++ 扩展》,相当于同时学习Chrome V8 开发、libuv 开发以及 Node.js 的原生 C++ 扩展开发知识,非常值得!', })}) .expectResult((result: any) => { expect(result.statusCode).to.equal(200); const body = JSON.parse(result.body); expect(body.code).to.equal(0); s.restore(); }); }); it('error', () => { const s = sinon .mock(BooksModel); s.expects('findOneAndUpdate').rejects(booksMock.castError); return lambdaTester(update) .event({ pathParameters: { id: '30247892_' }, body: JSON.stringify({ name: 'Node.js:来一打 C++ 扩展', description: '阅读《Node.js:来一打 C++ 扩展》,相当于同时学习Chrome V8 开发、libuv 开发以及 Node.js 的原生 C++ 扩展开发知识,非常值得!', })}) .expectResult((result: any) => { expect(result.statusCode).to.equal(200); const body = JSON.parse(result.body); expect(body.code).to.equal(1000); s.restore(); }); }); }); describe('DeleteOne [Delete]', () => { it('success', () => { const s = sinon .mock(BooksModel); s.expects('deleteOne').resolves(booksMock.deleteOne); return lambdaTester(deleteOne) .event({ pathParameters: { id: 30247892 } }) .expectResult((result: any) => { expect(result.statusCode).to.equal(200); const body = JSON.parse(result.body); expect(body.code).to.equal(0); s.restore(); }); }); it('deletedCount === 0', () => { const s = sinon .mock(BooksModel); s.expects('deleteOne').resolves(booksMock.deletedCount); return lambdaTester(deleteOne) .event({ pathParameters: { id: 30247892 } }) .expectResult((result: any) => { expect(result.statusCode).to.equal(200); const body = JSON.parse(result.body); expect(body.code).to.equal(1010); s.restore(); }); }); it('error', () => { const s = sinon .mock(BooksModel); s.expects('deleteOne').rejects(booksMock.castError); return lambdaTester(deleteOne) .event({ pathParameters: { id: '30247892_' } }) .expectResult((result: any) => { expect(result.statusCode).to.equal(200); const body = JSON.parse(result.body); expect(body.code).to.equal(1000); s.restore(); }); }); }); ================================================ FILE: aws-node-rest-api-typescript/tsconfig.json ================================================ { "compilerOptions": { "allowSyntheticDefaultImports": true, "esModuleInterop": true, "lib": ["esnext"], "module": "commonjs", "moduleResolution": "node", "noUnusedLocals": true, "noUnusedParameters": true, "outDir": "lib", "sourceMap": true, "target": "esnext" }, "exclude": ["node_modules"] } ================================================ FILE: aws-node-rest-api-typescript/tslint.json ================================================ { "extends": ["tslint-config-airbnb"], "indent": [ true, "tabs", 2 ], "linterOptions": { "exclude": [ "tests/*books.mock.ts" ] } } ================================================ FILE: aws-node-rest-api-typescript-simple/.gitignore ================================================ # package directories node_modules jspm_packages # Serverless directories .serverless ================================================ FILE: aws-node-rest-api-typescript-simple/README.md ================================================ # Serverless Framework Node with Typescript REST API on AWS This template demonstrates how to make a simple REST API with Node.js and Typescript running on AWS Lambda and API Gateway using the Serverless Framework v1. This template does not include any kind of persistence (database). For a more advanced example check out the [aws-node-rest-api-typescript example](https://github.com/serverless/examples/tree/master/aws-node-rest-api-typescript) which has must RESTful resources and persistence using MongoDB. ## Setup Run this command to initialize a new project in a new working directory. `sls init aws-node-rest-api-typescript` ## Usage **Deploy** This example is made to work with the Serverless Framework dashboard which includes advanced features like CI/CD, monitoring, metrics, etc. ``` $ serverless login $ serverless deploy ``` To deploy without the dashboard you will need to remove `org` and `app` fields from the `serverless.yml`, and you won’t have to run `sls login` before deploying. **Invoke the function locally.** ``` serverless invoke local --function hello ``` **Invoke the function** ``` curl https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/ ``` ================================================ FILE: aws-node-rest-api-typescript-simple/handler.ts ================================================ import { Handler } from 'aws-lambda'; export const hello: Handler = (event: any) => { const response = { statusCode: 200, body: JSON.stringify( { message: 'Go Serverless v1.0! Your function executed successfully!', input: event, }, null, 2 ), }; return new Promise((resolve) => { resolve(response) }) } ================================================ FILE: aws-node-rest-api-typescript-simple/package.json ================================================ { "name": "aws-node-rest-api-typescript", "description": "", "version": "0.1.0", "dependencies": {}, "devDependencies": { "@types/aws-lambda": "^8.10.61", "serverless": "^1.78.1", "serverless-offline": "^6.5.0", "serverless-plugin-typescript": "^1.1.9", "typescript": "^3.9.7" } } ================================================ FILE: aws-node-rest-api-typescript-simple/serverless.yml ================================================ org: serverlessinc app: aws-node-rest-api-typescript service: aws-node-rest-api-typescript provider: name: aws runtime: nodejs12.x functions: hello: handler: handler.hello events: - http: path: / method: get plugins: - serverless-plugin-typescript ================================================ FILE: aws-node-rest-api-with-dynamodb/.gitignore ================================================ node_modules .serverless ================================================ FILE: aws-node-rest-api-with-dynamodb/README.md ================================================ # Serverless REST API This example demonstrates how to setup a [RESTful Web Services](https://en.wikipedia.org/wiki/Representational_state_transfer#Applied_to_web_services) allowing you to create, list, get, update and delete Todos. DynamoDB is used to store the data. This is just an example and of course you could use any data storage as a backend. ## Structure This service has a separate directory for all the todo operations. For each operation exactly one file exists e.g. `todos/delete.js`. In each of these files there is exactly one function which is directly attached to `module.exports`. The idea behind the `todos` directory is that in case you want to create a service containing multiple resources e.g. users, notes, comments you could do so in the same service. While this is certainly possible you might consider creating a separate service for each resource. It depends on the use-case and your preference. ## Use-cases - API for a Web Application - API for a Mobile Application ## Setup ```bash npm install ``` ## Deploy In order to deploy the endpoint simply run ```bash serverless deploy ``` The expected result should be similar to: ```bash Serverless: Packaging service… Serverless: Uploading CloudFormation file to S3… Serverless: Uploading service .zip file to S3… Serverless: Updating Stack… Serverless: Checking Stack update progress… Serverless: Stack update finished… Service Information service: serverless-rest-api-with-dynamodb stage: dev region: us-east-1 api keys: None endpoints: POST - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/dev/todos GET - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/dev/todos GET - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/dev/todos/{id} PUT - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/dev/todos/{id} DELETE - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/dev/todos/{id} functions: serverless-rest-api-with-dynamodb-dev-update: arn:aws:lambda:us-east-1:488110005556:function:serverless-rest-api-with-dynamodb-dev-update serverless-rest-api-with-dynamodb-dev-get: arn:aws:lambda:us-east-1:488110005556:function:serverless-rest-api-with-dynamodb-dev-get serverless-rest-api-with-dynamodb-dev-list: arn:aws:lambda:us-east-1:488110005556:function:serverless-rest-api-with-dynamodb-dev-list serverless-rest-api-with-dynamodb-dev-create: arn:aws:lambda:us-east-1:488110005556:function:serverless-rest-api-with-dynamodb-dev-create serverless-rest-api-with-dynamodb-dev-delete: arn:aws:lambda:us-east-1:488110005556:function:serverless-rest-api-with-dynamodb-dev-delete ``` ## Usage You can create, retrieve, update, or delete todos with the following commands: ### Create a Todo ```bash curl -X POST https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/todos --data '{ "text": "Learn Serverless" }' ``` Example Result: ```bash {"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":false,"updatedAt":1479138570824}% ``` ### List all Todos ```bash curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/todos ``` Example output: ```bash [{"text":"Deploy my first service","id":"ac90feaa11e6-9ede-afdfa051af86","checked":true,"updatedAt":1479139961304},{"text":"Learn Serverless","id":"206793aa11e6-9ede-afdfa051af86","createdAt":1479139943241,"checked":false,"updatedAt":1479139943241}]% ``` ### Get one Todo ```bash # Replace the part with a real id from your todos table curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/todos/ ``` Example Result: ```bash {"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":false,"updatedAt":1479138570824}% ``` ### Update a Todo ```bash # Replace the part with a real id from your todos table curl -X PUT https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/todos/ --data '{ "text": "Learn Serverless", "checked": true }' ``` Example Result: ```bash {"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":true,"updatedAt":1479138570824}% ``` ### Delete a Todo ```bash # Replace the part with a real id from your todos table curl -X DELETE https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/todos/ ``` No output ## Scaling ### AWS Lambda By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 100. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit). ### DynamoDB When you create a table, you specify how much provisioned throughput capacity you want to reserve for reads and writes. DynamoDB will reserve the necessary resources to meet your throughput needs while ensuring consistent, low-latency performance. You can change the provisioned throughput and increasing or decreasing capacity as needed. This is can be done via settings in the `serverless.yml`. ```yaml ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 ``` In case you expect a lot of traffic fluctuation we recommend to checkout this guide on how to auto scale DynamoDB [https://aws.amazon.com/blogs/aws/auto-scale-dynamodb-with-dynamic-dynamodb/](https://aws.amazon.com/blogs/aws/auto-scale-dynamodb-with-dynamic-dynamodb/) ================================================ FILE: aws-node-rest-api-with-dynamodb/package.json ================================================ { "name": "aws-rest-with-dynamodb", "version": "1.0.0", "description": "Serverless CRUD service exposing a REST HTTP interface", "author": "", "license": "MIT", "dependencies": { "uuid": "^2.0.3" } } ================================================ FILE: aws-node-rest-api-with-dynamodb/serverless.yml ================================================ service: serverless-rest-api-with-dynamodb frameworkVersion: ">=1.1.0 <2.0.0" provider: name: aws runtime: nodejs10.x environment: DYNAMODB_TABLE: ${self:service}-${opt:stage, self:provider.stage} iam: role: statements: - Effect: Allow Action: - dynamodb:Query - dynamodb:Scan - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}" functions: create: handler: todos/create.create events: - http: path: todos method: post cors: true list: handler: todos/list.list events: - http: path: todos method: get cors: true get: handler: todos/get.get events: - http: path: todos/{id} method: get cors: true update: handler: todos/update.update events: - http: path: todos/{id} method: put cors: true delete: handler: todos/delete.delete events: - http: path: todos/{id} method: delete cors: true resources: Resources: TodosDynamoDbTable: Type: 'AWS::DynamoDB::Table' DeletionPolicy: Retain Properties: AttributeDefinitions: - AttributeName: id AttributeType: S KeySchema: - AttributeName: id KeyType: HASH ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 TableName: ${self:provider.environment.DYNAMODB_TABLE} ================================================ FILE: aws-node-rest-api-with-dynamodb/todos/create.js ================================================ 'use strict'; const uuid = require('uuid'); const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies const dynamoDb = new AWS.DynamoDB.DocumentClient(); module.exports.create = (event, context, callback) => { const timestamp = new Date().getTime(); const data = JSON.parse(event.body); if (typeof data.text !== 'string') { console.error('Validation Failed'); callback(null, { statusCode: 400, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t create the todo item.', }); return; } const params = { TableName: process.env.DYNAMODB_TABLE, Item: { id: uuid.v1(), text: data.text, checked: false, createdAt: timestamp, updatedAt: timestamp, }, }; // write the todo to the database dynamoDb.put(params, (error) => { // handle potential errors if (error) { console.error(error); callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t create the todo item.', }); return; } // create a response const response = { statusCode: 200, body: JSON.stringify(params.Item), }; callback(null, response); }); }; ================================================ FILE: aws-node-rest-api-with-dynamodb/todos/delete.js ================================================ 'use strict'; const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies const dynamoDb = new AWS.DynamoDB.DocumentClient(); module.exports.delete = (event, context, callback) => { const params = { TableName: process.env.DYNAMODB_TABLE, Key: { id: event.pathParameters.id, }, }; // delete the todo from the database dynamoDb.delete(params, (error) => { // handle potential errors if (error) { console.error(error); callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t remove the todo item.', }); return; } // create a response const response = { statusCode: 200, body: JSON.stringify({}), }; callback(null, response); }); }; ================================================ FILE: aws-node-rest-api-with-dynamodb/todos/get.js ================================================ 'use strict'; const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies const dynamoDb = new AWS.DynamoDB.DocumentClient(); module.exports.get = (event, context, callback) => { const params = { TableName: process.env.DYNAMODB_TABLE, Key: { id: event.pathParameters.id, }, }; // fetch todo from the database dynamoDb.get(params, (error, result) => { // handle potential errors if (error) { console.error(error); callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t fetch the todo item.', }); return; } // create a response const response = { statusCode: 200, body: JSON.stringify(result.Item), }; callback(null, response); }); }; ================================================ FILE: aws-node-rest-api-with-dynamodb/todos/list.js ================================================ 'use strict'; const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies const dynamoDb = new AWS.DynamoDB.DocumentClient(); const params = { TableName: process.env.DYNAMODB_TABLE, }; module.exports.list = (event, context, callback) => { // fetch all todos from the database dynamoDb.scan(params, (error, result) => { // handle potential errors if (error) { console.error(error); callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t fetch the todos.', }); return; } // create a response const response = { statusCode: 200, body: JSON.stringify(result.Items), }; callback(null, response); }); }; ================================================ FILE: aws-node-rest-api-with-dynamodb/todos/update.js ================================================ 'use strict'; const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies const dynamoDb = new AWS.DynamoDB.DocumentClient(); module.exports.update = (event, context, callback) => { const timestamp = new Date().getTime(); const data = JSON.parse(event.body); // validation if (typeof data.text !== 'string' || typeof data.checked !== 'boolean') { console.error('Validation Failed'); callback(null, { statusCode: 400, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t update the todo item.', }); return; } const params = { TableName: process.env.DYNAMODB_TABLE, Key: { id: event.pathParameters.id, }, ExpressionAttributeNames: { '#todo_text': 'text', }, ExpressionAttributeValues: { ':text': data.text, ':checked': data.checked, ':updatedAt': timestamp, }, UpdateExpression: 'SET #todo_text = :text, checked = :checked, updatedAt = :updatedAt', ReturnValues: 'ALL_NEW', }; // update the todo in the database dynamoDb.update(params, (error, result) => { // handle potential errors if (error) { console.error(error); callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t fetch the todo item.', }); return; } // create a response const response = { statusCode: 200, body: JSON.stringify(result.Attributes), }; callback(null, response); }); }; ================================================ FILE: aws-node-rest-api-with-dynamodb-and-offline/.gitignore ================================================ .serverless node_modules ================================================ FILE: aws-node-rest-api-with-dynamodb-and-offline/README.md ================================================ # Serverless REST API with DynamoDB and offline support This example demonstrates how to run a service locally, using the [serverless-offline](https://github.com/dherault/serverless-offline) plugin. It provides a REST API to manage Todos stored in a DynamoDB, similar to the [aws-node-rest-api-with-dynamodb](https://github.com/serverless/examples/tree/master/aws-node-rest-api-with-dynamodb) example. A local DynamoDB instance is provided by the [serverless-dynamodb-local](https://github.com/99xt/serverless-dynamodb-local) plugin. ## Use-case Test your service locally, without having to deploy it first. ## Setup ```bash npm install serverless dynamodb install (or to use a persistent docker dynamodb instead, open a new terminal: cd ./dynamodb && docker-compose up -d) serverless offline start serverless dynamodb migrate (this imports schema) ``` ## Run service offline ```bash serverless offline start ``` ## Usage You can create, retrieve, update, or delete todos with the following commands: ### Create a Todo ```bash curl -X POST -H "Content-Type:application/json" http://localhost:3000/todos --data '{ "text": "Learn Serverless" }' ``` Example Result: ```bash {"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":false,"updatedAt":1479138570824}% ``` ### List all Todos ```bash curl -H "Content-Type:application/json" http://localhost:3000/todos ``` Example output: ```bash [{"text":"Deploy my first service","id":"ac90feaa11e6-9ede-afdfa051af86","checked":true,"updatedAt":1479139961304},{"text":"Learn Serverless","id":"206793aa11e6-9ede-afdfa051af86","createdAt":1479139943241,"checked":false,"updatedAt":1479139943241}]% ``` ### Get one Todo ```bash # Replace the part with a real id from your todos table curl -H "Content-Type:application/json" http://localhost:3000/todos/ ``` Example Result: ```bash {"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":false,"updatedAt":1479138570824}% ``` ### Update a Todo ```bash # Replace the part with a real id from your todos table curl -X PUT -H "Content-Type:application/json" http://localhost:3000/todos/ --data '{ "text": "Learn Serverless", "checked": true }' ``` Example Result: ```bash {"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":true,"updatedAt":1479138570824}% ``` ### Delete a Todo ```bash # Replace the part with a real id from your todos table curl -X DELETE -H "Content-Type:application/json" http://localhost:3000/todos/ ``` No output ================================================ FILE: aws-node-rest-api-with-dynamodb-and-offline/dynamodb/Dockerfile ================================================ FROM amazon/dynamodb-local WORKDIR /home/dynamodblocal RUN mkdir ./db && chown -R 1000 ./db CMD ["-jar", "DynamoDBLocal.jar", "-dbPath", "./db", "-sharedDb"] VOLUME ["./db"] ================================================ FILE: aws-node-rest-api-with-dynamodb-and-offline/dynamodb/docker-compose.yml ================================================ version: "3" services: dynamodb: build: context: . dockerfile: Dockerfile ports: - 8000:8000 volumes: - aws-rest-api-dynamodb:/home/dynamodblocal/db volumes: aws-rest-api-dynamodb: driver: local ================================================ FILE: aws-node-rest-api-with-dynamodb-and-offline/offline/migrations/todos.json ================================================ { "Table": { "TableName": "serverless-rest-api-with-dynamodb-dev", "KeySchema": [ { "AttributeName": "id", "KeyType": "HASH" } ], "AttributeDefinitions": [ { "AttributeName": "id", "AttributeType": "S" } ], "ProvisionedThroughput": { "ReadCapacityUnits": 1, "WriteCapacityUnits": 1 } } } ================================================ FILE: aws-node-rest-api-with-dynamodb-and-offline/package.json ================================================ { "name": "aws-rest-api-offline", "version": "1.0.0", "description": "Serverless REST API with DynamoDB and offline support", "repository": "", "author": "Christoph Gysin ", "license": "MIT", "dependencies": { "uuid": "^2.0.3" }, "devDependencies": { "aws-sdk": "^2.12.0", "serverless-dynamodb-local": "^0.2.18", "serverless-offline": "^6.8.0" } } ================================================ FILE: aws-node-rest-api-with-dynamodb-and-offline/serverless.yml ================================================ service: serverless-rest-api-with-dynamodb frameworkVersion: ">=2.24.0" plugins: - serverless-dynamodb-local - serverless-offline custom: dynamodb: stages: - dev start: port: 8000 inMemory: true migrate: true # Comment if you don't have a DynamoDB running locally noStart: true migration: dir: offline/migrations provider: name: aws runtime: nodejs12.x environment: DYNAMODB_TABLE: ${self:service}-${opt:stage, self:provider.stage} iam: role: statements: - Effect: Allow Action: - dynamodb:Query - dynamodb:Scan - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}" functions: create: handler: todos/create.create events: - http: path: todos method: post cors: true list: handler: todos/list.list events: - http: path: todos method: get cors: true get: handler: todos/get.get events: - http: path: todos/{id} method: get cors: true update: handler: todos/update.update events: - http: path: todos/{id} method: put cors: true delete: handler: todos/delete.delete events: - http: path: todos/{id} method: delete cors: true resources: Resources: TodosDynamoDbTable: Type: 'AWS::DynamoDB::Table' DeletionPolicy: Retain Properties: AttributeDefinitions: - AttributeName: id AttributeType: S KeySchema: - AttributeName: id KeyType: HASH BillingMode: PAY_PER_REQUEST TableName: ${self:provider.environment.DYNAMODB_TABLE} ================================================ FILE: aws-node-rest-api-with-dynamodb-and-offline/todos/create.js ================================================ 'use strict'; const uuid = require('uuid'); const dynamodb = require('./dynamodb'); module.exports.create = (event, context, callback) => { const timestamp = new Date().getTime(); const data = JSON.parse(event.body); if (typeof data.text !== 'string') { console.error('Validation Failed'); callback(null, { statusCode: 400, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t create the todo item.', }); return; } const params = { TableName: process.env.DYNAMODB_TABLE, Item: { id: uuid.v1(), text: data.text, checked: false, createdAt: timestamp, updatedAt: timestamp, }, }; // write the todo to the database dynamodb.put(params, (error) => { // handle potential errors if (error) { console.error(error); callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t create the todo item.', }); return; } // create a response const response = { statusCode: 200, body: JSON.stringify(params.Item), }; callback(null, response); }); }; ================================================ FILE: aws-node-rest-api-with-dynamodb-and-offline/todos/delete.js ================================================ 'use strict'; const dynamodb = require('./dynamodb'); module.exports.delete = (event, context, callback) => { const params = { TableName: process.env.DYNAMODB_TABLE, Key: { id: event.pathParameters.id, }, }; // delete the todo from the database dynamodb.delete(params, (error) => { // handle potential errors if (error) { console.error(error); callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t remove the todo item.', }); return; } // create a response const response = { statusCode: 200, body: JSON.stringify({}), }; callback(null, response); }); }; ================================================ FILE: aws-node-rest-api-with-dynamodb-and-offline/todos/dynamodb.js ================================================ 'use strict'; const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies let options = {}; // connect to local DB if running offline if (process.env.IS_OFFLINE) { options = { region: 'localhost', endpoint: 'http://localhost:8000', }; } const client = new AWS.DynamoDB.DocumentClient(options); module.exports = client; ================================================ FILE: aws-node-rest-api-with-dynamodb-and-offline/todos/get.js ================================================ 'use strict'; const dynamodb = require('./dynamodb'); module.exports.get = (event, context, callback) => { const params = { TableName: process.env.DYNAMODB_TABLE, Key: { id: event.pathParameters.id, }, }; // fetch todo from the database dynamodb.get(params, (error, result) => { // handle potential errors if (error) { console.error(error); callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t fetch the todo item.', }); return; } // create a response const response = { statusCode: 200, body: JSON.stringify(result.Item), }; callback(null, response); }); }; ================================================ FILE: aws-node-rest-api-with-dynamodb-and-offline/todos/list.js ================================================ 'use strict'; const dynamodb = require('./dynamodb'); module.exports.list = (event, context, callback) => { const params = { TableName: process.env.DYNAMODB_TABLE, }; // fetch all todos from the database dynamodb.scan(params, (error, result) => { // handle potential errors if (error) { console.error(error); callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t fetch the todo item.', }); return; } // create a response const response = { statusCode: 200, body: JSON.stringify(result.Items), }; callback(null, response); }); }; ================================================ FILE: aws-node-rest-api-with-dynamodb-and-offline/todos/update.js ================================================ 'use strict'; const dynamodb = require('./dynamodb'); module.exports.update = (event, context, callback) => { const timestamp = new Date().getTime(); const data = JSON.parse(event.body); // validation if (typeof data.text !== 'string' || typeof data.checked !== 'boolean') { console.error('Validation Failed'); callback(null, { statusCode: 400, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t update the todo item.', }); return; } const params = { TableName: process.env.DYNAMODB_TABLE, Key: { id: event.pathParameters.id, }, ExpressionAttributeNames: { '#todo_text': 'text', }, ExpressionAttributeValues: { ':text': data.text, ':checked': data.checked, ':updatedAt': timestamp, }, UpdateExpression: 'SET #todo_text = :text, checked = :checked, updatedAt = :updatedAt', ReturnValues: 'ALL_NEW', }; // update the todo in the database dynamodb.update(params, (error, result) => { // handle potential errors if (error) { console.error(error); callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t update the todo item.', }); return; } // create a response const response = { statusCode: 200, body: JSON.stringify(result.Attributes), }; callback(null, response); }); }; ================================================ FILE: aws-node-s3-file-replicator/.gitignore ================================================ node_modules .serverless ================================================ FILE: aws-node-s3-file-replicator/README.md ================================================ # AWS S3 File Replicator This example creates 2 AWS S3 buckets and copies files in one bucket to the other. It's written in Node.js Unless you use the existing S3 bucket plugin, CloudFormation won't let you use an existing AWS S3 bucket. In Serverless Framework, when you have `function` `s3` `events`, they will automatically create a new AWS S3 bucket, if it doesn't exist already. This is where the inputs bucket comes from. The outputs bucket is created in the `resources` section. Simply upload a file to the inputs bucket (e.g. using the AWS S3 console) and see it be instantly transferred to the outputs bucket. ================================================ FILE: aws-node-s3-file-replicator/handler.js ================================================ const aws = require('aws-sdk') const s3 = new aws.S3() const path = require('path') const outputBucket = process.env.OUTPUT_BUCKET exports.replicate = function main(event, context) { // Fail on mising data if (!outputBucket) { context.fail('Error: Environment variable OUTPUT_BUCKET missing') return } if (event.Records === null) { context.fail('Error: Event has no records.') return } let tasks = [] for (let i = 0; i < event.Records.length; i++) { tasks.push(replicatePromise(event.Records[i], outputBucket)) } Promise.all(tasks) .then(() => { context.succeed() }) .catch(() => { context.fail() }) } function replicatePromise(record, destBucket) { return new Promise((resolve, reject) => { // The source bucket and source key are part of the event data var srcBucket = record.s3.bucket.name var srcKey = decodeURIComponent(record.s3.object.key.replace(/\+/g, " ")) // Modify destKey if an alternate copy location is preferred var destKey = srcKey var msg = 'copying ' + srcBucket + ':' + srcKey + ' to ' + destBucket + ':' + destKey console.log('Attempting: ' + msg) s3.copyObject({ Bucket: destBucket, Key: destKey, CopySource: encodeURIComponent(srcBucket + '/' + srcKey), MetadataDirective: 'COPY' }, (err, data) => { if (err) { console.log('Error:' + msg) console.log(err, err.stack) // an error occurred return reject('Error:' + msg) } else { console.log('Success: ' + msg) return resolve('Success: ' + msg) } }) }) } ================================================ FILE: aws-node-s3-file-replicator/package.json ================================================ { "name": "aws-fetch-file-and-store-in-s3", "description": "Fetch an image from remote source (URL) and then upload the image to a S3 bucket.", "version": "1.0.0", "author": "Bozhao Yu", "license": "MIT", "dependencies": {}, "devDependencies": { "aws-sdk": "^2.467.0", "serverless-lift": "^1.1.0" } } ================================================ FILE: aws-node-s3-file-replicator/serverless.yml ================================================ service: replicator plugins: - serverless-lift constructs: inputBucket: type: storage outputBucket: type: storage provider: name: aws runtime: nodejs12.x stage: dev region: us-east-1 lambdaHashingVersion: 20201221 functions: replicate: handler: handler.replicate environment: OUTPUT_BUCKET: ${construct:outputBucket.bucketName} events: - s3: bucket: ${construct:inputBucket.bucketName} existing: true ================================================ FILE: aws-node-scheduled-cron/.gitignore ================================================ node_modules .serverless ================================================ FILE: aws-node-scheduled-cron/README.md ================================================ # Serverless Framework Node Scheduled Cron on AWS This template demonstrates how to develop and deploy a simple cron-like service running on AWS Lambda using the Serverless Framework. This examples defines a single function, `rateHandler` which is triggered by an event of `schedule` type at a rate of 1 per minute. For detailed information about `schedule` event, please refer to corresponding section of Serverless [docs](https://serverless.com/framework/docs/providers/aws/events/schedule/). ## Usage ### Deployment In order to deploy the example, you need to run the following command: ``` serverless deploy ``` After running deploy, you should see output similar to: ``` Deploying "aws-node-scheduled-cron" to stage "dev" (us-east-1) ✔ Service deployed to stack aws-node-scheduled-cron-dev (151s) functions: rateHandler: aws-node-scheduled-cron-dev-rateHandler (2.3 kB) ``` There is no additional step required. Your defined schedules becomes active right away after deployment. ### Local development The easiest way to develop and test your function is to use the `dev` command: ``` serverless dev ``` This will start a local emulator of AWS Lambda and tunnel your requests to and from AWS Lambda, allowing you to interact with your function as if it were running in the cloud. Now you can invoke the function as before, but this time the function will be executed locally. Now you can develop your function locally, invoke it, and see the results immediately without having to re-deploy. When you are done developing, don't forget to run `serverless deploy` to deploy the function to the cloud. ================================================ FILE: aws-node-scheduled-cron/handler.js ================================================ exports.run = async () => { const time = new Date(); console.log(`Your cron function ran at ${time}`); }; ================================================ FILE: aws-node-scheduled-cron/serverless.yml ================================================ service: aws-node-scheduled-cron frameworkVersion: "4" provider: name: aws runtime: nodejs20.x functions: rateHandler: handler: handler.run events: - schedule: rate(1 minute) ================================================ FILE: aws-node-scheduled-weather/.gitignore ================================================ node_modules .serverless ================================================ FILE: aws-node-scheduled-weather/README.md ================================================ # AWS Node Scheduled Weather Example This is an example of creating a function that runs as a cron job using the serverless `schedule` event. It retrieves weather information at 10am (UTC) and emails it to a predefined recipient. For more information on `schedule` event check out the Serverless docs on [schedule](https://serverless.com/framework/docs/providers/aws/events/schedule/). ## Cron syntax ```pseudo cron(Minutes Hours Day-of-month Month Day-of-week Year) ``` All fields are required and time zone is UTC only. | Field | Values | Wildcards | | ------------- |:--------------:|:-------------:| | Minutes | 0-59 | , - * / | | Hours | 0-23 | , - * / | | Day-of-month | 1-31 | , - * ? / L W | | Month | 1-12 or JAN-DEC| , - * / | | Day-of-week | 1-7 or SUN-SAT | , - * ? / L # | | Year | 192199 | , - * / | Read the [AWS cron expression syntax](http://docs.aws.amazon.com/lambda/latest/dg/tutorial-scheduled-events-schedule-expressions.html) docs for more info on how to setup cron ## Setup ### DarkSky Please visit https://darksky.net/dev/ to register for a free API token. ### Postmark Please visit https://postmarkapp.com to register for a free Postmark account. ### Configuration Upon setting up access to both external services, you'll be required to update the environment variables in `serverless.yml`: ``` environment: RECIPIENT: tom@carrotcreative.com DARK_SKY_API_KEY: abc123 POSTMARK_API_KEY: abc123 POSTMARK_SENDER: devops@carrotcreative.com LATITUDE: 40.702637 LONGITUDE: -73.989406 ``` ## Deploy In order to deploy the you endpoint simply run ```bash serverless deploy ``` The expected result should be similar to: ```bash Serverless: Packaging service... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading service .zip file to S3 (1.87 MB)... Serverless: Updating Stack... Serverless: Checking Stack update progress... ........... Serverless: Stack update finished... Serverless: Removing old service versions... Service Information service: scheduled-weather-example stage: dev region: us-east-1 api keys: None endpoints: None functions: scheduled-weather-example-dev-weather: arn:aws:lambda:us-east-1:219106525755:function:scheduled-weather-example-dev-weather ``` There is no additional step required. Your defined schedule becomes active right away after deployment. ## Usage To test your function remotely: ```bash sls invoke -f weather ``` The expected result should be similar to: ```json { "success": true } ``` ## Additonal Resources For more information on running cron with Serverless check out the [Tutorial: Serverless Scheduled Tasks](https://parall.ax/blog/view/3202/tutorial-serverless-scheduled-tasks) by Parallax. ================================================ FILE: aws-node-scheduled-weather/handler.js ================================================ 'use strict'; const getForecast = require('./lib/forecast'); const sendEmail = require('./lib/email'); const latitude = process.env.LATITUDE; const longitude = process.env.LONGITUDE; const emailRecpient = process.env.RECIPIENT; const emailSubject = 'Current Weather'; module.exports.run = (event, context, callback) => { getForecast(latitude, longitude) .then((forecast) => { // eslint-disable-line arrow-body-style return sendEmail(emailRecpient, emailSubject, forecast); }) .then(() => { callback(null, { success: true }); }) .catch((error) => { callback(error, { success: false }); }); }; ================================================ FILE: aws-node-scheduled-weather/lib/email.js ================================================ 'use strict'; const Postmark = require('postmark'); const client = new Postmark.Client(process.env.POSTMARK_API_KEY); module.exports = (to, subject, body) => { const options = { From: process.env.POSTMARK_SENDER, To: to, Subject: subject, TextBody: JSON.stringify(body), }; return new Promise((resolve, reject) => { client.sendEmail(options, (error, result) => { if (error) { reject(error); } else { resolve(result); } }); }); }; ================================================ FILE: aws-node-scheduled-weather/lib/forecast.js ================================================ 'use strict'; const DarkSky = require('forecast.io'); const client = new DarkSky({ APIKey: process.env.DARK_SKY_API_KEY, }); module.exports = (latitude, longitude) => { const options = { exclude: 'minutely,hourly,daily,flags,alerts', }; return new Promise((resolve, reject) => { client.get(latitude, longitude, options, (error, data) => { if (error) { reject(error); } else { resolve(data); } }); }); }; ================================================ FILE: aws-node-scheduled-weather/package.json ================================================ { "name": "aws-scheduled-weather", "version": "1.0.0", "description": "Example of creating a function that runs as a cron job using the serverless `schedule` event through pulling weather and sending an email daily.", "author": "Tom Milewski ", "license": "MIT", "dependencies": { "forecast.io": "0.0.11", "postmark": "^1.3.1" } } ================================================ FILE: aws-node-scheduled-weather/serverless.yml ================================================ service: scheduled-weather-example frameworkVersion: ">=1.1.0 <2.0.0" provider: name: aws runtime: nodejs12.x environment: RECIPIENT: tom@carrotcreative.com DARK_SKY_API_KEY: abc123 POSTMARK_API_KEY: abc123 POSTMARK_SENDER: devops@carrotcreative.com LATITUDE: 40.702637 LONGITUDE: -73.989406 functions: weather: handler: handler.run memorySize: 128 timeout: 5 events: # 10am UTC, daily - schedule: cron(0 10 * * ? *) ================================================ FILE: aws-node-serve-dynamic-html-via-http-endpoint/.gitignore ================================================ node_modules .serverless ================================================ FILE: aws-node-serve-dynamic-html-via-http-endpoint/README.md ================================================ # Serving Dynamic HTML via API Gateway Example This example illustrates how to hookup an API Gateway endpoint to a Lambda function to render HTML on a `GET` request. ## Use-cases - Landing pages for marketing activities - Single use dynamic webpages ## How it works Instead of returning the default `json` from a request, you can display custom dynamic HTML by setting the `Content-Type` header to `text/html`. ```js const response = { statusCode: 200, headers: { 'Content-Type': 'text/html', }, body: html, }; // callback will send HTML back callback(null, response); ``` ## Deploy In order to deploy the endpoint simply run ```bash serverless deploy ``` The expected result should be similar to: ```bash Serverless: Creating Stack... Serverless: Checking Stack create progress... ..... Serverless: Stack create finished... Serverless: Packaging service... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading service .zip file to S3 (1.01 KB)... Serverless: Updating Stack... Serverless: Checking Stack update progress... ........................... Serverless: Stack update finished... Service Information service: serve-dynamic-html-via-http-endpoint stage: dev region: us-east-1 api keys: None endpoints: GET - https://nzkl1kas89.execute-api.us-east-1.amazonaws.com/dev/landing-page functions: serve-dynamic-html-via-http-endpoint-dev-landingPage: arn:aws:lambda:us-east-1:377024778620:function:serve-dynamic-html-via-http-endpoint-dev-landingPage ``` ## Usage You can now send an HTTP request directly to the endpoint using a tool like curl ```bash curl https://nzkl1kas89.execute-api.us-east-1.amazonaws.com/dev/landing-page?name=Nik%20Graf ``` The expected result should be similar to: ```bash

Landing Page

Hey Nik Graf!

``` Of course you can visit the URL in your browser and this is how it should look like: ![Screenshot without a name](https://cloud.githubusercontent.com/assets/223045/20668061/12c6db9a-b56d-11e6-911c-8396d545471a.png) To greet a specific person, provide the query parameter with the name of that person e.g. `?name=Nik%20Graf`. The response should now contain the provided name: ![Screenshot with a name](https://cloud.githubusercontent.com/assets/223045/20668055/0758b4cc-b56d-11e6-80ce-3e137151311f.png) ## Scaling By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 100. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit). ================================================ FILE: aws-node-serve-dynamic-html-via-http-endpoint/handler.js ================================================ 'use strict'; module.exports.landingPage = (event, context, callback) => { let dynamicHtml = '

Hey Unknown!

'; // check for GET params and use if available if (event.queryStringParameters && event.queryStringParameters.name) { dynamicHtml = `

Hey ${event.queryStringParameters.name}!

`; } const html = `

Landing Page

${dynamicHtml} `; const response = { statusCode: 200, headers: { 'Content-Type': 'text/html', }, body: html, }; // callback is sending HTML back callback(null, response); }; ================================================ FILE: aws-node-serve-dynamic-html-via-http-endpoint/package.json ================================================ { "name": "aws-serve-dynamic-html-via-http-endpoint", "version": "1.0.0", "description": "Hookup an AWS API Gateway endpoint to a Lambda function to render HTML on a `GET` request", "author": "", "license": "MIT" } ================================================ FILE: aws-node-serve-dynamic-html-via-http-endpoint/serverless.yml ================================================ # Serving HTML through API Gateway for AWS Lambda service: serve-dynamic-html-via-http-endpoint frameworkVersion: ">=1.1.0 <2.0.0" provider: name: aws runtime: nodejs12.x functions: landingPage: handler: handler.landingPage events: - http: method: get path: landing-page ================================================ FILE: aws-node-serverless-gong/README.md ================================================ # The Serverless Gong! 🔔 A serverless gong with GitHub and Slack webhooks - made for the [No Server November Challenge](https://serverless.com/blog/no-server-november-challenge/). When a selected repository in GitHub has a release event, a chosen Slack channel is messaged with a gong! Your final result will look like this: ![screenshot](https://www.stackery.io/blog/assets/images/posts/serverless-gong/gong6.png) ## Instructions * Read the [Serverless Webhooks Tutorial](https://docs.stackery.io/docs/tutorials/serverless-webhooks/) to get started * Read the [blog post on the Serverless Gong](https://www.stackery.io/blog/serverless-gong/) for more on this project and detailed instructions with screenshots ## Setup #### Deploy this to your AWS account using Serverless Framework If you have the Serverless CLI set up, you can simply enter `serverless deploy` to deploy! #### Deploy this to your AWS account using Stackery You can create and deploy this application to your own AWS account using the following two Stackery CLI commands: `stackery create` will initialize a new repo in your GitHub account, initializing it with the contents of the referenced template repository. ``` stackery create --stack-name 'serverless-gong' \ --git-provider 'github' \ --template-git-url 'https://github.com/stackery/serverless-gong' ``` `stackery deploy` will deploy the newly created stack into your AWS account. ``` stackery deploy --stack-name 'serverless-gong' \ --env-name 'development' \ --git-ref 'master' ``` ================================================ FILE: aws-node-serverless-gong/handler.js ================================================ const crypto = require('crypto'); const Slack = require('slack-node'); // validate your payload from GitHub function signRequestBody(key, body) { return `sha1=${crypto.createHmac('sha1', key).update(body, 'utf-8').digest('hex')}`; } // webhook handler function exports.gongHandler = async (event) => { // get the GitHub secret from the environment variables const token = process.env.GITHUB_WEBHOOK_SECRET; const calculatedSig = signRequestBody(token, event.body); let errMsg; // get the remaining variables from the GitHub event const headers = event.headers; const sig = headers['X-Hub-Signature']; const githubEvent = headers['X-GitHub-Event']; const body = JSON.parse(event.body); // get repo variables const { repository, release } = body; const repo = repository.full_name; const url = repository.url; // set variables for a release event let releaseVersion, releaseUrl, author = null; if (githubEvent === 'release') { releaseVersion = release.tag_name; releaseUrl = release.html_url; author = release.author.login; } // check that a GitHub webhook secret variable exists, if not, return an error if (typeof token !== 'string') { errMsg = 'Must provide a \'GITHUB_WEBHOOK_SECRET\' env variable'; return { statusCode: 401, headers: { 'Content-Type': 'text/plain' }, body: errMsg, }; } // check validity of GitHub token if (sig !== calculatedSig) { errMsg = 'X-Hub-Signature incorrect. Github webhook token doesn\'t match'; return { statusCode: 401, headers: { 'Content-Type': 'text/plain' }, body: errMsg, }; } // if the event is a 'release' event, gong the Slack channel! const webhookUri = process.env.SLACK_WEBHOOK_URL; const slack = new Slack(); slack.setWebhook(webhookUri); // send slack message if (githubEvent === 'release') { slack.webhook({ channel: '#gong-test', // your desired channel here username: 'gongbot', // be creative! icon_emoji: ':gong:', // because Slack is for emojis // customize your message below text: `It's time to celebrate! ${author} pushed release version ${releaseVersion}. See it here: ${releaseUrl}!\n:gong: https://youtu.be/8nBOF5sJrSE?t=11`, }, function (err, response) { console.log(response); if (err) { console.log('Something went wrong'); console.log(err); } }); } // (optional) print some messages to the CloudWatch console (for testing) console.log('---------------------------------'); console.log(`\nGithub-Event: "${githubEvent}" on this repo: "${repo}" at the url: ${url}.`); console.log(event.body); console.log('---------------------------------'); // return a 200 response if the GitHub tokens match const response = { statusCode: 200, body: JSON.stringify({ input: event, }), }; return response; }; ================================================ FILE: aws-node-serverless-gong/node ================================================ ================================================ FILE: aws-node-serverless-gong/package.json ================================================ { "name": "aws-node-serverless-gong", "version": "1.0.0", "description": "A simple serverless gong using GitHub webhooks and a Slack app", "author": "Anna Spysz", "license": "MIT", "dependencies": { "aws-sdk": "~2", "slack-node": "0.1.8" } } ================================================ FILE: aws-node-serverless-gong/serverless-examples@0.0.0 ================================================ ================================================ FILE: aws-node-serverless-gong/serverless.yml ================================================ service: serverless-gong frameworkVersion: '>=1.4.0 <2.0.0' provider: name: aws runtime: nodejs12.x functions: handleGong: handler: handler.gongHandler description: Fn::Sub: - 'Stackery Stack #{StackeryStackTagName} Environment #{StackeryEnvironmentTagName} Function #{ResourceName}' - ResourceName: handleGong events: - http: path: /webhook method: POST environment: GITHUB_WEBHOOK_SECRET: Ref: StackeryEnvConfiggithubSecretAsString SLACK_WEBHOOK_URL: Ref: StackeryEnvConfigslackWebhookURLAsString resources: Parameters: StackeryStackTagName: Type: String Description: Stack Name (injected by Stackery at deployment time) Default: serverless-gong StackeryEnvironmentTagName: Type: String Description: Environment Name (injected by Stackery at deployment time) Default: dev StackeryEnvConfiggithubSecretAsString: Type: AWS::SSM::Parameter::Value Default: /Stackery/Environments//Config/githubSecret StackeryEnvConfigslackWebhookURLAsString: Type: AWS::SSM::Parameter::Value Default: /Stackery/Environments//Config/slackWebhookURL Metadata: StackeryEnvConfigParameters: StackeryEnvConfiggithubSecretAsString: githubSecret StackeryEnvConfigslackWebhookURLAsString: slackWebhookURL plugins: - serverless-cf-vars ================================================ FILE: aws-node-ses-receive-email-body/.gitignore ================================================ node_modules .serverless ================================================ FILE: aws-node-ses-receive-email-body/README.md ================================================ # Receive an email, store in S3 bucket, trigger a lambda function This example shows how to receive an email with SES, store the email including the body on S3 and have S3 trigger a lambda function. ## Use-cases - Postprocess of email body. ## Setup - [Create a SES verified Domain](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/receiving-email-getting-started-verify.html) but do not setup the "Rule Set" - Edit `serverless.yml` and choose a unique S3 bucket name but follow the [normalizing Rules](https://serverless.com/framework/docs/providers/aws/guide/resources#aws-cloudformation-resource-reference) to allow to use the name for the `bucketRef`. To keep it working use a name which contains only the characters a-z. The `bucketRef` is the constant string `S3Bucket` plus the `bucket` name with the first letter uppercase. - if you change the region check if SES receiving exists in your region ## Deploy In order to deploy the example, simply run: ```bash serverless deploy ``` The output should look similar to: ``` Serverless: Packaging service... Serverless: Excluding development dependencies... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading artifacts... Serverless: Uploading service .zip file to S3 (2.69 KB)... Serverless: Validating template... Serverless: Updating Stack... Serverless: Checking Stack update progress... ........................ Serverless: Stack update finished... Service Information service: aws-node-ses-receive-email-body stage: dev region: eu-west-1 stack: aws-node-ses-receive-email-body-dev api keys: None endpoints: None functions: postprocess: aws-node-ses-receive-email-body-dev-postprocess ``` ## Setup SNS Email Receiving Rule 1) Open the Amazon SES console at https://console.aws.amazon.com/ses/ 2) In the navigation pane, under Email Receiving, choose Rule Sets. 3) Choose **Create a Receipt Rule**. 4) On the Recipients page, choose **Next Step**. (Without a adding any recipients, Amazon SES applies this rule to all recipients) 5) For **Add action**, choose **S3**. 6) For **S3 bucket**,choose **Enter a bucket name** and select the bucket with the name you defined in `serverless.yml` 7) Choose **Next Step** 8) On the **Rule Details** page, for **Rule name**, type **my-rule**. Select the check box next to **Enabled**, and then choose **Next Step**. 9) On the **Review** page, choose **Create Rule**. ## Usage Send a test email to the receipient. You should see a new S3 object in the bucket which contains the whole email body. After a while, the postprocess function gets triggerd by an S3 event: ```bash serverless logs --function postprocess ``` ``` START RequestId: 695a6fa8-a711e8-ab5d-0fdb1ebfe5ea Version: $LATEST date: 2003T18:46:47.000Z subject: Test Subject body: Hello World from: Tim Turbo attachments: [] END RequestId: 695a6fa8-a711e8-ab5d-0fdb1ebfe5ea REPORT RequestId: 695a6fa8-a711e8-ab5d-0fdb1ebfe5ea Duration: 55.12 ms Billed Duration: 100 ms Memory Size: 1024 MB Max Memory Used: 42 MB ``` ================================================ FILE: aws-node-ses-receive-email-body/handler.js ================================================ 'use strict'; const AWS = require('aws-sdk'); const s3 = new AWS.S3({ apiVersion: '2006-03-01', region: process.env.AWSREGION, }); const simpleParser = require('mailparser').simpleParser; module.exports.postprocess = async (event) => { // console.log('Received event:', JSON.stringify(event, null, 2)); const record = event.Records[0]; // Retrieve the email from your bucket const request = { Bucket: record.s3.bucket.name, Key: record.s3.object.key, }; try { const data = await s3.getObject(request).promise(); // console.log('Raw email:' + data.Body); const email = await simpleParser(data.Body); console.log('date:', email.date); console.log('subject:', email.subject); console.log('body:', email.text); console.log('from:', email.from.text); console.log('attachments:', email.attachments); return { status: 'success' }; } catch (Error) { console.log(Error, Error.stack); return Error; } }; ================================================ FILE: aws-node-ses-receive-email-body/package.json ================================================ { "name": "aws-node-ses-receive-email-body", "description": "Receive an email, store in S3 bucket, trigger a lambda function.", "version": "1.0.0", "author": "Andreas Heissenberger ", "license": "MIT", "dependencies": { "mailparser": "^2.3.4" } } ================================================ FILE: aws-node-ses-receive-email-body/serverless.yml ================================================ service: aws-node-ses-receive-email-body frameworkVersion: ">=2.24.0" custom: bucket: sesreceiveemailbody bucketRef: S3BucketSesreceiveemailbody provider: name: aws runtime: nodejs12.x region: eu-west-1 iam: role: statements: - Effect: Allow Action: - s3:* Resource: "*" functions: postprocess: handler: handler.postprocess events: - s3: bucket: ${self:custom.bucket} event: s3:ObjectCreated:* resources: Resources: S3EMailBucketPermissions: Type: AWS::S3::BucketPolicy Properties: Bucket: Ref: ${self:custom.bucketRef} PolicyDocument: Statement: - Principal: Service: "ses.amazonaws.com" Action: - s3:PutObject Effect: Allow Sid: "AllowSESPuts" Resource: Fn::Join: ['', ['arn:aws:s3:::', Ref: "${self:custom.bucketRef}", '/*'] ] Condition: StringEquals: "aws:Referer": { Ref: AWS::AccountId } ================================================ FILE: aws-node-ses-receive-email-header/.gitignore ================================================ node_modules .serverless ================================================ FILE: aws-node-ses-receive-email-header/README.md ================================================ # Receive an email, trigger a lambda function to process header This example shows how to receive an email header with SES, trigger a lambda function, process headers or accept or reject emails. ## Use-cases - Postprocess of email header. - accept or reject emails ## Setup - [Create a SES verified Domain](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/receiving-email-getting-started-verify.html) but do not setup the "Rule Set" - if you change the region check if SES receiving exists in your region - if you change the function names you will need to update the normalized function name used in the resource section - e.g. processacceptreject => ProcessacceptrejectLambdaFunction ## Deploy In order to deploy the example, simply run: ```bash serverless deploy ``` The output should look similar to: ``` Serverless: Packaging service... Serverless: Excluding development dependencies... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading artifacts... Serverless: Uploading service .zip file to S3 (2.69 KB)... Serverless: Validating template... Serverless: Updating Stack... Serverless: Checking Stack update progress... ........................ Serverless: Stack update finished... Service Information service: aws-node-ses-receive-email-header stage: dev region: eu-west-1 stack: aws-node-ses-receive-email-header-dev api keys: None endpoints: None functions: processheader: aws-node-ses-receive-email-header-dev-processheader ``` ## Setup SNS Email Receiving Rule for process header 1) Open the Amazon SES console at https://console.aws.amazon.com/ses/ 2) In the navigation pane, under Email Receiving, choose Rule Sets. 3) Choose **Create a Receipt Rule**. 4) On the Recipients page, choose **Next Step**. (Without a adding any recipients, Amazon SES applies this rule to all recipients) 5) For **Add action**, choose **lambda**. 6) For **Lambda function**, choose the lambda function with the name **aws-node-ses-receive-email-header-dev-processheader** you defined in `serverless.yml` 6) **Invocation type** choose **Event** 7) Choose **Next Step** 8) On the **Rule Details** page, for **Rule name**, type **my-rule**. Select the check box next to **Enabled**, and then choose **Next Step**. 9) On the **Review** page, choose **Create Rule**. ## Setup SNS Email Receiving Rule for accept or reject emails Schritte 1-5 sind identisch dann: 6) For **Lambda function**, choose the lambda function with the name **aws-node-ses-receive-email-header-dev-processacceptreject** you defined in `serverless.yml` 6) **Invocation type** choose **RequestResponse** (Lambda function will be called synchronously to control mail flow) 7) Choose **Next Step** 8) On the **Rule Details** page, for **Rule name**, type **my-rule**. Select the check box next to **Enabled**, and then choose **Next Step**. 9) On the **Review** page, choose **Create Rule**. ## Usage Send a test email to the receipient. ``` serverless logs -t --function processheader ``` ``` START RequestId: eada06fc-c76a-11a8-bffd389a883292 Version: $LATEST { from: 'Tim Turbo ', to: 'ses-in@domain.test', subject: 'Testsubject', date: 'Thu, 4 Oct 2018 01:33:06 +0200' } END RequestId: eada06fc-c76a-11a8-bffd389a883292 REPORT RequestId: eada06fc-c76a-11a8-bffd389a883292 Duration: 5.62 ms Billed Duration: 100 ms Memory Size: 1024 MB Max Memory Used: 19 MB ``` ================================================ FILE: aws-node-ses-receive-email-header/handler.js ================================================ 'use strict'; module.exports.processheader = (event, context, callback) => { // console.log('Received event:', JSON.stringify(event, null, 2)); const mail = event.Records[0].ses.mail; const { timestamp, source, messageId } = mail; // console.log('received mail', mail); const { from, date, to, subject } = mail.commonHeaders; console.log({ from: from[0], to: to[0], subject, date, }); callback(null, { from: from[0], to: to[0], subject, date, timestamp, source, messageId, }); }; module.exports.processacceptreject = (event, context, callback) => { // console.log('Received event:', JSON.stringify(event, null, 2)); const sesNotification = event.Records[0].ses; // Check if any spam check failed if ( sesNotification.receipt.spfVerdict.status === 'FAIL' || sesNotification.receipt.dkimVerdict.status === 'FAIL' || sesNotification.receipt.spamVerdict.status === 'FAIL' || sesNotification.receipt.virusVerdict.status === 'FAIL' ) { console.log('Dropping spam'); // Stop processing rule set, dropping message callback(null, { disposition: 'STOP_RULE_SET' }); } else { callback(null, null); } }; ================================================ FILE: aws-node-ses-receive-email-header/package.json ================================================ { "name": "aws-node-ses-receive-email-header", "description": "Receive an email, trigger a lambda function to process header.", "version": "1.0.0", "author": "Andreas Heissenberger ", "license": "MIT" } ================================================ FILE: aws-node-ses-receive-email-header/serverless.yml ================================================ service: aws-node-ses-receive-email-header frameworkVersion: ">=1.1.0" provider: name: aws runtime: nodejs12.x region: eu-west-1 functions: processheader: handler: handler.processheader processacceptreject: handler: handler.processacceptreject resources: Resources: GiveSESPermissionToInvokeProcessheaderLambdaFunction: Type: AWS::Lambda::Permission Properties: FunctionName: { "Fn::GetAtt": [ "ProcessheaderLambdaFunction", "Arn" ] } Principal: ses.amazonaws.com Action: 'lambda:InvokeFunction' SourceAccount: { Ref: AWS::AccountId } GiveSESPermissionToInvokeProcessacceptrejectLambdaFunction: Type: AWS::Lambda::Permission Properties: FunctionName: { "Fn::GetAtt": [ "ProcessacceptrejectLambdaFunction", "Arn" ] } Principal: ses.amazonaws.com Action: 'lambda:InvokeFunction' SourceAccount: { Ref: AWS::AccountId } ================================================ FILE: aws-node-shared-gateway/README.md ================================================ # Shared AWS API Gateway Working on production projects would often require the usage of a shared API gateway between multiple Lambda functions. This repository showcases how to deploy multiple Lambda functions that are attached on a single API gateway. ## Add execution permission to CI deploy + decomission scripts. ```sh chmod +x .\ci-deploy.sh chmod +x .\ci-decomission.sh ``` ### Configure your AWS Deployment Account Provide an AWS account that have sufficient access so that serverless can deploy the stack. ```sh serverless config credentials --provider aws --key YOUR_AWS_ACCESS_KEY --secret YOUR_AWS_SECRET_KEY ``` # Deploying the API Gateway + Lambda Stack To deploy the ```sh .\ci-deploy ``` # Decomission all resources ```sh .\ci-decomission ``` ================================================ FILE: aws-node-shared-gateway/ci-decomission.sh ================================================ #!/bin/bash echo "Demolishing your awesome stacks..." cd products serverless remove cd .. cd transactions serverless remove cd .. cd users serverless remove cd .. cd gateway serverless remove echo "Demolishing complete :)" read ================================================ FILE: aws-node-shared-gateway/ci-deploy.sh ================================================ #!/bin/bash cd gateway serverless deploy sleep 5s cd .. cd products serverless deploy sleep 5s cd .. cd transactions serverless deploy sleep 5s cd .. cd users serverless deploy sleep 5s echo "Press any key to continue" read ================================================ FILE: aws-node-shared-gateway/gateway/.gitignore ================================================ # package directories node_modules jspm_packages # Serverless directories .serverless ================================================ FILE: aws-node-shared-gateway/gateway/serverless.yml ================================================ service: shared-gateway provider: name: aws runtime: nodejs12.x region: ap-southeast-1 resources: Resources: SharedGW: Type: AWS::ApiGateway::RestApi Properties: Name: SharedGW Outputs: apiGatewayRestApiId: Value: Ref: SharedGW Export: Name: SharedGW-restApiId apiGatewayRestApiRootResourceId: Value: Fn::GetAtt: - SharedGW - RootResourceId Export: Name: SharedGW-rootResourceId ================================================ FILE: aws-node-shared-gateway/package.json ================================================ { "name": "shared-aws-api-gateway-nodejs", "version": "1.0.0", "description": "A sample of implementing shared API gateway with multiple Node Lambdas.", "main": "handler.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { } } ================================================ FILE: aws-node-shared-gateway/products/.gitignore ================================================ # package directories node_modules jspm_packages # Serverless directories .serverless ================================================ FILE: aws-node-shared-gateway/products/handler.js ================================================ "use strict"; module.exports.getProducts = async event => { return { statusCode: 200, body: JSON.stringify([ { id: "20379435-7c7b-4bdd-8d0c-3f3136979c29", name: "Foo 1", price: 22 }, { id: "f7c26612-63ff-4064-89c8-9a316ba043a3", name: "Foo 2", price: 23 }, { id: "c04f63f0-b2ad-4526-a187-b6ac8adcc648", name: "Foo 3", price: 24 } ]) }; }; ================================================ FILE: aws-node-shared-gateway/products/serverless.yml ================================================ service: eshop-products provider: name: aws runtime: nodejs12.x region: ap-southeast-1 apiGateway: restApiId: "Fn::ImportValue": SharedGW-restApiId restApiRootResourceId: "Fn::ImportValue": SharedGW-rootResourceId functions: get-products: handler: handler.getProducts events: - http: path: products/list method: get ================================================ FILE: aws-node-shared-gateway/transactions/.gitignore ================================================ # package directories node_modules jspm_packages # Serverless directories .serverless ================================================ FILE: aws-node-shared-gateway/transactions/handler.js ================================================ "use strict"; module.exports.getTransactions = async event => { return { statusCode: 200, body: JSON.stringify([ { id: "72cd348d-3a9a-4173-a424-34908c43580a", productName: "Foo 1", price: 50, customerName: "Allan" }, { id: "94df26f3-7acc-4b3f-a698-a28707f90f04", productName: "Foo 2", price: 40, customerName: "Laura" }, { id: "5931c2f2-7345-4820-93ce-0cb5907a361b", name: "Foo 3", price: 21, customerName: "Tom" } ]) }; }; ================================================ FILE: aws-node-shared-gateway/transactions/serverless.yml ================================================ service: eshop-transactions provider: name: aws runtime: nodejs12.x region: ap-southeast-1 apiGateway: restApiId: "Fn::ImportValue": SharedGW-restApiId restApiRootResourceId: "Fn::ImportValue": SharedGW-rootResourceId functions: get-transactions: handler: handler.getTransactions events: - http: path: transactions/list method: get ================================================ FILE: aws-node-shared-gateway/users/.gitignore ================================================ # package directories node_modules jspm_packages # Serverless directories .serverless ================================================ FILE: aws-node-shared-gateway/users/handler.js ================================================ "use strict"; module.exports.getUsers = async event => { return { statusCode: 200, body: JSON.stringify([ { id: "09d21d59-c138-4f70-ae82-3552148d3d43", name: "Jimbo" }, { id: "2f8fb730-5d09-4493-aea5-46b3e3f8b08a", name: "Dumbo" }, { id: "03d2690f-6d01-4cf1-b947-a8cc0133cc9f", name: "Rambo" } ]) }; }; ================================================ FILE: aws-node-shared-gateway/users/serverless.yml ================================================ service: eshop-users provider: name: aws runtime: nodejs12.x region: ap-southeast-1 apiGateway: restApiId: "Fn::ImportValue": SharedGW-restApiId restApiRootResourceId: "Fn::ImportValue": SharedGW-rootResourceId functions: get-users: handler: handler.getUsers events: - http: path: users/list method: get ================================================ FILE: aws-node-signed-uploads/.babelrc ================================================ { "presets": [ [ "env", { "targets": { "node": "8.10" }, "modules": false, "loose": true } ] ] } ================================================ FILE: aws-node-signed-uploads/.eslintrc ================================================ --- root: true extends: - airbnb-base env: node: true ================================================ FILE: aws-node-signed-uploads/.gitignore ================================================ coverage .serverless .webpack node_modules *.log config.json ================================================ FILE: aws-node-signed-uploads/README.md ================================================ # AWS Node Signed Uploads ## Requirements * Node.js (version 8 is best at the moment) * npm which comes with Node.js * yarn ## Introduction The approach implemented in this service is useful when you want to use [Amazon API Gateway](https://aws.amazon.com/api-gateway/) and you want to solve the 10MB payload limit. The service is based on the [serverless](https://serverless.com/) framework. The service is uploading objects to a specific S3 bucket using a [pre-signed URL](http://docs.aws.amazon.com/AmazonS3/latest/dev/PresignedUrlUploadObject.html). Implemented in node.js runtime using [getSignedUrl](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getSignedUrl-property) method. The package is targeting the latest runtime of AWS Lambda. ([8.10](https://aws.amazon.com/blogs/compute/node-js-8-10-runtime-now-available-in-aws-lambda/)) ## Settings If you prefer to use a different region or stage, change these: ```sh $ export AWS_STAGE= $ export AWS_REGION= ``` Defaults are `dev` and `eu-central-1`. Change name of upload bucket: ```yaml bucketName: testBucket ``` ### File name to sign The file you want to upload is signed via `x-amz-meta-filekey` header. ### How to use Get dependencies with `yarn` or `npm install`. The following examples will assume the usage of `yarn`. Issue a `GET` request to get the signed URL: ```sh curl --request GET \ --url https://{serviceID}.execute-api.{region}.amazonaws.com/dev/upload \ --header 'x-amz-meta-filekey: the-road-to-graphql.pdf' ``` If your bucket is called `foo`, and you upload `the-road-to-graphql`, after receiving the signed URL, issue a `PUT` request with the information you have signed: ```sh curl --request PUT \ --url 'https://foo.s3.eu-central-1.amazonaws.com/the-road-to-graphql.pdf?X-Amz-SignedHeaders=host&X-Amz-Signature=the-signature&X-Amz-Security-Token=the-token&X-Amz-Expires=30&X-Amz-Date=20181210T113015Z&X-Amz-Credential=something10%2Feu-central-1%2Fs3%2Faws4_request&X-Amz-Algorithm=AWS4-HMAC-SHA256' \ --data 'somemething-awesome' ``` ### Integrations Here's a short list of possible integrations I found making a quick Google search: * [Using pre-signed URLs to upload a file to a private S3 bucket](https://sanderknape.com/2017/08/using-pre-signed-urls-upload-file-private-s3-bucket/) * [react-s3-uploader](https://www.npmjs.com/package/react-s3-uploader) ### Develop locally Starting a local dev server and its endpoint for receiving uploads: ```bash $ yarn start ``` ### Linter Starting the linter tasks: ```bash $ yarn lint ``` ### Deployment [Setup your AWS credentials](https://serverless.com/framework/docs/providers/aws/guide/credentials/). Run the following the fire the deployment: ```bash $ yarn deploy ``` ================================================ FILE: aws-node-signed-uploads/package.json ================================================ { "private": true, "name": "aws-node-signed-uploads", "description": "Serverless example for S3 signed uploads", "version": "1.0.0", "scripts": { "deploy": "serverless deploy -v", "lint": "eslint .", "start": "serverless offline start" }, "devDependencies": { "aws-sdk": "2.223.1", "babel-core": "6.26.0", "babel-loader": "7.1.4", "babel-preset-env": "1.6.1", "eslint": "4.19.1", "eslint-config-airbnb-base": "12.1.0", "eslint-plugin-import": "2.10.0", "serverless": "1.26.1", "serverless-offline": "3.20.1", "serverless-webpack": "5.1.1", "webpack": "4.5.0" }, "author": "Kalin Chernev ", "license": "MIT" } ================================================ FILE: aws-node-signed-uploads/serverless.yml ================================================ service: aws-node-signed-uploads plugins: - serverless-webpack - serverless-offline #serverless-offline needs to be last in the list custom: bucketName: testbucket123notaken webpack: webpackConfig: 'webpack.config.js' includeModules: true packager: 'yarn' serverless-offline: port: 4000 provider: name: aws runtime: nodejs12.x stage: ${opt:stage, env:AWS_STAGE, 'dev'} region: ${opt:region, env:AWS_REGION, 'eu-central-1'} environment: REGION: ${self:provider.region} BUCKET: { Ref: Uploads } versionFunctions: false iam: role: statements: - Effect: "Allow" Action: - "s3:*" Resource: "*" functions: upsert-objects: handler: src/upload.handler name: ${self:provider.stage}-${self:service}-upload memorySize: 128 events: - http: path: upload method: get cors: true resources: Resources: Uploads: Type: AWS::S3::Bucket Properties: BucketName: ${self:custom.bucketName} CorsConfiguration: CorsRules: - AllowedHeaders: - "Authorization" AllowedMethods: - GET AllowedOrigins: - "*" - AllowedHeaders: - "*" AllowedMethods: - PUT AllowedOrigins: - "*" ================================================ FILE: aws-node-signed-uploads/src/upload.js ================================================ import AWS from 'aws-sdk'; // eslint-disable-line import/no-extraneous-dependencies export const handler = async event => { const { REGION: region, BUCKET: bucket } = process.env; if (!region || !bucket) { throw new Error('REGION and BUCKET environment variables are required!'); } const S3 = new AWS.S3({ signatureVersion: 'v4', region }); const file = event.headers && event.headers['x-amz-meta-filekey'] ? event.headers['x-amz-meta-filekey'] : undefined; if (!file) { return { statusCode: 400, body: JSON.stringify({ message: 'Missing x-amz-meta-filekey in the header of the request.', }), }; } const params = { Bucket: bucket, Key: file, Expires: 30, }; try { const url = await S3.getSignedUrl('putObject', params); return { statusCode: 200, headers: { 'Access-Control-Allow-Origin': '*', // Required for CORS support to work 'Access-Control-Allow-Credentials': true, // Required for cookies, authorization headers with HTTPS }, body: JSON.stringify(url), }; } catch (error) { return { statusCode: 400, body: JSON.stringify(error), }; } }; export default handler; ================================================ FILE: aws-node-signed-uploads/webpack.config.js ================================================ const slsw = require('serverless-webpack'); const path = require('path'); module.exports = { entry: slsw.lib.entries, target: 'node', externals: [{ 'aws-sdk': true }], module: { rules: [ { test: /\.js$/, include: __dirname, exclude: /node_modules/, use: [{ loader: 'babel-loader' }], }, ], }, output: { libraryTarget: 'commonjs', path: path.join(__dirname, '.webpack'), filename: '[name].js', }, }; ================================================ FILE: aws-node-simple-http-endpoint/.gitignore ================================================ node_modules .serverless ================================================ FILE: aws-node-simple-http-endpoint/README.md ================================================ # Simple HTTP Endpoint Example This example demonstrates how to setup a simple HTTP GET endpoint. Once you fetch it, it will reply with the current time. While the internal function is name `currentTime` the HTTP endpoint is exposed as `time`. ## Use Cases - Wrapping an existing internal or external endpoint/service ## Invoke the function locally ```bash serverless invoke local --function currentTime ``` Which should result in: ```bash Serverless: Your function ran successfully. { "statusCode": 200, "body": "{\"message\":\"Hello, the current time is 12:49:06 GMT+0100 (CET).\"}" } ``` ## Deploy In order to deploy the endpoint, simply run: ```bash serverless deploy ``` The expected result should be similar to: ```bash Serverless: Packaging service… Serverless: Uploading CloudFormation file to S3… Serverless: Uploading service .zip file to S3… Serverless: Updating Stack… Serverless: Checking Stack update progress… ........................... Serverless: Stack update finished… Service Information service: serverless-simple-http-endpoint stage: dev region: us-east-1 api keys: None endpoints: GET - https://2e16njizla.execute-api.us-east-1.amazonaws.com/time functions: serverless-simple-http-endpoint-dev-currentTime: arn:aws:lambda:us-east-1:488110005556:function:serverless-simple-http-endpoint-dev-currentTime ``` ## Usage You can now invoke the Lambda directly and even see the resulting log via ```bash serverless invoke --function currentTime --log ``` or as send an HTTP request directly to the endpoint using a tool like curl ```bash curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/time ``` ## Scaling By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 1000. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit). ================================================ FILE: aws-node-simple-http-endpoint/handler.js ================================================ 'use strict'; module.exports.endpoint = (event, context, callback) => { const response = { statusCode: 200, body: JSON.stringify({ message: `Hello, the current time is ${new Date().toTimeString()}.`, }), }; callback(null, response); }; ================================================ FILE: aws-node-simple-http-endpoint/package.json ================================================ { "name": "aws-serve-simple-http-endpoint", "version": "1.0.0", "description": "Example demonstrates how to setup a simple HTTP GET endpoint", "author": "", "license": "MIT" } ================================================ FILE: aws-node-simple-http-endpoint/serverless.yml ================================================ service: serverless-simple-http-endpoint frameworkVersion: '2' provider: name: aws runtime: nodejs12.x functions: currentTime: handler: handler.endpoint events: - httpApi: path: /time method: get ================================================ FILE: aws-node-simple-transcribe-s3/.gitignore ================================================ .serverless ================================================ FILE: aws-node-simple-transcribe-s3/README.md ================================================ # Simple AWS Transcribe example in NodeJS This example demonstrates how to setup a lambda function to transcribe your audio file (.wav format) into a text transcription. The lambda will be triggered whenever a new audio file is uploaded to S3 and the transcription (JSON format) will be saved to a S3 bucket. ## Use Cases - Transcribe your audio file (voice messages, phone recordings) to text using AWS Transcribe ## Setup - Edit `serverless.yml` and change the language code if you need to, at the moment: `en-US | es-US | en-AU | fr-CA | en-UK` is supported - Declare AWS region if you need to, beware that at the moment, AWS Transcribe is not supported for all regions. ## Deploy In order to deploy the you endpoint simply run ```bash serverless deploy ``` The expected result should be similar to: ```bash Serverless: Packaging service... Serverless: Excluding development dependencies... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading artifacts... Serverless: Uploading service .zip file to S3 (1.71 KB)... Serverless: Validating template... Serverless: Updating Stack... Serverless: Checking Stack update progress... ........................ Serverless: Stack update finished... Service Information service: aws-node-simple-transcribe-s3 stage: dev region: us-east-1 stack: aws-node-simple-transcribe-s3-dev api keys: None endpoints: None functions: transcribe: aws-node-simple-transcribe-s3-dev-transcribe ``` ## Usage - Upload a audio file to your S3 audio bucket - A transcription job should be created in AWS Transcribe - The result (in JSON format) should then be found in the S3 transcription bucket ================================================ FILE: aws-node-simple-transcribe-s3/handler.js ================================================ 'use strict'; const awsSdk = require('aws-sdk'); const transcribeService = new awsSdk.TranscribeService(); module.exports.transcribe = (event, context, callback) => { const records = event.Records; const transcribingPromises = records.map((record) => { const recordUrl = [ 'https://s3.amazonaws.com', process.env.S3_AUDIO_BUCKET, record.s3.object.key, ].join('/'); const TranscriptionJobName = record.s3.object.key; return transcribeService.startTranscriptionJob({ LanguageCode: process.env.LANGUAGE_CODE, Media: { MediaFileUri: recordUrl }, MediaFormat: 'wav', TranscriptionJobName, MediaSampleRateHertz: 8000, // normally 8000 if you are using wav file OutputBucketName: process.env.S3_TRANSCRIPTION_BUCKET, }).promise(); }); Promise.all(transcribingPromises) .then(() => { callback(null, { message: 'Start transcription job successfully' }); }) .catch(err => callback(err, { message: 'Error start transcription job' })); }; ================================================ FILE: aws-node-simple-transcribe-s3/package.json ================================================ { "name": "aws-node-simple-transcribe-s3", "version": "1.0.0", "description": "Example demonstrates how to setup a lambda function to transcribe audio file", "author": "", "license": "MIT" } ================================================ FILE: aws-node-simple-transcribe-s3/serverless.yml ================================================ service: aws-node-simple-transcribe-s3 provider: name: aws runtime: nodejs12.x environment: S3_AUDIO_BUCKET: ${self:service}-${opt:stage, self:provider.stage}-records S3_TRANSCRIPTION_BUCKET: ${self:service}-${opt:stage, self:provider.stage}-transcriptions LANGUAGE_CODE: en-US iam: role: statements: - Effect: Allow Action: - s3:PutObject - s3:GetObject Resource: - 'arn:aws:s3:::${self:provider.environment.S3_AUDIO_BUCKET}/*' - 'arn:aws:s3:::${self:provider.environment.S3_TRANSCRIPTION_BUCKET}/*' - Effect: Allow Action: - transcribe:StartTranscriptionJob Resource: '*' functions: transcribe: handler: handler.transcribe events: - s3: bucket: ${self:provider.environment.S3_AUDIO_BUCKET} event: s3:ObjectCreated:* resources: Resources: S3TranscriptionBucket: Type: 'AWS::S3::Bucket' Properties: BucketName: ${self:provider.environment.S3_TRANSCRIPTION_BUCKET} ================================================ FILE: aws-node-single-page-app-via-cloudfront/.gitignore ================================================ node_modules .serverless ================================================ FILE: aws-node-single-page-app-via-cloudfront/README.md ================================================ # Single Page Application This example demonstrates how to setup a Single Page Application. Our goals here are to serve a static page with low latency. One additional goal is to make sure the client side application can leverage the History API functions `pushState` and `replaceState` to change the current URL without reloading. Further we want to make sure all the content is only served via HTTPS. HTTP requests should get redirected to HTTPS. To achieve these goals we use S3 in combination with CloudFront. S3 is used to store our static HTML file while CloudFront is responsible for making it available via Amazon's Content Delivery Network. ## Prerequisite [Nodejs](https://nodejs.org/en/) (at least version 8) The `serverless-single-page-app-plugin` in this example requires the Serverless Framework version 1.2.0 or higher and the AWS Command Line Interface. Learn more [here](http://docs.aws.amazon.com/cli/latest/userguide/installing.html) on how to install the AWS Command Line Interface. ## Setup Replace the bucket name in `serverless.yaml` which you can find inside the `custom` section. There is a placeholder text `yourBucketName123`. This is due the fact that bucket names must be globally unique across all AWS S3 buckets. Since this plugin uses a custom Serverless plugin you need to setup the `node_modules` by running: ```bash npm install ``` The `serverless-single-page-app-plugin` plugin in this example is there to simplify the experience using this example. It's not necessary to understand the plugin to deploy your Single Page Application. # Deploy Warning: Whenever you making changes to CloudFront resource in `serverless.yml` the deployment might take a while e.g 20 minutes. In order to deploy the Single Page Application you need to setup the infrastructure first by running ```bash serverless deploy ``` The expected result should be similar to: ```bash Serverless: Packaging service… Serverless: Uploading CloudFormation file to S3… Serverless: Uploading service .zip file to S3… Serverless: Updating Stack… Serverless: Checking Stack update progress… ........................... Serverless: Stack update finished… Service Information service: serverless-simple-http-endpoint stage: dev region: us-east-1 api keys: None endpoints: None functions: None ``` After this step your S3 bucket and CloudFront distribution is setup. Now you need to upload your static file e.g. `index.html` and `app.js` to S3. You can do this by running ```bash serverless syncToS3 ``` The expected result should be similar to ```bash Serverless: upload: app/index.html to s3://yourBucketName123/index.html Serverless: upload: app/app.js to s3://yourBucketName123/app.js Serverless: Successfully synced to the S3 bucket ``` Hint: The plugin is simply running the AWS CLI command: `aws S3 sync app/ s3://yourBucketName123/` Now you just need to figure out the deployed URL. You can use the AWS Console UI or run ```bash sls domainInfo ``` The expected result should be similar to ```bash Serverless: Web App Domain: dyj5gf0t6nqke.cloudfront.net ``` Visit the printed domain domain and navigate on the web site. It should automatically redirect you to HTTPS and visiting /about will not result in an error with the status code 404, but rather serves the `index.html` and renders the about page. This is how it should look like: ![Screenshot](https://cloud.githubusercontent.com/assets/223045/20391786/287cb3acd5-11e6-9eaf-89f641ed9e14.png) # Re-deploying If you make changes to your Single Page Application you might need to invalidate CloudFront's cache to make sure new files are served. Meaning, run: ```bash serverless syncToS3 ``` To sync your files and then: ```bash serverless invalidateCloudFrontCache ``` ## Further Improvements Here a list of potential improvements you can do with your CloudFront setup depending on your use-case: - Setup a custom domain alias - Logging for CloudFront requests - Setup a restriction so the Bucket is not publicly accessible except via CloudFront ================================================ FILE: aws-node-single-page-app-via-cloudfront/app/app.js ================================================ /* eslint-disable */ // Renders the page based on the current URL function renderApp() { var content; if (window.location.pathname === '/about') { content = '
Welcome to the About page
' } else if (window.location.pathname === '/') { content = '
Welcome Serverless Developer :)
' } var main = document.getElementsByTagName('main')[0]; main.innerHTML = content; } // Navigate to another URL and re-render the application function navigate(evt) { evt.preventDefault(); var href = evt.target.getAttribute('href'); window.history.pushState({}, undefined, href); renderApp(); } document.addEventListener('DOMContentLoaded', function(event) { // Attach the event listener once the DOM has been loaded var nav = document.getElementsByTagName('nav')[0]; nav.addEventListener("click", navigate, false); // First initial App rendering renderApp(); }); ================================================ FILE: aws-node-single-page-app-via-cloudfront/app/index.html ================================================
================================================ FILE: aws-node-single-page-app-via-cloudfront/package.json ================================================ { "name": "aws-single-page-app-via-cloudfront", "version": "1.0.0", "description": "Demonstrating how to deploy a Single Page Application with Serverless", "repository": "", "author": "", "license": "MIT", "devDependencies": { "serverless-single-page-app-plugin": "file:./serverless-single-page-app-plugin" } } ================================================ FILE: aws-node-single-page-app-via-cloudfront/serverless-single-page-app-plugin/index.js ================================================ 'use strict'; const spawnSync = require('child_process').spawnSync; class ServerlessPlugin { constructor(serverless, options) { this.serverless = serverless; this.options = options; this.commands = { syncToS3: { usage: 'Deploys the `app` directory to your bucket', lifecycleEvents: [ 'sync', ], }, domainInfo: { usage: 'Fetches and prints out the deployed CloudFront domain names', lifecycleEvents: [ 'domainInfo', ], }, invalidateCloudFrontCache: { usage: 'Invalidates CloudFront cache', lifecycleEvents: [ 'invalidateCache', ], }, }; this.hooks = { 'syncToS3:sync': this.syncDirectory.bind(this), 'domainInfo:domainInfo': this.domainInfo.bind(this), 'invalidateCloudFrontCache:invalidateCache': this.invalidateCache.bind( this, ), }; } runAwsCommand(args) { let command = 'aws'; if (this.serverless.variables.service.provider.region) { command = `${command} --region ${this.serverless.variables.service.provider.region}`; } if (this.serverless.variables.service.provider.profile) { command = `${command} --profile ${this.serverless.variables.service.provider.profile}`; } const result = spawnSync(command, args, { shell: true }); const stdout = result.stdout.toString(); const sterr = result.stderr.toString(); if (stdout) { this.serverless.cli.log(stdout); } if (sterr) { this.serverless.cli.log(sterr); } return { stdout, sterr }; } // syncs the `app` directory to the provided bucket syncDirectory() { const s3Bucket = this.serverless.variables.service.custom.s3Bucket; const args = [ 's3', 'sync', 'app/', `s3://${s3Bucket}/`, '--delete', ]; const { sterr } = this.runAwsCommand(args); if (!sterr) { this.serverless.cli.log('Successfully synced to the S3 bucket'); } else { throw new Error('Failed syncing to the S3 bucket'); } } // fetches the domain name from the CloudFront outputs and prints it out async domainInfo() { const provider = this.serverless.getProvider('aws'); const stackName = provider.naming.getStackName(this.options.stage); const result = await provider.request( 'CloudFormation', 'describeStacks', { StackName: stackName }, this.options.stage, this.options.region, ); const outputs = result.Stacks[0].Outputs; const output = outputs.find( entry => entry.OutputKey === 'WebAppCloudFrontDistributionOutput', ); if (output && output.OutputValue) { this.serverless.cli.log(`Web App Domain: ${output.OutputValue}`); return output.OutputValue; } this.serverless.cli.log('Web App Domain: Not Found'); const error = new Error('Could not extract Web App Domain'); throw error; } async invalidateCache() { const provider = this.serverless.getProvider('aws'); const domain = await this.domainInfo(); const result = await provider.request( 'CloudFront', 'listDistributions', {}, this.options.stage, this.options.region, ); const distributions = result.DistributionList.Items; const distribution = distributions.find( entry => entry.DomainName === domain, ); if (distribution) { this.serverless.cli.log( `Invalidating CloudFront distribution with id: ${distribution.Id}`, ); const args = [ 'cloudfront', 'create-invalidation', '--distribution-id', distribution.Id, '--paths', '/*', ]; const { sterr } = this.runAwsCommand(args); if (!sterr) { this.serverless.cli.log('Successfully invalidated CloudFront cache'); } else { throw new Error('Failed invalidating CloudFront cache'); } } else { const message = `Could not find distribution with domain ${domain}`; const error = new Error(message); this.serverless.cli.log(message); throw error; } } } module.exports = ServerlessPlugin; ================================================ FILE: aws-node-single-page-app-via-cloudfront/serverless-single-page-app-plugin/package.json ================================================ { "name": "serverless-single-page-app-plugin", "version": "1.0.0", "description": "A plugin to simplify deploying Single Page Application using S3 and CloudFront", "author": "", "license": "MIT" } ================================================ FILE: aws-node-single-page-app-via-cloudfront/serverless.yml ================================================ service: single-page-app-via-cloudfront7 frameworkVersion: ">=1.2.0 <2.0.0" plugins: - serverless-single-page-app-plugin custom: s3Bucket: your-bucket-name-123 provider: name: aws runtime: nodejs12.x resources: Resources: ## Specifying the S3 Bucket WebAppS3Bucket: Type: AWS::S3::Bucket Properties: BucketName: ${self:custom.s3Bucket} AccessControl: Private PublicAccessBlockConfiguration: BlockPublicPolicy: false WebsiteConfiguration: IndexDocument: index.html ErrorDocument: index.html ## Specifying the policies to make sure all files inside the Bucket are avaialble to CloudFront WebAppS3BucketPolicy: Type: AWS::S3::BucketPolicy Properties: Bucket: Ref: WebAppS3Bucket PolicyDocument: Statement: - Sid: PublicReadGetObject Effect: Allow Principal: "*" Action: - s3:GetObject Resource: arn:aws:s3:::${self:custom.s3Bucket}/* ## Specifying the CloudFront Distribution to server your Web Application WebAppCloudFrontDistribution: Type: AWS::CloudFront::Distribution Properties: DistributionConfig: Origins: - DomainName: ${self:custom.s3Bucket}.s3.amazonaws.com ## An identifier for the origin which must be unique within the distribution Id: WebApp CustomOriginConfig: HTTPPort: 80 HTTPSPort: 443 OriginProtocolPolicy: https-only ## In case you want to restrict the bucket access use S3OriginConfig and remove CustomOriginConfig # S3OriginConfig: # OriginAccessIdentity: origin-access-identity/cloudfront/E127EXAMPLE51Z Enabled: 'true' ## Uncomment the following section in case you are using a custom domain # Aliases: # - mysite.example.com DefaultRootObject: index.html ## Since the Single Page App is taking care of the routing we need to make sure ever path is served with index.html ## The only exception are files that actually exist e.h. app.js, reset.css CustomErrorResponses: - ErrorCode: 404 ResponseCode: 200 ResponsePagePath: /index.html DefaultCacheBehavior: AllowedMethods: - DELETE - GET - HEAD - OPTIONS - PATCH - POST - PUT ## The origin id defined above TargetOriginId: WebApp ## Defining if and how the QueryString and Cookies are forwarded to the origin which in this case is S3 ForwardedValues: QueryString: 'false' Cookies: Forward: none ## The protocol that users can use to access the files in the origin. To allow HTTP use `allow-all` ViewerProtocolPolicy: redirect-to-https ## The certificate to use when viewers use HTTPS to request objects. ViewerCertificate: CloudFrontDefaultCertificate: 'true' ## Uncomment the following section in case you want to enable logging for CloudFront requests # Logging: # IncludeCookies: 'false' # Bucket: mylogs.s3.amazonaws.com # Prefix: myprefix ## In order to print out the hosted domain via `serverless info` we need to define the DomainName output for CloudFormation Outputs: WebAppCloudFrontDistributionOutput: Value: 'Fn::GetAtt': [ WebAppCloudFrontDistribution, DomainName ] ================================================ FILE: aws-node-sqs-worker/.gitignore ================================================ node_modules .serverless ================================================ FILE: aws-node-sqs-worker/README.md ================================================ # Serverless Framework Node SQS Producer-Consumer on AWS This template demonstrates how to develop and deploy a simple SQS-based producer-consumer service running on AWS Lambda using the Serverless Framework and the [Lift](https://github.com/getlift/lift) plugin. It allows to accept messages, for which computation might be time or resource intensive, and offload their processing to an asynchronous background process for a faster and more resilient system. ## Anatomy of the template This template defines one function `producer` and one Lift construct - `jobs`. The producer function is triggered by `http` event type, accepts JSON payloads and sends it to a SQS queue for asynchronous processing. The SQS queue is created by the `jobs` queue construct of the Lift plugin. The queue is set up with a "dead-letter queue" (to receive failed messages) and a `worker` Lambda function that processes the SQS messages. To learn more: - about `http` event configuration options, refer to [http event docs](https://www.serverless.com/framework/docs/providers/aws/events/apigateway/) - about the `queue` construct, refer to [the `queue` documentation in Lift](https://github.com/getlift/lift/blob/master/docs/queue.md) - about the Lift plugin in general, refer to [the Lift project](https://github.com/getlift/lift) - about SQS processing with AWS Lambda, please refer to the official [AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html) ### Deployment Install dependencies with: ``` npm install ``` Then deploy: ``` serverless deploy ``` After running deploy, you should see output similar to: ```bash Serverless: Packaging service... Serverless: Excluding development dependencies... Serverless: Creating Stack... Serverless: Checking Stack create progress... ........ Serverless: Stack create finished... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading artifacts... Serverless: Uploading service aws-node-sqs-worker.zip file to S3 (21.45 MB)... Serverless: Validating template... Serverless: Updating Stack... Serverless: Checking Stack update progress... ................................................ Serverless: Stack update finished... Service Information service: aws-node-sqs-worker stage: dev region: us-east-1 stack: aws-node-sqs-worker-dev resources: 17 api keys: None endpoints: POST - https://xxxx.execute-api.us-east-1.amazonaws.com/produce functions: producer: aws-node-sqs-worker-dev-producer jobsWorker: aws-node-sqs-worker-dev-jobsWorker layers: None jobs: queueUrl: https://sqs.us-east-1.amazonaws.com/xxxx/aws-node-sqs-worker-dev-jobs ``` _Note_: In current form, after deployment, your API is public and can be invoked by anyone. For production deployments, you might want to configure an authorizer. For details on how to do that, refer to [http event docs](https://www.serverless.com/framework/docs/providers/aws/events/apigateway/). ### Invocation After successful deployment, you can now call the created API endpoint with `POST` request to invoke `producer` function: ```bash curl --request POST 'https://xxxxxx.execute-api.us-east-1.amazonaws.com/produce' --header 'Content-Type: application/json' --data-raw '{"name": "John"}' ``` In response, you should see output similar to: ```bash {"message": "Message accepted!"} ``` ================================================ FILE: aws-node-sqs-worker/handler.js ================================================ exports.consumer = async (event) => { for (const record of event.Records) { console.log("Message Body: ", record.body); } }; ================================================ FILE: aws-node-sqs-worker/package.json ================================================ { "name": "aws-node-sqs-worker", "version": "1.0.0", "description": "Serverless Framework Node SQS Producer-Consumer on AWS", "author": "", "license": "MIT" } ================================================ FILE: aws-node-sqs-worker/serverless.template.yml ================================================ name: aws-node-sqs-worker org: serverlessinc description: Deploys a Node SQS Producer-Consumer service with traditional Serverless Framework keywords: aws, serverless, faas, lambda, node, sqs repo: https://github.com/serverless/examples/aws-node-sqs-worker license: MIT ================================================ FILE: aws-node-sqs-worker/serverless.yml ================================================ service: aws-node-sqs-worker frameworkVersion: '4' provider: name: aws runtime: nodejs20.x functions: producer: handler: handler.consumer events: - sqs: arn:aws:sqs:${aws:region}:${aws:accountId}:MyFirstQueue resources: Resources: JobQueue: Type: AWS::SQS::Queue Properties: QueueName: MyFirstQueue ================================================ FILE: aws-node-stripe-integration/.gitignore ================================================ # package directories node_modules jspm_packages # Serverless directories .serverless # Config yaml (local.yaml) config/local.yaml ================================================ FILE: aws-node-stripe-integration/README.md ================================================ # Stripe Integration Example This example for Stripe integration using AWS Lambda and API Gateway. ## Use Cases - Notified about events that happen in a Stripe account. ## Setup ### Install npm packages ```bash $ npm install ``` ### Edit config overrides for production deployment ```bash $ vi config/local.yaml ``` ```yaml stripe: test_sk: 'Stripe_Test_Secret_Key_here' live_sk: 'Stripe_Live_Secret_Key_here' ``` ### Deploy! ```bash $ serverless deploy -v ``` or production ```bash:production $ serverless deploy -v --stage live ``` ``` Serverless: Packaging service... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading service .zip file to S3 (2.15 MB)... Serverless: Updating Stack... Serverless: Checking Stack update progress... ..... Serverless: Stack update finished... Serverless: Removing old service versions... Service Information service: aws-node-stripe-integration stage: development region: us-east-1 api keys: None endpoints: POST - https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/test/stripe/incoming functions: incoming: aws-node-stripe-integration-test-incoming Stack Outputs ServiceEndpoint: https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/test ServerlessDeploymentBucketName: aws-node-stripe-integration-serverlessdeploymentbuck-xxxxxxxxxxxx IncomingLambdaFunctionQualifiedArn: arn:aws:lambda:us-east-1:000000000000:function:aws-node-stripe-integration-test-incoming:20 ``` ================================================ FILE: aws-node-stripe-integration/config/default.yaml ================================================ stripe: test_sk: 'sk_test_XXXXXXXXXXXXXXXXXXXXXXXX' live_sk: 'sk_live_XXXXXXXXXXXXXXXXXXXXXXXX' ================================================ FILE: aws-node-stripe-integration/handler.js ================================================ 'use strict'; const ConfigFile = require('config'); // eslint-disable-line module.exports.incoming = (event, context, callback) => { const requestContextStage = event.requestContext ? event.requestContext.stage : 'test'; const stripeApiKey = requestContextStage === 'test' ? ConfigFile.stripe.test_sk : ConfigFile.stripe.live_sk; const stripe = require('stripe')(stripeApiKey); // eslint-disable-line try { // Parse Stripe Event const jsonData = JSON.parse(event.body); // https://stripe.com/docs/api#event_object // Verify the event by fetching it from Stripe console.log("Stripe Event: %j", jsonData); // eslint-disable-line stripe.events.retrieve(jsonData.id, (err, stripeEvent) => { const eventType = stripeEvent.type ? stripeEvent.type : ''; const response = { statusCode: 200, body: JSON.stringify({ message: 'Stripe webhook incoming!', stage: requestContextStage, }), }; console.log("Event Type: %j", eventType); // eslint-disable-line // Branch by event type switch (eventType) { case 'invoice.created': // invoice.created event break; default: break; } callback(null, response); }); } catch (err) { callback(null, { statusCode: err.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: err.message || 'Internal server error', }); } }; ================================================ FILE: aws-node-stripe-integration/package.json ================================================ { "name": "aws-node-stripe-integration", "version": "1.0.0", "description": "This example for Stripe integration using AWS Lambda and API Gateway.", "main": "handler.js", "dependencies": { "config": "^1.24.0", "js-yaml": "^3.7.0", "stripe": "^4.14.0" }, "devDependencies": {}, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC" } ================================================ FILE: aws-node-stripe-integration/serverless.yml ================================================ # Welcome to Serverless! # # This file is the main config file for your service. # It's very minimal at this point and uses default values. # You can always add more config options for more control. # We've included some commented out config examples here. # Just uncomment any of them to get that config option. # # For full config options, check the docs: # docs.serverless.com # # Happy Coding! service: aws-node-stripe-integration # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details # frameworkVersion: "=X.X.X" provider: name: aws runtime: nodejs12.x stage: test region: us-east-1 # you can add statements to the Lambda function's IAM Role here # iam: # role: # statements: # - Effect: "Allow" # Action: # - "lambda:InvokeAsync" # - "lambda:InvokeFunction" # Resource: # - "*" # you can define service wide environment variables here # environment: # variable1: value1 # you can add packaging information here package: include: - config/** - node_modules/** exclude: - package.json functions: incoming: handler: handler.incoming events: - http: path: stripe/incoming method: post ================================================ FILE: aws-node-telegram-echo-bot/README.md ================================================ # AWS-telegram-echo-bot This is a simple echo bot on Telegram. (NodeJS) ### Required - Node.js `v6.5.0` or later - Telegram account - AWS account ## Get Started 1. Install serverless via npm ``` $ npm install -g serverless $ npm install ``` 2. Create a bot from Telegram, sending this message to [@BotFather](https://web.telegram.org/#/im?p=@BotFather) ``` $ /newbot ``` 3. Put the token received into a file called `handle.js`. ``` const token = "YOUR_API_TOKEN"; ``` 4. Deploy it! ``` $ serverless deploy ``` 5. Configure webhook ``` curl --request POST --url https://api.telegram.org/bot{token}/setWebhook --header 'content-type: application/json' --data '{"url": "{end-poinnt}"}' ``` Say `hello` to your bot 🤖 ================================================ FILE: aws-node-telegram-echo-bot/handler.js ================================================ 'use strict'; const request = require('request'); module.exports.webhook = (event, context, callback) => { const token = '[YOU TOKEN PLZ]'; const BASE_URL = `https://api.telegram.org/bot${token}/sendMessage`; const body = JSON.parse(event.body) const message = body.message const chatId = message.chat.id request.post(BASE_URL).form({ text: message.text, chat_id: chatId }); const response = { statusCode: 200, body: JSON.stringify({ input: event, }), }; return callback(null, response); }; ================================================ FILE: aws-node-telegram-echo-bot/package.json ================================================ { "name": "aws-node-line-echo-bot", "version": "1.0.0", "description": "This is a simple echo bot on Telegram", "main": "handler.js", "keywords": [ "aws", "nodejs", "telegram", "chatbot", "serverless" ], "dependencies": { "request": "^2.88.0" } } ================================================ FILE: aws-node-telegram-echo-bot/serverless.yml ================================================ service: aws-node-telegram-echo-bot provider: name: aws runtime: nodejs12.x functions: webhook: handler: handler.webhook events: - http: path: webhook method: post ================================================ FILE: aws-node-text-analysis-via-sns-post-processing/.gitignore ================================================ node_modules .serverless ================================================ FILE: aws-node-text-analysis-via-sns-post-processing/README.md ================================================ # Data processing This example demonstrates how to setup a simple data processing pipeline. The service exposes one HTTP endpoint that allows you to add a text note. This HTTP endpoint returns instantly to provide a good user experience while the actual analysis is deferred. Only messages above a certain sentiment level are actually saved. Instead of invoking another Lambda function directly it's considered best practice to store the note as a message in a SNS queue. The queue has certain benefits compared to invoking the `analyzeNote` function directly. The queue supports retries in case the analyzeNote function fails as well as back-off to avoid too many concurrent invocations. ## Setup ```bash npm install ``` In order to use SNS you need to add your AWS account ID to config.js. There is already a placeholder: `XXXXXXXXXXXX`. You can retrieve the your account ID by running this command (you need the AWS SDK installed) ```bash aws sts get-caller-identity --output text --query Account ``` # Explanation - sns topic will be added by default ## Deploy In order to deploy the you endpoint simply run ```bash serverless deploy ``` The expected result should be similar to: ```bash Serverless: Packaging service… Serverless: Uploading CloudFormation file to S3… Serverless: Uploading service .zip file to S3… Serverless: Updating Stack… Serverless: Checking Stack update progress… ............ Serverless: Stack update finished… Serverless: Removing old service versions… Service Information service: text-analysis-via-post-processing stage: dev region: us-east-1 api keys: None endpoints: POST - https://5cvfn0wwv7.execute-api.us-east-1.amazonaws.com/dev/notes functions: text-analysis-via-post-processing-dev-analyzeNote: arn:aws:lambda:us-east-1:377024778620:function:text-analysis-via-post-processing-dev-analyzeNote text-analysis-via-post-processing-dev-addNote: arn:aws:lambda:us-east-1:377024778620:function:text-analysis-via-post-processing-dev-addNote ``` ## Usage In order to add a note run ```bash curl -X POST https://XXXXXXXXX.execute-api.us-east-1.amazonaws.com/dev/notes --data '{ "note": "This is such a great Day" }' ``` You should see the following output ```bash {"message":"Successfully added the note."}% ``` To verify that the note has been processed run ```bash serverless logs --function analyzeNote ``` This command will show you the logged output and looks liked this ```bash START RequestId: 75a970ba-ab11e6-809d-435833490828 Version: $LATEST 2015 17:56:32.497 (+01:00) 75a970ba-ab11e6-809d-435833490828 Positive note - will be published: This is such a great Day END RequestId: 75a970ba-ab11e6-809d-435833490828 REPORT RequestId: 75a970ba-ab11e6-809d-435833490828 Duration: 3.45 ms Billed Duration: 100 ms Memory Size: 1024 MB Max Memory Used: 15 MB ``` You can play with the system and see which notes will be published and which won't. # Scaling TODO ================================================ FILE: aws-node-text-analysis-via-sns-post-processing/addNote.js ================================================ 'use strict'; const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies const config = require('./config.js'); const sns = new AWS.SNS(); module.exports.addNote = (event, context, callback) => { const data = JSON.parse(event.body); if (typeof data.note !== 'string') { console.error('Validation Failed'); callback(null, { statusCode: 400, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t add the note.', }); return; } const params = { Message: data.note, TopicArn: `arn:aws:sns:us-east-1:${config.awsAccountId}:analyzeNote`, }; sns.publish(params, (error) => { if (error) { console.error(error); callback(null, { statusCode: 501, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t add the note due an internal error. Please try again later.', }); } // create a resonse const response = { statusCode: 200, body: JSON.stringify({ message: 'Successfully added the note.' }), }; callback(null, response); }); }; ================================================ FILE: aws-node-text-analysis-via-sns-post-processing/analyzeNote.js ================================================ 'use strict'; const sentiment = require('sentiment'); module.exports.analyzeNote = (event) => { const note = event.Records[0].Sns.Message; const result = sentiment(note); if (result.score > 2) { console.log(`Positive note - will be published: ${note}`); } else { console.log(`Negative note - won't be published: ${note}`); } }; ================================================ FILE: aws-node-text-analysis-via-sns-post-processing/config.js ================================================ 'use strict'; module.exports = { awsAccountId: 'XXXXXXXXXXXX', }; ================================================ FILE: aws-node-text-analysis-via-sns-post-processing/package.json ================================================ { "name": "aws-text-analysis-via-sns-post-processing", "version": "1.0.0", "description": "Example demonstrates how to setup a simple data processing pipeline", "author": "", "license": "MIT", "dependencies": { "sentiment": "^2.1.0" } } ================================================ FILE: aws-node-text-analysis-via-sns-post-processing/serverless.yml ================================================ service: text-analysis-via-sns-post-processing frameworkVersion: ">=1.1.0 <2.0.0" provider: name: aws runtime: nodejs12.x region: us-east-1 stage: dev iam: role: statements: - Effect: "Allow" Resource: "*" Action: - "sns:*" functions: addNote: handler: addNote.addNote events: - http: path: notes method: post cors: true analyzeNote: handler: analyzeNote.analyzeNote events: - sns: analyzeNote ================================================ FILE: aws-node-twilio-send-text-message/.gitignore ================================================ node_modules .serverless ================================================ FILE: aws-node-twilio-send-text-message/.npmignore ================================================ # package directories node_modules jspm_packages # Serverless directories .serverless ================================================ FILE: aws-node-twilio-send-text-message/README.md ================================================ # Send SMS Message with Twilio This example demonstrates how to send SMS messages with the Twilio SDK and AWS lambda. [Live the live demo](http://twilio-serverless-example.surge.sh) ## Use Cases: * Sending users confirmation text messages ## Setup 1. Sign up for a [Twilio account](http://www.twilio.com) 2. Create a [new phone number](https://www.twilio.com/console/phone-numbers/) in your Twilio trial account 3. Grab your ACCOUNT SID and AUTH TOKEN from the [Twilio console](https://www.twilio.com/console) and plug those into the `serverless.yml` file in the next step 4. Set your `env` variables in `serverless.yml` with your Twilio account values ```yml environment: # replace these env variables with your twilio account values TWILIO_ACCOUNT_SID: YOUR-TWILIO-ACCOUNT-SID-HERE TWILIO_AUTH_TOKEN: YOUR-TWILIO-AUTH-TOKEN-HERE TWILIO_PHONE_NUMBER: YOUR-TWILIO-PHONE-NUMBER-HERE ``` If you want to use encrypted API keys, see our [encrypted environment variables example](https://github.com/serverless/examples/tree/master/aws-node-env-variables-encrypted-in-a-file) 5. Install the dependencies required by the service ```bash npm i --only=prod ``` 6. Deploy the service ```bash serverless deploy ``` 7. Invoke the function and send an SMS message Update the `to` phone number the `event.json` file and `message` to send in the SMS Then invoke the function with the serverless CLI. Set the `--path event.json` so the function knows where to send the SMS. ```bash serverless invoke -f sendText --path event.json ``` 8. (Optional) Deploy the front-end application Update the `API_ENDPOINT` variable in the `/frontend/index.html` file and deploy the `/frontend` folder to a static host of your choice. We recommend S3, [netlify](https://www.netlify.com/), or [surge.sh](http://surge.sh/) for quick and easy static site hosting. ================================================ FILE: aws-node-twilio-send-text-message/event.json ================================================ { "body": { "to": "your-number-here", "message": "Welcome to Serverless ⊂◉‿◉つ", "image": "https://c1.staticflickr.com/3/2899/14341091933_1e92e62d12_b.jpg" } } ================================================ FILE: aws-node-twilio-send-text-message/frontend/app.css ================================================ html, body { padding: 0px; margin: 0px; } body { font-family: "proxima-nova", sans-serif; text-align: center; font-size: 14px; font-weight: 100; } h1, h2, h3 { font-weight: 100; } #message { min-height: 40px; font-size: 22px; } #logo img { width: 100px; margin-bottom: 10px; margin-top: 50px; } .inputs { display: flex; flex-direction: column; align-items: center; } .inputs input { width: 300px; margin-bottom: 15px; text-align: center; font-size: 20px; padding: 10px; } .btn { font-size: 16px; letter-spacing: 1px; border: 0; background-color: #16214D; color: white; cursor: pointer; min-height: 30px; min-width: 200px; } .btn:hover { background-color: #44C7F4; } .btn:focus { outline: none !important; } .btn:disabled { background-color: #333; color: #666; } .api-actions, .user-actions { display: flex; align-items: center; justify-content: center; margin-bottom: 30px; } .api-actions button:last-of-type, .user-actions button:last-of-type { margin-left: 20px; } @media (max-width: 768px) { .api-actions, .user-actions { flex-direction: column; } .api-actions button:last-of-type, .user-actions button:last-of-type { margin-left: 0px; margin-top: 20px; } } ================================================ FILE: aws-node-twilio-send-text-message/frontend/index.html ================================================

Twilio Serverless Example

View the source on github
================================================ FILE: aws-node-twilio-send-text-message/handler.js ================================================ const Messenger = require('./messenger.js'); const twilioAccountSid = process.env.TWILIO_ACCOUNT_SID; const twilioAuthToken = process.env.TWILIO_AUTH_TOKEN; const twilioClient = require('twilio')(twilioAccountSid, twilioAuthToken); // eslint-disable-line module.exports.sendText = (event, context, callback) => { const messenger = new Messenger(twilioClient); const response = { headers: { 'Access-Control-Allow-Origin': '*' }, // CORS requirement statusCode: 200, }; Object.assign(event, { from: process.env.TWILIO_PHONE_NUMBER }); messenger.send(event) .then((message) => { // text message sent! ✅ console.log(`message ${message.body}`); console.log(`date_created: ${message.date_created}`); response.body = JSON.stringify({ message: 'Text message successfully sent!', data: message, }); callback(null, response); }) .catch((error) => { response.statusCode = error.status; response.body = JSON.stringify({ message: error.message, error: error, // eslint-disable-line }); callback(null, response); }); }; ================================================ FILE: aws-node-twilio-send-text-message/messenger.js ================================================ 'use strict'; class Messenger { constructor(client) { this.client = client; } send(event) { // use twilio SDK to send text message const sms = { to: event.body.to, body: event.body.message || '', from: event.from, }; // add image to sms if supplied if (event.body.image) { sms.mediaUrl = event.body.image; } return this.client.messages.create(sms); } } module.exports = Messenger; ================================================ FILE: aws-node-twilio-send-text-message/messenger.test.js ================================================ 'use strict'; const expect = require('chai').expect; // eslint-disable-line const sinon = require('sinon'); // eslint-disable-line const Messenger = require('./messenger.js'); const createMessageStub = sinon.stub().returns(Promise.resolve({})); const client = { messages: { create: createMessageStub, }, }; const event = { body: { to: '+112345', messege: 'serverless rocks', image: 'cats.jpg', }, from: '+154321', }; describe('Messenger', () => { // eslint-disable-line it('should send messages', () => { // eslint-disable-line const messenger = new Messenger(client); messenger.send(event) .then(() => { expect(createMessageStub.called).to.be.true; // eslint-disable-line }); }); }); ================================================ FILE: aws-node-twilio-send-text-message/package.json ================================================ { "name": "aws-node-twilio-send-text-message", "version": "0.0.1", "description": "Send a text message via twilio from aws lambda. [See live demo](http://twilio-serverless-example.surge.sh)", "main": "handler.js", "scripts": { "test": "./node_modules/.bin/mocha *.test.js" }, "author": "David Wells", "license": "MIT", "dependencies": { "twilio": "^3.65.0" }, "devDependencies": { "chai": "^4.0.0", "mocha": "^9.0.2", "sinon": "^2.3.2" } } ================================================ FILE: aws-node-twilio-send-text-message/serverless.yml ================================================ service: aws-node-twilio provider: name: aws runtime: nodejs12.x environment: # replace these env variables with your twilio account values TWILIO_ACCOUNT_SID: YOUR-TWILIO-ACCOUNT-SID-HERE TWILIO_AUTH_TOKEN: YOUR-TWILIO-AUTH-TOKEN-HERE TWILIO_PHONE_NUMBER: YOUR-TWILIO-PHONE-NUMBER-HERE package: exclude: - "*.test.js" - 'frontend/**' functions: sendText: handler: handler.sendText events: - http: path: api/sendText method: post integration: lambda cors: true ================================================ FILE: aws-node-twitter-joke-bot/README.md ================================================ # Joke Twitter Bot ## Description This is a Twitter bot that will periodically tweet out a joke obtained from a joke API [https://icanhazdadjoke.com/](https://icanhazdadjoke.com/) ## Installation Run `npm i` to install dependencies ## Setup An environment file is required to contain the secret keys for the Twitter API and the joke API's URL. The naming convention for this file is `[STAGE].env.json`, for example `dev.env.json`. This is the env file that should be placed in the project root. ```json { "JOKES_API_URL":"https://icanhazdadjoke.com/", "TWITTER_CONSUMER_KEY": "YOUR KEY GOES HERE", "TWITTER_CONSUMER_SECRET": "YOUR KEY GOES HERE", "TWITTER_ACCESS_TOKEN_KEY": "YOUR KEY GOES HERE", "TWITTER_ACCESS_TOKEN_SECRET": "YOUR KEY GOES HERE" } ``` ## Testing The bot can be invoked manually during development with the following command ``` sls invoke local -f bot ``` ## Deployment - Both the `STAGE` and `REGION` options can be used with this bot. If left out the bot will default to `dev` stage and `eu-west-1` region. Deploy command ``` sls deploy --stage dev --region eu-west-1 ``` ## Notes The API request in `helpers/jokes.js` sets a custom User-Agent header to identify users to the joke API owners. Please customise this to your library name. ================================================ FILE: aws-node-twitter-joke-bot/handler.js ================================================ 'use strict'; const { getDadJoke } = require('./helpers/jokes'); const { tweetJoke } = require('./helpers/twitter'); module.exports.bot = (event, context, callback) => { // Call jokes api to get a joke getDadJoke() // eslint-disable-next-line consistent-return .then((json) => { // Check for a successful response // Bail if not! if (json.status !== 200) { return callback(null, { statusCode: json.status, body: JSON.stringify({ error: 'Could not fetch a joke' }) }); } // Get the joke text const { joke } = json; console.log(`JOKE API RESPONSE ==> ${joke}`); // Set up the twitter module // Tweet the joke tweetJoke(joke) .then((response) => { console.log(JSON.stringify(response)); // eslint-disable-next-line max-len return callback(null, { statusCode: json.status, body: JSON.stringify({ message: response }) }); }) .catch((error) => { console.error(error); return callback(null, { statusCode: 500, body: JSON.stringify({ error }) }); }); }) .catch((error) => { console.error(error); return callback(null, { statusCode: 500, body: JSON.stringify({ error }) }); }); }; ================================================ FILE: aws-node-twitter-joke-bot/helpers/jokes.js ================================================ const fetch = require('node-fetch'); const options = { headers: { Accept: 'application/json', 'User-Agent': 'My Library (YOUR LIBRARY URL HERE)', }, }; const getDadJoke = () => new Promise((resolve, reject) => { fetch(process.env.JOKES_API_URL, options) .then(response => response.json()) .then(json => resolve(json)) .catch(error => reject(error)); }); module.exports = { getDadJoke, }; ================================================ FILE: aws-node-twitter-joke-bot/helpers/twitter.js ================================================ const Twitter = require('twitter'); const client = new Twitter({ consumer_key: process.env.TWITTER_CONSUMER_KEY, consumer_secret: process.env.TWITTER_CONSUMER_SECRET, access_token_key: process.env.TWITTER_ACCESS_TOKEN_KEY, access_token_secret: process.env.TWITTER_ACCESS_TOKEN_SECRET, }); const tweetJoke = joke => new Promise((resolve, reject) => { client.post('statuses/update', { status: joke }) .then(tweet => resolve(tweet)) .catch(error => reject(error)); }); module.exports = { tweetJoke, }; ================================================ FILE: aws-node-twitter-joke-bot/package.json ================================================ { "name": "baddadjokesbot", "version": "1.0.0", "description": "", "main": "handler.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { "type": "git", "url": "git+https://github.com/Fixy250185/baddadjokesbot.git" }, "keywords": [], "author": "", "license": "ISC", "bugs": { "url": "https://github.com/Fixy250185/baddadjokesbot/issues" }, "homepage": "https://github.com/Fixy250185/baddadjokesbot#readme", "dependencies": { "node-fetch": "^2.2.1", "twitter": "^1.7.1" } } ================================================ FILE: aws-node-twitter-joke-bot/serverless.yml ================================================ service: baddadjokesbot provider: name: aws runtime: nodejs12.x stage: ${opt:stage, 'dev'} region: ${opt:region, 'eu-west-1'} custom: env: ${file(./${self:provider.stage}.env.json)} functions: bot: handler: handler.bot events: - schedule: rate(5 hours) environment: JOKES_API_URL: ${self:custom.env.JOKES_API_URL} TWITTER_CONSUMER_KEY: ${self:custom.env.TWITTER_CONSUMER_KEY} TWITTER_CONSUMER_SECRET: ${self:custom.env.TWITTER_CONSUMER_SECRET} TWITTER_ACCESS_TOKEN_KEY: ${self:custom.env.TWITTER_ACCESS_TOKEN_KEY} TWITTER_ACCESS_TOKEN_SECRET: ${self:custom.env.TWITTER_ACCESS_TOKEN_SECRET} ================================================ FILE: aws-node-typescript-apollo-lambda/.gitignore ================================================ # files .DS_Store .env* .vscode *.log # package directories node_modules jspm_packages .build # Serverless directories .serverless # Webpack directories .webpack ================================================ FILE: aws-node-typescript-apollo-lambda/README.md ================================================ # Apollo Lambda GraphQL API Example This example demonstrates how to setup a lambda graphql API with apollo - I used apiKeys to secure the endpoints but you can add custom authorizers ## Use Cases - Small graphql API - Creating a temporary lambda API that can easily be converted to standard GraphQL API ## Setup - Setup your env file for AWS deployment with: - - APOLLO_LAMBDA_KEY - - NODE_ENV - sls deploy ## Usage - To test it locally with serverless-offline by running: `npm run dev` - set `x-api-key` header with key `your-api-key-that-is-at-least-characters-long` ## Future - Add support for subscription with Redis ================================================ FILE: aws-node-typescript-apollo-lambda/package.json ================================================ { "name": "aws-node-typescript-apollo-lambda", "version": "1.0.0", "description": "Serverless example for apollo lambda", "main": "handler.js", "scripts": { "deploy": "serverless deploy", "dev": "serverless offline --apiKey your-api-key-that-is-at-least-characters-long", "lint": "tslint -p tsconfig.json -c tslint.json" }, "dependencies": { "apollo-server-lambda": "^2.16.1", "graphql": "^15.3.0", "source-map-support": "^0.5.10" }, "devDependencies": { "@types/aws-lambda": "^8.10.17", "@types/node": "^10.14.22", "eslint": "^6.6.0", "serverless": "^1.79.0", "serverless-dotenv-plugin": "^3.0.0", "serverless-offline": "^6.5.0", "serverless-plugin-typescript": "^1.1.9", "ts-loader": "^5.3.3", "tslint": "^5.20.0", "tslint-config-airbnb": "^5.11.2", "typescript": "^3.2.4" }, "author": "Miguel Frazao (https://github.com/jmpfrazao/aws-node-typescript-apollo-lambda)", "license": "MIT" } ================================================ FILE: aws-node-typescript-apollo-lambda/serverless.yml ================================================ service: name: aws-node-typescript-apollo-lambda plugins: - serverless-dotenv-plugin - serverless-offline - serverless-plugin-typescript provider: name: aws runtime: nodejs12.x timeout: 15 apiKeys: - name: lambdaApolloKey value: ${env:APOLLO_LAMBDA_KEY} functions: graphql: handler: src/handler.graphqlHandler events: - http: private: true path: graphql method: post - http: private: true path: graphql method: get ================================================ FILE: aws-node-typescript-apollo-lambda/src/graphql/apolloServer.ts ================================================ import { ApolloServer, IResolvers } from 'apollo-server-lambda'; import * as queries from './resolvers/queries'; import * as mutations from './resolvers/mutations'; import typeDefs from './type-defs'; const NODE_ENV = process.env.NODE_ENV; const IS_DEV = !NODE_ENV || !['production'].includes(NODE_ENV); const resolvers = { Mutation: mutations, Query: queries, } as IResolvers; const apolloServer = new ApolloServer({ typeDefs, resolvers, // subscriptions: {}, introspection: IS_DEV, // context: {}, }); export default apolloServer.createHandler(); ================================================ FILE: aws-node-typescript-apollo-lambda/src/graphql/resolvers/mutations/dummyMutation.ts ================================================ import { IDummyMutationArgs } from '../typings'; async function dummyMutation( _: any, args: IDummyMutationArgs, ): Promise { const { input: { firstInput, secondInput } } = args; console.log(`Mutation with inputs firstInput=${firstInput} and secondInput=${secondInput}`); return true; } export default dummyMutation; ================================================ FILE: aws-node-typescript-apollo-lambda/src/graphql/resolvers/mutations/index.ts ================================================ export { default as dummyMutation } from './dummyMutation'; ================================================ FILE: aws-node-typescript-apollo-lambda/src/graphql/resolvers/queries/dummyQuery.ts ================================================ import { IDummyObject, IDummyQueryArgs } from '../typings'; async function dummyQuery( _: any, args: IDummyQueryArgs, ): Promise { const { itemId } = args; console.log(`Query object with id ${itemId}`); return { firstItem: 'first', secondItem: 'second', }; } export default dummyQuery; ================================================ FILE: aws-node-typescript-apollo-lambda/src/graphql/resolvers/queries/index.ts ================================================ export { default as dummyQuery } from './dummyQuery'; ================================================ FILE: aws-node-typescript-apollo-lambda/src/graphql/resolvers/typings.ts ================================================ export interface IDummyObject { firstItem: string; secondItem: string; } export interface IDummyQueryArgs { itemId: string; } export interface IDummyMutationArgs { input: { firstInput: string; secondInput: string; }; } ================================================ FILE: aws-node-typescript-apollo-lambda/src/graphql/type-defs/index.ts ================================================ import { gql } from 'apollo-server-lambda'; // Inputs import DummyInput from './inputs/DummyInput'; // Objects import DummyObject from './objects/DummyObject'; // Root types import Mutation from './root/Mutation'; // tslint:disable-line ordered-imports import Query from './root/Query'; // tslint:disable-line ordered-imports const typeDefStrings = [ // Inputs DummyInput, // Objects DummyObject, // Root types Mutation, Query, ]; const typeDefs = typeDefStrings.map(typeDef => gql(typeDef)); export default typeDefs; ================================================ FILE: aws-node-typescript-apollo-lambda/src/graphql/type-defs/inputs/DummyInput.ts ================================================ export default ` input DummyInput { firstInput: String! secondInput: String! } `; ================================================ FILE: aws-node-typescript-apollo-lambda/src/graphql/type-defs/objects/DummyObject.ts ================================================ export default ` type DummyObject { firstItem: String! secondItem: String! } `; ================================================ FILE: aws-node-typescript-apollo-lambda/src/graphql/type-defs/root/Mutation.ts ================================================ export default ` type Mutation { dummyMutation(input: DummyInput!): Boolean! } `; ================================================ FILE: aws-node-typescript-apollo-lambda/src/graphql/type-defs/root/Query.ts ================================================ export default ` type Query { dummyQuery(itemId: ID!): DummyObject! } `; ================================================ FILE: aws-node-typescript-apollo-lambda/src/handler.ts ================================================ export { default as graphqlHandler } from './graphql/apolloServer'; ================================================ FILE: aws-node-typescript-apollo-lambda/tsconfig.json ================================================ { "compilerOptions": { "allowSyntheticDefaultImports": true, "emitDecoratorMetadata": true , "esModuleInterop": true, "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ "lib": ["esnext"], "module": "commonjs", "moduleResolution": "node", "outDir": "lib", "sourceMap": true, "target": "esnext" }, "include": [ "src/**/*", "declarations.d.ts" ], "exclude": [ "node_modules/**/*", ".serverless/**/*", ".webpack/**/*", ".vscode/**/*" ] } ================================================ FILE: aws-node-typescript-apollo-lambda/tslint.json ================================================ { "extends": ["tslint-config-airbnb"] } ================================================ FILE: aws-node-typescript-kinesis/.gitignore ================================================ # package directories node_modules jspm_packages # Serverless directories .serverless # Webpack directories .webpack ================================================ FILE: aws-node-typescript-kinesis/README.md ================================================ # Simple Kinesis Example This example demonstrates how to setup a Kinesis producer and consumer to send and receive messages through a Kinesis Data Stream. ## Use Cases - Decouple message producers from message consumers. - This is one way to architect for scale and reliability. - Real-time processing of streaming data ## Setup - sls deploy ## Usage - To send a message to the producer, get the address from your sls deploy output. ``` Serverless: Stack update finished... Service Information service: aws-node-typescript-kinesis stage: dev region: us-east-1 stack: aws-node-typescript-kinesis-dev resources: 16 api keys: None endpoints: POST - https://xxx.execute-api.us-east-1.amazonaws.com/dev/producer functions: producer: aws-node-typescript-kinesis-dev-producer consumer: aws-node-typescript-kinesis-dev-consumer layers: None ``` - To print out the logs of the Kinesis consumer handler on the terminal `sls logs -f consumer -t` - send a HTTP POST request to the producer lambda ``` curl -d "{ 'key': 'employee', 'value': 'Bill' }" \ https://xxx.execute-api.us-east-1.amazonaws.com/dev/producer ``` - You should see confirmation that the message was sent. `{"message":"Message placed in the Event Stream!"}` - The logs from the consumer will be delayed several seconds. ``` INFO Kinesis Message: partition key: eb2da704-4972-4bd7-8c25-cce1decce95d sequence number: 49608726715828497972227004620876254203171519877947064322 kinesis schema version: 1.0 data: { 'key': 'employee', 'value': 'Bill' } ``` ## Acknowledgements Adapted from Miguel Frazao's [SQS Standard example](https://github.com/serverless/examples/tree/master/aws-node-typescript-sqs-standard). ================================================ FILE: aws-node-typescript-kinesis/handler.ts ================================================ export { default as producer } from './kinesis/producer'; export { default as consumer } from './kinesis/consumer'; ================================================ FILE: aws-node-typescript-kinesis/kinesis/consumer.ts ================================================ import { KinesisStreamHandler, KinesisStreamRecordPayload, } from 'aws-lambda'; const consumer: KinesisStreamHandler = async (event) => { try { for (const record of event.Records) { const payload: KinesisStreamRecordPayload = record.kinesis; const message: string = Buffer.from(payload.data, 'base64').toString(); console.log( `Kinesis Message: partition key: ${payload.partitionKey} sequence number: ${payload.sequenceNumber} kinesis schema version: ${payload.kinesisSchemaVersion} data: ${message} `); // Do something } } catch (error) { console.log(error); } }; export default consumer; ================================================ FILE: aws-node-typescript-kinesis/kinesis/producer.ts ================================================ import { APIGatewayProxyHandler } from 'aws-lambda'; import { Kinesis } from 'aws-sdk'; import { v4 as uuidv4 } from 'uuid'; const kinesis = new Kinesis({ apiVersion: '2013-12-02', }); const producer: APIGatewayProxyHandler = async (event) => { let statusCode: number = 200; let message: string; if (!event.body) { return { statusCode: 400, body: JSON.stringify({ message: 'No body was found', }), }; } const streamName: string = 'eventStream'; try { await kinesis.putRecord({ StreamName: streamName, PartitionKey: uuidv4(), Data: event.body, }).promise(); message = 'Message placed in the Event Stream!'; } catch (error) { console.log(error); message = error; statusCode = 500; } return { statusCode, body: JSON.stringify({ message, }), }; }; export default producer; ================================================ FILE: aws-node-typescript-kinesis/package.json ================================================ { "name": "aws-node-typescript-kinesis", "version": "1.0.0", "description": "Serverless example using Kinesis with TypeScript", "main": "producer.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "lint": "tslint -p tsconfig.json -c tslint.json" }, "dependencies": { "serverless-webpack": "^5.2.0", "aws-lambda": "^1.0.5", "aws-sdk": "^2.553.0", "source-map-support": "^0.5.10", "uuid": "^8.2.0" }, "devDependencies": { "@types/aws-lambda": "^8.10.17", "@types/node": "^10.14.22", "eslint": "^6.6.0", "ts-loader": "^5.3.3", "tslint": "^5.20.0", "tslint-config-airbnb": "^5.11.2", "typescript": "^3.2.4", "webpack": "^4.29.0" }, "author": "Bill Kidwell (https://github.com/billkidwell/aws-node-typescript-kinesis)", "license": "MIT" } ================================================ FILE: aws-node-typescript-kinesis/serverless.yml ================================================ service: name: aws-node-typescript-kinesis plugins: - serverless-webpack provider: name: aws runtime: nodejs12.x iam: role: statements: - Effect: Allow Action: - kinesis:PutRecord - kinesis:PutRecords Resource: - Fn::GetAtt: [ kinesisStream, Arn ] functions: producer: handler: handler.producer events: - http: method: post path: producer consumer: handler: handler.consumer events: - stream: type: kinesis arn: Fn::GetAtt: - kinesisStream - Arn resources: Resources: kinesisStream: Type: AWS::Kinesis::Stream Properties: Name: eventStream RetentionPeriodHours: 24 ShardCount: 1 ================================================ FILE: aws-node-typescript-kinesis/tsconfig.json ================================================ { "compilerOptions": { "allowSyntheticDefaultImports": true, "esModuleInterop": true, "lib": ["esnext"], "module": "commonjs", "moduleResolution": "node", "noUnusedLocals": true, "noUnusedParameters": true, "outDir": "lib", "sourceMap": true, "target": "esnext" }, "exclude": ["node_modules"] } ================================================ FILE: aws-node-typescript-kinesis/tslint.json ================================================ { "extends": ["tslint-config-airbnb"] } ================================================ FILE: aws-node-typescript-kinesis/webpack.config.js ================================================ const path = require('path'); const slsw = require('serverless-webpack'); module.exports = { mode: slsw.lib.webpack.isLocal ? 'development' : 'production', entry: slsw.lib.entries, devtool: 'source-map', resolve: { extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'], }, output: { libraryTarget: 'commonjs', path: path.join(__dirname, '.webpack'), filename: '[name].js', }, target: 'node', module: { rules: [ // all files with a `.ts` or `.tsx` extension will be handled by `ts-loader` { test: /\.tsx?$/, loader: 'ts-loader' }, ], }, }; ================================================ FILE: aws-node-typescript-nest/.gitignore ================================================ /node_modules/ /.build/ /.serverless/ /_warmup/ ================================================ FILE: aws-node-typescript-nest/.prettierrc ================================================ { "singleQuote": true, "trailingComma": "all" } ================================================ FILE: aws-node-typescript-nest/README.md ================================================ # Nest application example This example demonstrates how to setup a [Nest](https://github.com/nestjs/nest) application. ## Use Cases - Setup & deploy a [Nest Application starter](https://github.com/nestjs/typescript-starter) ## Running the app locally ```bash npm start ``` Which should result in: ```bash $ sls offline start Serverless: Compiling with Typescript... Serverless: Using local tsconfig.json Serverless: Typescript compiled. Serverless: Watching typescript files... Serverless: Starting Offline: dev/us-east-1. Serverless: Routes for main: Serverless: ANY /{proxy*} Serverless: Offline listening on http://localhost:3000 ``` Then browse http://localhost:3000/hello The logs should be : ```bash Serverless: ANY /hello (λ: main) [Nest] 7956 - 2018-12-13 10:34:22 [NestFactory] Starting Nest application... +6933ms [Nest] 7956 - 2018-12-13 10:34:22 [InstanceLoader] AppModule dependencies initialized +4ms [Nest] 7956 - 2018-12-13 10:34:22 [RoutesResolver] AppController {/}: +2ms [Nest] 7956 - 2018-12-13 10:34:22 [RouterExplorer] Mapped {/hello, GET} route +1ms [Nest] 7956 - 2018-12-13 10:34:22 [NestApplication] Nest application successfully started +1ms Serverless: [200] {"statusCode":200,"body":"Hello World!","headers":{"x-powered-by":"Express","content-type":"text/html; charset=utf-8","content-length":"12","etag":"W/\"c-Lve95gjOVATpfV8EL5X4nxwjKHE\"","date":"Thu, 13 Dec 2018 09:34:22 GMT","connection":"keep-alive"},"isBase64Encoded":false} ``` ### Skiping cache invalidation Skiping cache invalidation is the same behavior as a deployed function ```bash npm start -- --skipCacheInvalidation ``` ## Deploy In order to deploy the endpoint, simply run: ```bash sls deploy ``` The expected result should be similar to: ```bash $ sls deploy Serverless: Compiling with Typescript... Serverless: Using local tsconfig.json Serverless: Typescript compiled. Serverless: Packaging service... Serverless: Excluding development dependencies... Serverless: Creating Stack... Serverless: Checking Stack create progress... ..... Serverless: Stack create finished... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading artifacts... Serverless: Uploading service .zip file to S3 (32.6 MB)... Serverless: Validating template... Serverless: Updating Stack... Serverless: Checking Stack update progress... .............................. Serverless: Stack update finished... Service Information service: serverless-nest-example stage: dev region: us-east-1 stack: serverless-nest-example-dev api keys: None endpoints: ANY - https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/{proxy?} functions: main: serverless-nest-example-dev-main layers: None ``` ## Usage Send an HTTP request directly to the endpoint using a tool like curl ```bash curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/hello ``` ## Tail logs ```bash sls logs --function main --tail ``` ## Scaling By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 100. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit). ## Cold start Cold start may cause latencies for your application See : https://serverless.com/blog/keep-your-lambdas-warm/ These behavior can be fixed with the plugin [serverless-plugin-warmup](https://www.npmjs.com/package/serverless-plugin-warmup) 1. Install the plugin ```bash npm install serverless-plugin-warmup --save-dev ``` 2. Enable the plugin ```yaml plugins: - '@hewmen/serverless-plugin-typescript' - serverless-plugin-optimize - serverless-offline - serverless-plugin-warmup custom: # Enable warmup on all functions (only for production and staging) warmup: - production - staging ``` ## Benchmark A basic benchmark script can be used locally, it performs 1000 "GET" requests on "http://localhost:3000/hello" ```bash # /!\ The app must run locally npm start # Or npm start -- --skipCacheInvalidation for better performances # Run bench node bench.js ``` The expected result should be similar to: ```bash $ node bench.js 1000 "GET" requests to "http://localhost:3000/hello" total: 8809.733ms Average: 8.794ms ``` ================================================ FILE: aws-node-typescript-nest/bench.js ================================================ const http = require('http'); const url = 'http://localhost:3000/hello'; const nbRequests = 1000; const bench = async () => { console.info(nbRequests + ' "GET" requests to "' + url + '"'); console.time('Total'); const results = []; for (let i = 1; i <= nbRequests; i++) { await new Promise((resolve, reject) => { var begin = Date.now(); http.get(url, (resp) => { let data = ''; // A chunk of data has been recieved. resp.on('data', (chunk) => { data += chunk; }); // The whole response has been received. Calculate the time spent resp.on('end', () => { results.push(Date.now() - begin); resolve(); }); }).on('error', (error) => { reject(error); }); }); } console.timeEnd('Total'); console.info('Average: ', results.reduce((p, c) => { return p + c; }) / results.length + 'ms'); }; bench(); ================================================ FILE: aws-node-typescript-nest/nest-cli.json ================================================ { "language": "ts", "collection": "@nestjs/schematics", "sourceRoot": "src" } ================================================ FILE: aws-node-typescript-nest/nodemon-debug.json ================================================ { "watch": ["src"], "ext": "ts", "ignore": ["src/**/*.spec.ts"], "exec": "node --inspect-brk -r ts-node/register -r tsconfig-paths/register src/main.ts" } ================================================ FILE: aws-node-typescript-nest/nodemon.json ================================================ { "watch": ["src"], "ext": "ts", "ignore": ["src/**/*.spec.ts"], "exec": "ts-node -r tsconfig-paths/register src/main.ts" } ================================================ FILE: aws-node-typescript-nest/package.json ================================================ { "name": "nest-serverless", "version": "0.0.0", "description": "serverless app", "author": "ouistiti-dev", "license": "MIT", "scripts": { "build": "tsc -p tsconfig.build.json", "format": "prettier --write \"src/**/*.ts\"", "start": "sls offline start", "lint": "tslint -p tsconfig.json -c tslint.json", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^5.4.0", "@nestjs/core": "^5.4.0", "aws-serverless-express": "^3.3.5", "reflect-metadata": "^0.1.12", "rimraf": "^2.6.2", "rxjs": "^6.2.2", "typescript": "^3.0.1" }, "devDependencies": { "@hewmen/serverless-plugin-typescript": "^1.1.17", "@nestjs/testing": "^5.1.0", "@types/aws-lambda": "^8.10.15", "@types/express": "^4.16.0", "@types/jest": "^23.3.1", "@types/node": "^10.7.1", "@types/supertest": "^2.0.5", "jest": "^23.5.0", "nodemon": "^1.18.3", "prettier": "^1.14.2", "serverless-offline": "^3.31.3", "serverless-plugin-optimize": "^4.0.2-rc.1", "supertest": "^3.1.0", "ts-jest": "^23.1.3", "ts-loader": "^4.4.2", "ts-node": "^7.0.1", "tsconfig-paths": "^3.5.0", "tslint": "5.11.0" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "coverageDirectory": "../coverage", "testEnvironment": "node" } } ================================================ FILE: aws-node-typescript-nest/serverless.yml ================================================ service: serverless-nest-example plugins: - '@hewmen/serverless-plugin-typescript' - serverless-plugin-optimize - serverless-offline # - serverless-plugin-warmup # custom: # # Enable warmup on all functions (only for production and staging) # warmup: # - production # - staging provider: name: aws runtime: nodejs12.x package: individually: true functions: main: handler: src/main.handler events: - http: method: any path: /{proxy+} ================================================ FILE: aws-node-typescript-nest/src/app.controller.spec.ts ================================================ import { Test, TestingModule } from '@nestjs/testing'; import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { let app: TestingModule; beforeAll(async () => { app = await Test.createTestingModule({ controllers: [AppController], providers: [AppService], }).compile(); }); describe('root', () => { it('should return "Hello World!"', () => { const appController = app.get(AppController); expect(appController.getHello()).toBe('Hello World!'); }); }); }); ================================================ FILE: aws-node-typescript-nest/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get('hello') getHello(): string { return this.appService.getHello(); } } ================================================ FILE: aws-node-typescript-nest/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @Module({ imports: [], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: aws-node-typescript-nest/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { getHello(): string { return 'Hello World!'; } } ================================================ FILE: aws-node-typescript-nest/src/main.ts ================================================ import { Handler, Context } from 'aws-lambda'; import { Server } from 'http'; import { createServer, proxy } from 'aws-serverless-express'; import { eventContext } from 'aws-serverless-express/middleware'; import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; // NOTE: If you get ERR_CONTENT_DECODING_FAILED in your browser, this is likely // due to a compressed response (e.g. gzip) which has not been handled correctly // by aws-serverless-express and/or API Gateway. Add the necessary MIME types to // binaryMimeTypes below const binaryMimeTypes: string[] = []; let cachedServer: Server; process.on('unhandledRejection', (reason) => { console.error(reason); }); process.on('uncaughtException', (reason) => { console.error(reason); }); async function bootstrapServer(): Promise { if (!cachedServer) { try { const expressApp = require('express')(); const nestApp = await NestFactory.create(AppModule, expressApp); nestApp.use(eventContext()); await nestApp.init(); cachedServer = createServer(expressApp, undefined, binaryMimeTypes); } catch (error) { return Promise.reject(error); } } return Promise.resolve(cachedServer); } export const handler: Handler = async (event: any, context: Context) => { cachedServer = await bootstrapServer(); return proxy(cachedServer, event, context, 'PROMISE').promise; } ================================================ FILE: aws-node-typescript-nest/test/app.e2e-spec.ts ================================================ import { INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; describe('AppController (e2e)', () => { let app: INestApplication; beforeAll(async () => { const moduleFixture = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); it('/hello (GET)', () => { return request(app.getHttpServer()) .get('/hello') .expect(200) .expect('Hello World!'); }); }); ================================================ FILE: aws-node-typescript-nest/test/jest-e2e.json ================================================ { "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } } ================================================ FILE: aws-node-typescript-nest/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "include": ["src/**/*"], "exclude": ["node_modules", "**/*.spec.ts"] } ================================================ FILE: aws-node-typescript-nest/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "declaration": true, "noImplicitAny": false, "removeComments": true, "noLib": false, "allowSyntheticDefaultImports": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "target": "es6", "sourceMap": true, "outDir": "./dist", "baseUrl": "./" }, "exclude": ["node_modules"] } ================================================ FILE: aws-node-typescript-nest/tsconfig.spec.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "types": ["jest", "node"] }, "include": ["**/*.spec.ts", "**/*.d.ts"] } ================================================ FILE: aws-node-typescript-nest/tslint.json ================================================ { "defaultSeverity": "error", "extends": ["tslint:recommended"], "jsRules": { "no-unused-expression": true }, "rules": { "quotemark": [true, "single"], "member-access": [false], "ordered-imports": [false], "max-line-length": [true, 150], "member-ordering": [false], "interface-name": [false], "arrow-parens": false, "object-literal-sort-keys": false }, "rulesDirectory": [] } ================================================ FILE: aws-node-typescript-rest-api-with-dynamodb/.gitignore ================================================ *.js ================================================ FILE: aws-node-typescript-rest-api-with-dynamodb/README.md ================================================ # Introduction TypeScript (ts) offers type safety which is helpful when working with the AWS SDK, which comes with ts definitions (d.ts) # compiling You can compile the ts files in this directory by 1st installing typescript via `npm install -g typescript` then `npm i` You can then run the compiler by running `tsc` in this directory. It will pull the settings from .tsconfig and extra @types from package.json. The output create.js file is what will be uploaded by serverless. For brevity, I have just demonstrated this to match with the todos/create.js, todos/list.js, todos/get.js and todos/update.js lambda function ## Usage You can create, retrieve, update, or delete todos with the following commands: ### Create a Todo ```bash curl -X POST https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/todos --data '{ "text": "Learn Serverless" }' ``` Example Result: ```bash {"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":false,"updatedAt":1479138570824}% ``` ### List all Todos ```bash curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/todos ``` Example output: ```bash [{"text":"Deploy my first service","id":"ac90feaa11e6-9ede-afdfa051af86","checked":true,"updatedAt":1479139961304},{"text":"Learn Serverless","id":"206793aa11e6-9ede-afdfa051af86","createdAt":1479139943241,"checked":false,"updatedAt":1479139943241}]% ``` ### Get one Todo ```bash # Replace the part with a real id from your todos table curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/todos/ ``` Example Result: ```bash {"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":false,"updatedAt":1479138570824}% ``` ### Update a Todo ```bash # Replace the part with a real id from your todos table curl -X PUT https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/todos/ --data '{ "text": "Learn Serverless", "checked": true }' ``` Example Result: ```bash {"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":true,"updatedAt":1479138570824}% ``` ================================================ FILE: aws-node-typescript-rest-api-with-dynamodb/package.json ================================================ { "name": "typescript-example", "version": "0.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "watch": "tsc -w", "lint": "tslint '*.ts'" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "@types/aws-sdk": "0.0.42", "@types/node": "^7.0.5", "tslint": "^5.5.0", "tslint-config-standard": "^6.0.1", "typescript": "^2.4.2" }, "dependencies": { "uuid": "^3.1.0" } } ================================================ FILE: aws-node-typescript-rest-api-with-dynamodb/serverless.yml ================================================ service: sls-typescript-rest-api-with-dynamodb frameworkVersion: ">=1.1.0 <2.0.0" provider: name: aws runtime: nodejs12.x region: eu-west-2 environment: DYNAMODB_TABLE: ${self:service}-${opt:stage, self:provider.stage} iam: role: statements: - Effect: Allow Action: - dynamodb:Query - dynamodb:Scan - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}" functions: create: handler: todos/create.create events: - http: path: todos method: post cors: true list: handler: todos/list.list events: - http: path: todos method: get cors: true get: handler: todos/get.get events: - http: path: todos/{id} method: get cors: true update: handler: todos/update.update events: - http: path: todos/{id} method: put cors: true resources: Resources: TodosDynamoDbTable: Type: 'AWS::DynamoDB::Table' DeletionPolicy: Retain Properties: AttributeDefinitions: - AttributeName: id AttributeType: S KeySchema: - AttributeName: id KeyType: HASH ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 TableName: ${self:provider.environment.DYNAMODB_TABLE} ================================================ FILE: aws-node-typescript-rest-api-with-dynamodb/todos/create.ts ================================================ 'use strict' import * as uuid from 'uuid' import { DynamoDB } from 'aws-sdk' const dynamoDb = new DynamoDB.DocumentClient() module.exports.create = (event, context, callback) => { const timestamp = new Date().getTime() const data = JSON.parse(event.body) if (typeof data.text !== 'string') { console.error('Validation Failed') callback(new Error('Couldn\'t create the todo item.')) return } const params = { TableName: process.env.DYNAMODB_TABLE, Item: { id: uuid.v1(), text: data.text, checked: false, createdAt: timestamp, updatedAt: timestamp } } // write the todo to the database dynamoDb.put(params, (error, result) => { // handle potential errors if (error) { console.error(error) callback(new Error('Couldn\'t create the todo item.')) return } // create a response const response = { statusCode: 200, body: JSON.stringify(params.Item) } callback(null, response) }) } ================================================ FILE: aws-node-typescript-rest-api-with-dynamodb/todos/get.ts ================================================ 'use strict'; import { DynamoDB } from 'aws-sdk' const dynamoDb = new DynamoDB.DocumentClient() module.exports.get = (event, context, callback) => { const params = { TableName: process.env.DYNAMODB_TABLE, Key: { id: event.pathParameters.id, }, }; // fetch todo from the database dynamoDb.get(params, (error, result) => { // handle potential errors if (error) { console.error(error); callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t fetch the todo item.', }); return; } // create a response const response = { statusCode: 200, body: JSON.stringify(result.Item), }; callback(null, response); }); }; ================================================ FILE: aws-node-typescript-rest-api-with-dynamodb/todos/list.ts ================================================ 'use strict'; import { DynamoDB } from 'aws-sdk' const dynamoDb = new DynamoDB.DocumentClient() const params = { TableName: process.env.DYNAMODB_TABLE, }; module.exports.list = (event, context, callback) => { // fetch all todos from the database // For production workloads you should design your tables and indexes so that your applications can use Query instead of Scan. dynamoDb.scan(params, (error, result) => { // handle potential errors if (error) { console.error(error); callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t fetch the todo items.', }); return; } // create a response const response = { statusCode: 200, body: JSON.stringify(result.Items), }; callback(null, response); }); }; ================================================ FILE: aws-node-typescript-rest-api-with-dynamodb/todos/update.ts ================================================ 'use strict'; const AWS = require('aws-sdk'); // eslint-disable-line import/no-extraneous-dependencies const dynamoDb = new AWS.DynamoDB.DocumentClient(); module.exports.update = (event, context, callback) => { const timestamp = new Date().getTime(); const data = JSON.parse(event.body); // validation if (typeof data.text !== 'string' || typeof data.checked !== 'boolean') { console.error('Validation Failed'); callback(null, { statusCode: 400, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t update the todo item.', }); return; } const params = { TableName: process.env.DYNAMODB_TABLE, Key: { id: event.pathParameters.id, }, ExpressionAttributeNames: { '#todo_text': 'text', }, ExpressionAttributeValues: { ':text': data.text, ':checked': data.checked, ':updatedAt': timestamp, }, UpdateExpression: 'SET #todo_text = :text, checked = :checked, updatedAt = :updatedAt', ReturnValues: 'ALL_NEW', }; // update the todo in the database dynamoDb.update(params, (error, result) => { // handle potential errors if (error) { console.error(error); callback(null, { statusCode: error.statusCode || 501, headers: { 'Content-Type': 'text/plain' }, body: 'Couldn\'t fetch the todo item.', }); return; } // create a response const response = { statusCode: 200, body: JSON.stringify(result.Attributes), }; callback(null, response); }); }; ================================================ FILE: aws-node-typescript-rest-api-with-dynamodb/tsconfig.json ================================================ { "compilerOptions": { "target": "es6", "module": "commonjs" }, "exclude": [ "node_modules" ], "types": [ "node", "aws-sdk" ] } ================================================ FILE: aws-node-typescript-rest-api-with-dynamodb/tslint.json ================================================ { "extends": "tslint-config-standard" } ================================================ FILE: aws-node-typescript-sqs-standard/.gitignore ================================================ # package directories node_modules jspm_packages # Serverless directories .serverless # Webpack directories .webpack ================================================ FILE: aws-node-typescript-sqs-standard/README.md ================================================ # Simple SQS Standard Example This example demonstrates how to setup a SQS Standard and send messages through the message body and attributes. ## Use Cases - Decouple message producers from message consumers. - This is one way to architect for scale and reliability. ## Setup - sls deploy ## Usage - To print out the logs of the receiver sqs handler on the terminal `sls logs -f receiver -t` - send a HTTP POST request to the sender lambda with a JSON payload ================================================ FILE: aws-node-typescript-sqs-standard/handler.ts ================================================ export { default as sender } from './sqs/sender'; export { default as receiver } from './sqs/receiver'; ================================================ FILE: aws-node-typescript-sqs-standard/package.json ================================================ { "name": "aws-node-typescript-sqs-standard", "version": "1.0.0", "description": "Serverless example using Standard SQS with TypeScript", "main": "sender.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "lint": "tslint -p tsconfig.json -c tslint.json" }, "dependencies": { "aws-lambda": "^0.1.2", "aws-sdk": "^2.553.0", "source-map-support": "^0.5.10" }, "devDependencies": { "@types/aws-lambda": "^8.10.17", "@types/node": "^10.14.22", "eslint": "^6.6.0", "serverless-webpack": "^5.2.0", "ts-loader": "^5.3.3", "tslint": "^5.20.0", "tslint-config-airbnb": "^5.11.2", "typescript": "^3.2.4", "webpack": "^4.29.0" }, "author": "Miguel Frazao (https://github.com/jmpfrazao/serverless-ts-standard-sqs)", "license": "MIT" } ================================================ FILE: aws-node-typescript-sqs-standard/serverless.yml ================================================ service: name: aws-node-typescript-sqs-standard plugins: - serverless-webpack provider: name: aws runtime: nodejs10.x iam: role: statements: - Effect: Allow Action: - sqs:SendMessage Resource: - Fn::GetAtt: [ receiverQueue, Arn ] functions: sender: handler: handler.sender events: - http: method: post path: sender receiver: handler: handler.receiver events: - sqs: arn: Fn::GetAtt: - receiverQueue - Arn resources: Resources: receiverQueue: Type: AWS::SQS::Queue Properties: QueueName: receiverQueue ================================================ FILE: aws-node-typescript-sqs-standard/sqs/receiver.ts ================================================ import { SQSHandler, SQSMessageAttributes } from 'aws-lambda'; const receiver: SQSHandler = async (event) => { try { for (const record of event.Records) { const messageAttributes: SQSMessageAttributes = record.messageAttributes; console.log('Message Attributtes --> ', messageAttributes.AttributeNameHere.stringValue); console.log('Message Body --> ', record.body); // Do something } } catch (error) { console.log(error); } }; export default receiver; ================================================ FILE: aws-node-typescript-sqs-standard/sqs/sender.ts ================================================ import { APIGatewayProxyHandler } from 'aws-lambda'; import { SQS } from 'aws-sdk'; const sqs = new SQS(); const sender: APIGatewayProxyHandler = async (event, context) => { let statusCode: number = 200; let message: string; if (!event.body) { return { statusCode: 400, body: JSON.stringify({ message: 'No body was found', }), }; } const region = context.invokedFunctionArn.split(':')[3]; const accountId = context.invokedFunctionArn.split(':')[4]; const queueName: string = 'receiverQueue'; const queueUrl: string = `https://sqs.${region}.amazonaws.com/${accountId}/${queueName}` try { await sqs.sendMessage({ QueueUrl: queueUrl, MessageBody: event.body, MessageAttributes: { AttributeNameHere: { StringValue: 'Attribute Value Here', DataType: 'String', }, }, }).promise(); message = 'Message placed in the Queue!'; } catch (error) { console.log(error); message = error; statusCode = 500; } return { statusCode, body: JSON.stringify({ message, }), }; }; export default sender; ================================================ FILE: aws-node-typescript-sqs-standard/tsconfig.json ================================================ { "compilerOptions": { "allowSyntheticDefaultImports": true, "esModuleInterop": true, "lib": ["esnext"], "module": "commonjs", "moduleResolution": "node", "noUnusedLocals": true, "noUnusedParameters": true, "outDir": "lib", "sourceMap": true, "target": "esnext" }, "exclude": ["node_modules"] } ================================================ FILE: aws-node-typescript-sqs-standard/tslint.json ================================================ { "extends": ["tslint-config-airbnb"] } ================================================ FILE: aws-node-typescript-sqs-standard/webpack.config.js ================================================ const path = require('path'); const slsw = require('serverless-webpack'); module.exports = { mode: slsw.lib.webpack.isLocal ? 'development' : 'production', entry: slsw.lib.entries, devtool: 'source-map', resolve: { extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'], }, output: { libraryTarget: 'commonjs', path: path.join(__dirname, '.webpack'), filename: '[name].js', }, target: 'node', module: { rules: [ // all files with a `.ts` or `.tsx` extension will be handled by `ts-loader` { test: /\.tsx?$/, loader: 'ts-loader' }, ], }, }; ================================================ FILE: aws-node-upload-to-s3-and-postprocess/.gitignore ================================================ node_modules .serverless index.html ================================================ FILE: aws-node-upload-to-s3-and-postprocess/README.md ================================================ # Upload a file to S3 to trigger a lambda function This example shows how to upload a file to S3 using a HTML form, and have S3 trigger a lambda function. ## Use-cases - Postprocess files uploaded to an S3 bucket. ## Setup - Edit `serverless.yml` and choose a unique S3 bucket name. - Edit `generate-form.js` and fill in your `aws_access_key_id`, `aws_secret_access_key` and `bucket_name`. - Run `yarn install` to install crypto-js dependency for `generate-form.js`. - Generate the HTML form: ```bash yarn install node generate-form.js ``` ## Deploy In order to deploy the example, simply run: ```bash serverless deploy ``` The output should look similar to: ```bash Serverless: Creating Stack... Serverless: Checking Stack create progress... ..... Serverless: Stack create finished... Serverless: Packaging service... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading service .zip file to S3 (3.85 MB)... Serverless: Updating Stack... Serverless: Checking Stack update progress... ........................ Serverless: Stack update finished... Service Information service: upload-to-s3-and-postprocess stage: dev region: us-east-1 api keys: None endpoints: None functions: upload-to-s3-and-postprocess-dev-postprocess ``` ## Usage Open the generated `frontend/index.html` in your browser, or run: ```bash xdg-open frontend/index.html ``` Select a PNG image smaller than 1Mb, and click "Upload File to S3". You should get an XML response similar to: ```xml https://serverless-fetch-file-and-store-in-s3.s3.amazonaws.com/uploads%2Fimage.png serverless-fetch-file-and-store-in-s3 uploads/image.png "08c03c6a24e5058b9f3556981a23b1d7" ``` After a while, the postprocess function gets triggered by an S3 event: ```bash serverless logs --function postprocess ``` ``` START RequestId: e2deccf2a0-11e6-b6e3fbcfad7d8c Version: $LATEST 2014 12:32:30.350 (+02:00) e2deccf2a0-11e6-b6e3fbcfad7d8c New .png object has been created: uploads/image.png (23975 bytes) END RequestId: e2deccf2a0-11e6-b6e3fbcfad7d8c REPORT RequestId: e2deccf2a0-11e6-b6e3fbcfad7d8c Duration: 2.84 ms Billed Duration: 100 msMemory Size: 1024 MB Max Memory Used: 29 MB ``` ================================================ FILE: aws-node-upload-to-s3-and-postprocess/frontend/index.template.html ================================================ S3 POST Form
Destination filename:
File to upload to S3:
================================================ FILE: aws-node-upload-to-s3-and-postprocess/generate-form.js ================================================ #!/usr/bin/env node const crypto = require('crypto-js'); const Hex = require('crypto-js/enc-hex'); const fs = require('fs'); // from: https://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html#signature-v4-examples-javascript function getSignatureKey(key, dateStamp, regionName, serviceName) { const kDate = crypto.HmacSHA256(dateStamp, `AWS4${key}`); const kRegion = crypto.HmacSHA256(regionName, kDate); const kService = crypto.HmacSHA256(serviceName, kRegion); const kSigning = crypto.HmacSHA256('aws4_request', kService); return kSigning; } const awsAccessKeyId = ''; const awsSecretAccessKey = ''; const bucketName = ''; const region = ''; const msPerDay = 24 * 60 * 60 * 1000; const expiration = new Date(Date.now() + msPerDay).toISOString(); const bucketUrl = `https://${bucketName}.s3.amazonaws.com`; const date = new Date().toISOString().slice(0, 10).replace(/-/g, ''); const credentials = `${awsAccessKeyId}/${date}/${region}/s3/aws4_request`; // Sample policy and form: https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-post-example.html const policy = { expiration, conditions: [ { bucket: bucketName }, ['starts-with', '$key', 'uploads/'], { acl: 'public-read' }, ['starts-with', '$Content-Type', 'image/png'], // ['starts-with', '$success_action_redirect', ''], ['starts-with', '$success_action_status', ''], { 'x-amz-credential': credentials }, { 'x-amz-algorithm': 'AWS4-HMAC-SHA256' }, { 'x-amz-date': `${date}T000000Z` }, ], }; const policyB64 = Buffer.from(JSON.stringify(policy), 'utf-8').toString('base64'); const sigKey = getSignatureKey(awsSecretAccessKey, date, region, 's3'); const signature = Hex.stringify(crypto.HmacSHA256(policyB64, sigKey)); fs.readFile('frontend/index.template.html', 'utf8', (err, input) => { if (err) { console.log(err); } const data = input .replace(/%BUCKET_URL%/g, bucketUrl) .replace(/%POLICY_BASE64%/g, policyB64) .replace(/%CREDENTIAL%/g, credentials) .replace(/%DATE%/g, `${date}T000000Z`) .replace(/%SIGNATURE%/g, signature); fs.writeFile('frontend/index.html', data, 'utf8', (e) => { if (e) { console.log(e); } }); }); ================================================ FILE: aws-node-upload-to-s3-and-postprocess/handler.js ================================================ 'use strict'; module.exports.postprocess = (event) => { event.Records.forEach((record) => { const filename = record.s3.object.key; const filesize = record.s3.object.size; console.log(`New .png object has been created: ${filename} (${filesize} bytes)`); }); }; ================================================ FILE: aws-node-upload-to-s3-and-postprocess/package.json ================================================ { "name": "upload-to-s3-and-postprocess", "description": "Upload a files to S3 to trigger a lambda function.", "version": "1.0.0", "author": "Christoph Gysin ", "license": "MIT", "dependencies": { "crypto-js": "^4.0.0" } } ================================================ FILE: aws-node-upload-to-s3-and-postprocess/serverless.yml ================================================ service: upload-to-s3-and-postprocess frameworkVersion: ">=1.1.0" custom: bucket: provider: name: aws runtime: nodejs12.x iamRoleStatements: - Effect: Allow Action: - s3:* Resource: "*" functions: postprocess: handler: handler.postprocess events: - s3: bucket: ${self:custom.bucket} event: s3:ObjectCreated:* rules: - suffix: .png ================================================ FILE: aws-node-vue-nuxt-ssr/.gitignore ================================================ # package directories node_modules jspm_packages # Serverless directories .serverless # Nuxt directories .nuxt ================================================ FILE: aws-node-vue-nuxt-ssr/README.md ================================================ # Serverless-side rendering with Vue.js and Nuxt.js This project demonstrates how to use Nuxt.js to create a server-side rendered Vue.js app on AWS Lambda and AWS API Gateway. ## Use-cases - Develop single-page apps without worrying about SEO optimization. ## Benefits - SEO boost server-side rendering provides - Speed of a Single Page Application - Cheap hosting in a serverless environment on AWS Lambda - Easy deployment with the Serverless Framework - Can easily integrate with your own API or 3rd party APIs such as headless CMS, e-commerce or serverless architecture. ## How it works Well, first thing's first. We want a super fast Single Page Application. But, this usually comes with a cost. Lousy SEO capabilities. That won't do, meaning we also want the app to have server-side rendering. Okay, sounds simple. We'll grab Nuxt.js, which is a framework for creating universal Vue.js applications, and configure it to server-side render our pages. To accomplish this we need to spin up a simple Express server and configure the Nuxt renderer to serve files through Express. It is way simpler than it sounds. ## Setup - Install dependencies: ```bash $ npm install ``` - [Create public wildcard certificate](https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-request-public.html) for your domain (AWS ACM) ## Deploy 1. **Deploy service without custom domain:** ```bash $ npm run deploy ``` Output: ```bash > aws-node-vue-nuxt-ssr@1.0.0 deploy /home/raha/code/serverless/examples/aws-node-vue-nuxt-ssr > npm run build && sls deploy > aws-node-vue-nuxt-ssr@1.0.0 build /home/raha/code/serverless/examples/aws-node-vue-nuxt-ssr > nuxt build Hash: 969f557230f1916aaab2 Version: webpack 4.31.0 Time: 5531ms Built at: 05/14/2019 12:22:28 AM Asset Size Chunks Chunk Names ../server/client.manifest.json 6.81 KiB [emitted] 1aeb026dc2ca77c8b429.js 952 bytes 3 [emitted] pages/dogs/index 775caefedab77dd6e1a6.js 147 KiB 1 [emitted] commons.app 92fcd17f0b85f40f90ad.js 1.15 KiB 2 [emitted] pages/dogs/_breed 9f5aaac1c101c273e65f.js 845 bytes 4 [emitted] pages/index LICENSES 464 bytes [emitted] cf8b2abbbaec4a0c7b76.js 2.27 KiB 5 [emitted] runtime fa6324eac05d1b7d36b0.js 42.3 KiB 0 [emitted] app + 2 hidden assets Entrypoint app = cf8b2abbbaec4a0c7b76.js 775caefedab77dd6e1a6.js fa6324eac05d1b7d36b0.js Hash: ac61088f50920f2fc1a4 Version: webpack 4.31.0 Time: 1266ms Built at: 05/14/2019 12:22:30 AM Asset Size Chunks Chunk Names 92cd2f7d0e5f9c484439.js 641 bytes 2 [emitted] pages/dogs/index bbeb65244f57ade7cfbe.js 828 bytes 1 [emitted] pages/dogs/_breed e3465bc88d2bf9e1b91d.js 536 bytes 3 [emitted] pages/index server.js 25.1 KiB 0 [emitted] app server.manifest.json 483 bytes [emitted] + 4 hidden assets Entrypoint app = server.js server.js.map Done in 9.03s Serverless: Packaging service... Serverless: Excluding development dependencies... Serverless: Creating Stack... Serverless: Checking Stack create progress... ..... Serverless: Stack create finished... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading artifacts... Serverless: Uploading service .zip file to S3 (42.47 MB)... Serverless: Validating template... Serverless: Updating Stack... Serverless: Checking Stack update progress... ................................. Serverless: Stack update finished... Service Information service: serverless-side-rendering-vue-nuxt stage: dev region: us-east-1 stack: serverless-side-rendering-vue-nuxt-dev api keys: None endpoints: ANY - https://.execute-api.us-east-1.amazonaws.com/dev ANY - https://.execute-api.us-east-1.amazonaws.com/dev/{proxy+} functions: nuxt: serverless-side-rendering-vue-nuxt-dev-nuxt ``` Once deployed you'll have your app running on a default API Gateway URI. 2. **Create domain:** Uncomment domain settings in `serverless.yaml`. ```yaml [...] plugins: - serverless-apigw-binary - serverless-domain-manager # uncomment the plugin - serverless-offline [...] custom: secrets: ${file(secrets.json)} apigwBinary: types: - '*/*' customDomain: # uncomment the whole customDomain section domainName: ${self:custom.secrets.DOMAIN} basePath: '' stage: ${self:custom.secrets.NODE_ENV} createRoute53Record: true ``` Run the create domain command: ```bash $ sls create_domain ``` This will take a few minutes, go grab a coffee in the meantime. :smile: 3. **Re-deploy the service with the domain settings:** ```bash $ npm run deploy ``` Output: ```bash [...same as above but also with the domain info] Serverless Domain Manager Summary Domain Name vuessr.yourdomain.com Distribution Domain Name .cloudfront.net ``` ## Usage Navigate to `vuessr-yourdomain.com` or whichever domain you picked. You'll see the Vue.js SPA running. --- I've written a detailed tutorial about the process. You can check it out [here](https://dev.to/adnanrahic/a-crash-course-on-serverless-side-rendering-with-vuejs-nuxtjs-and-aws-lambda-1nk4). (**NOTE:** Some parts are outdated and are for `nuxt@1`. Please refer to this example for using with `nuxt@2`) ================================================ FILE: aws-node-vue-nuxt-ssr/binaryMimeTypes.js ================================================ module.exports = [ 'application/javascript', 'application/json', 'application/octet-stream', 'application/xml', 'font/eot', 'font/opentype', 'font/otf', 'image/jpeg', 'image/png', 'image/svg+xml', 'text/comma-separated-values', 'text/css', 'text/html', 'text/javascript', 'text/plain', 'text/text', 'text/xml', ]; ================================================ FILE: aws-node-vue-nuxt-ssr/client/components/navbar.vue ================================================ ================================================ FILE: aws-node-vue-nuxt-ssr/client/layouts/default.vue ================================================ ================================================ FILE: aws-node-vue-nuxt-ssr/client/pages/dogs/_breed.vue ================================================ ================================================ FILE: aws-node-vue-nuxt-ssr/client/pages/dogs/index.vue ================================================ ================================================ FILE: aws-node-vue-nuxt-ssr/client/pages/index.vue ================================================ ================================================ FILE: aws-node-vue-nuxt-ssr/index.js ================================================ const sls = require('serverless-http'); const binaryMimeTypes = require('./binaryMimeTypes'); const nuxt = require('./nuxt'); module.exports.nuxt = sls(nuxt, { binary: binaryMimeTypes, }); ================================================ FILE: aws-node-vue-nuxt-ssr/nuxt.config.js ================================================ module.exports = { mode: 'universal', head: { title: 'Vue Nuxt Test', meta: [ { charset: 'utf-8' }, { name: 'viewport', content: 'width=device-width, initial-scale=1' }, { hid: 'description', name: 'description', content: 'Nuxt.js project' }, ], }, srcDir: 'client/', modules: [ '@nuxt/http', ], http: { baseURL: 'https://api.thedogapi.com/v1/', }, render: { compressor: false, }, }; ================================================ FILE: aws-node-vue-nuxt-ssr/nuxt.js ================================================ const { Nuxt } = require('nuxt-start'); const config = require('./nuxt.config.js'); const nuxt = new Nuxt({ ...config, dev: false }); module.exports = (req, res) => nuxt.ready().then(() => nuxt.server.app(req, res)); ================================================ FILE: aws-node-vue-nuxt-ssr/package.json ================================================ { "name": "aws-node-vue-nuxt-ssr", "version": "1.0.0", "description": "Sample project for using Nuxt.js to create a server-side rendered Vue.js app on AWS Lambda and AWS API Gateway. Can easily integrate with your own API or 3rd party APIs such as headless CMS, e-commerce or serverless architecture.", "main": "index.js", "engines": { "node": ">=8.10" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev": "nuxt", "build": "nuxt build", "start": "nuxt start", "generate": "nuxt generate", "deploy": "npm run build && sls deploy", "start-server": "npm run build && node app.js", "start-sls": "npm run build && sls offline start" }, "keywords": [], "author": "", "license": "MIT", "dependencies": { "@nuxt/http": "^0.1.2", "axios": "^0.18.0", "nuxt": "^2.6.3", "nuxt-start": "^2.6.3", "serverless-apigw-binary": "^0.4.4", "serverless-http": "^2.0.1" }, "devDependencies": { "serverless-domain-manager": "^3.2.1", "serverless-offline": "^4.10.0" } } ================================================ FILE: aws-node-vue-nuxt-ssr/secrets.json ================================================ { "NODE_ENV": "dev", "DOMAIN": "vuessr.yourdomain.com" } ================================================ FILE: aws-node-vue-nuxt-ssr/serverless.yml ================================================ service: serverless-side-rendering-vue-nuxt provider: name: aws runtime: nodejs12.x stage: ${self:custom.secrets.NODE_ENV} region: us-east-1 environment: NODE_ENV: ${self:custom.secrets.NODE_ENV} functions: nuxt: handler: index.nuxt events: - http: ANY / - http: ANY /{proxy+} plugins: - serverless-apigw-binary # - serverless-domain-manager - serverless-offline custom: secrets: ${file(secrets.json)} apigwBinary: types: - '*/*' # customDomain: # domainName: ${self:custom.secrets.DOMAIN} # basePath: '' # stage: ${self:custom.secrets.NODE_ENV} # createRoute53Record: true ## endpointType: 'regional' ## if the ACM certificate is created in a region except for `'us-east-1'` you need `endpointType: 'regional'` ================================================ FILE: aws-node-websockets-authorizers/README.md ================================================ # Simple Websockets Authorizers Example * Deploy the example service. * connect to the outputted wss url using `wscat`: ``` wscat -c ``` * Connection should fail. If you try again, this time specifying an `Auth` header: ``` wscat -c -H Auth:secret ``` * Connection succeeds. * Feel free to chat with yourself :) ================================================ FILE: aws-node-websockets-authorizers/handler.js ================================================ 'use strict'; const AWS = require('aws-sdk') // the following section injects the new ApiGatewayManagementApi service // into the Lambda AWS SDK, otherwise you'll have to deploy the entire new version of the SDK /* START ApiGatewayManagementApi injection */ const { Service, apiLoader } = AWS apiLoader.services['apigatewaymanagementapi'] = {} const model = { metadata: { apiVersion: '2018-11-29', endpointPrefix: 'execute-api', signingName: 'execute-api', serviceFullName: 'AmazonApiGatewayManagementApi', serviceId: 'ApiGatewayManagementApi', protocol: 'rest-json', jsonVersion: '1.1', uid: 'apigatewaymanagementapi-2018-11-29', signatureVersion: 'v4' }, operations: { PostToConnection: { http: { requestUri: '/@connections/{connectionId}', responseCode: 200 }, input: { type: 'structure', members: { Data: { type: 'blob' }, ConnectionId: { location: 'uri', locationName: 'connectionId' } }, required: ['ConnectionId', 'Data'], payload: 'Data' } } }, paginators: {}, shapes: {} } AWS.ApiGatewayManagementApi = Service.defineService('apigatewaymanagementapi', ['2018-11-29']) Object.defineProperty(apiLoader.services['apigatewaymanagementapi'], '2018-11-29', { // eslint-disable-next-line get: function get() { return model }, enumerable: true, configurable: true }) /* END ApiGatewayManagementApi injection */ module.exports.connect = (event, context, cb) => { cb(null, { statusCode: 200, body: 'Connected.' }); }; module.exports.disconnect = (event, context, cb) => { cb(null, { statusCode: 200, body: 'Disconnected.' }); }; module.exports.default = async (event, context, cb) => { // default function that just echos back the data to the client const client = new AWS.ApiGatewayManagementApi({ apiVersion: '2018-11-29', endpoint: `https://${event.requestContext.domainName}/${event.requestContext.stage}` }); await client .postToConnection({ ConnectionId: event.requestContext.connectionId, Data: `default route received: ${event.body}` }) .promise(); cb(null, { statusCode: 200, body: 'Sent.' }); }; module.exports.auth = async (event, context) => { // return policy statement that allows to invoke the connect function. // in a real world application, you'd verify that the header in the event // object actually corresponds to a user, and return an appropriate statement accordingly return { "principalId": "user", "policyDocument": { "Version": "2012-10-17", "Statement": [ { "Action": "execute-api:Invoke", "Effect": "Allow", "Resource": event.methodArn } ] } }; }; ================================================ FILE: aws-node-websockets-authorizers/package.json ================================================ { "name": "aws-nodejs-websockets-authorizers", "version": "1.0.0", "description": "Simple example that demonstrates how to use authorizer functions with websocket events", "author": "", "license": "MIT" } ================================================ FILE: aws-node-websockets-authorizers/serverless.yml ================================================ service: websocket-authorizer-example provider: name: aws stage: dev runtime: nodejs12.x functions: connect: handler: handler.connect events: - websocket: route: $connect # authorizers are only for connect routes authorizer: name: auth identitySource: - 'route.request.header.Auth' default: handler: handler.default events: - websocket: route: $default auth: handler: handler.auth ================================================ FILE: aws-python/.gitignore ================================================ # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # Serverless directories .serverless ================================================ FILE: aws-python/README.md ================================================ # Serverless Framework AWS Python Example This template demonstrates how to deploy a Python function running on AWS Lambda using the Serverless Framework. The deployed function does not include any event definitions as well as any kind of persistence (database). For more advanced configurations check out the [examples repo](https://github.com/serverless/examples/) which includes integrations with SQS, DynamoDB or examples of functions that are triggered in `cron`-like manner. For details about configuration of specific `events`, please refer to our [documentation](https://www.serverless.com/framework/docs/providers/aws/events/). ## Usage ### Deployment In order to deploy the example, you need to run the following command: ``` serverless deploy ``` After running deploy, you should see output similar to: ``` Deploying "aws-python" to stage "dev" (us-east-1) ✔ Service deployed to stack aws-python-dev (90s) functions: hello: aws-python-dev-hello (1.9 kB) ``` ### Invocation After successful deployment, you can invoke the deployed function by using the following command: ``` serverless invoke --function hello ``` Which should result in response similar to the following: ```json { "statusCode": 200, "body": "{\"message\": \"Go Serverless v4.0! Your function executed successfully!\"}" } ``` ### Local development You can invoke your function locally by using the following command: ``` serverless invoke local --function hello ``` Which should result in response similar to the following: ``` { "statusCode": 200, "body": "{\"message\": \"Go Serverless v4.0! Your function executed successfully!\"}" } ``` ### Bundling dependencies In case you would like to include third-party dependencies, you will need to use a plugin called `serverless-python-requirements`. You can set it up by running the following command: ``` serverless plugin install -n serverless-python-requirements ``` Running the above will automatically add `serverless-python-requirements` to `plugins` section in your `serverless.yml` file and add it as a `devDependency` to `package.json` file. The `package.json` file will be automatically created if it doesn't exist beforehand. Now you will be able to add your dependencies to `requirements.txt` file (`Pipfile` and `pyproject.toml` is also supported but requires additional configuration) and they will be automatically injected to Lambda package during build process. For more details about the plugin's configuration, please refer to [official documentation](https://github.com/UnitedIncome/serverless-python-requirements). ================================================ FILE: aws-python/handler.py ================================================ import json def hello(event, context): body = { "message": "Go Serverless v4.0! Your function executed successfully!" } return {"statusCode": 200, "body": json.dumps(body)} ================================================ FILE: aws-python/serverless.yml ================================================ service: aws-python # NOTE: update this with your service name frameworkVersion: '4' provider: name: aws runtime: python3.12 functions: hello: handler: handler.hello ================================================ FILE: aws-python-alexa-skill/.gitignore ================================================ .serverless *.pyc *.pyo ================================================ FILE: aws-python-alexa-skill/README.md ================================================ # Serverless Alexa Skill Example This example demonstrates how to setup your own Alexa skill using AWS Lambdas. ## Use-cases - Building custom Alexa skills ## How it works In the Alexa Developer Portal you can add your own skill. To do so you need to define the available intents and then connect them to a AWS Lambda. The Lambda you can define and update with Serverless. ## Setup In order to deploy the endpoint simply run ```bash serverless deploy ``` The expected result should be similar to: ```bash Serverless: Creating Stack... Serverless: Checking Stack create progress... Serverless: Stack create finished... Serverless: Packaging service... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading service .zip file to S3 (2 KB)... Serverless: Updating Stack... Serverless: Checking Stack update progress... Serverless: Stack update finished... Service Information service: aws-python-alexa-skill stage: dev region: us-east-1 api keys: None endpoints: None functions: aws-python-alexa-skill-dev-luckyNumber: arn:aws:lambda:us-east-1:377024778620:function:aws-python-alexa-skill-dev-luckyNumber ``` Next we need to setup a Alexa skill. Once you've signed up for the Amazon Developer Platform visit `https://developer.amazon.com/edw/home.html`. There you should see the following screen: ![Welcome](https://cloud.githubusercontent.com/assets/223045/21183285/8403b37c-c211e6-89c0-d36582010af8.png) Next click on `Add a new Skill`: ![Add Skill](https://cloud.githubusercontent.com/assets/223045/21183286/840512c211e6-84945b6b45e83b.png) Go through the steps and fill in all the required fields e.g. Intent Schema and Sample Utterances: Intent Schema ``` { "intents": [ { "intent": "GetLuckyNumbers", "slots": [ { "name": "UpperLimit", "type": "AMAZON.NUMBER" } ] } ] } ``` Sample Utterances ``` GetLuckyNumbers what are my lucky numbers GetLuckyNumbers tell me my lucky numbers GetLuckyNumbers what are my lucky numbers lower than {UpperLimit} GetLuckyNumbers tell me my lucky numbers lower than {UpperLimit} ``` ![Skill Information](https://cloud.githubusercontent.com/assets/223045/21183279/83eec4c211e6-841b-d8925f0804a5.png) ![Interaction Model](https://cloud.githubusercontent.com/assets/223045/21183280/83ef3dc211e6-87a5-bb8dcbb903f8.png) Fill in the Lambda ARN which was printed or run `serverless info` to retrieve the ARN again. ![Configuration](https://cloud.githubusercontent.com/assets/223045/21183281/83f170c211e6-89b7-2f6d96ac559c.png) Next up visit the test page, fill in the utterance and click on `Ask LuckyNumbers`. ![Test](https://cloud.githubusercontent.com/assets/223045/21183283/83f1f6c211e6-858d-41b1a3154e91.png) ![Test](https://cloud.githubusercontent.com/assets/223045/21183282/83f1f6c211e6-974e-b7c051ffb6eb.png) ![Test](https://cloud.githubusercontent.com/assets/223045/21183284/83f708ac-c211e6-819489e8f3e494.png) ![Test](https://cloud.githubusercontent.com/assets/223045/21185805/78c1dfc211e6-9cf9-ce44edc30cdd.gif) You should have received a response containing the text `Your lucky number is` followed by your lucky number :) Check out this [Amazon guide](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/overviews/steps-to-build-a-custom-skill#your-skill-is-published-now-what) to learn more about how to submit your skill for publication. ================================================ FILE: aws-python-alexa-skill/handler.py ================================================ import random def parseInt(value): try: return int(value) except ValueError: return 100 def lucky_number(event, context): print(event) upperLimitDict = event['request']['intent']['slots']['UpperLimit'] upperLimit = None if 'value' in upperLimitDict: upperLimit = parseInt(upperLimitDict['value']) else: upperLimit = 100 number = random.randint(0, upperLimit) response = { 'version': '1.0', 'response': { 'outputSpeech': { 'type': 'PlainText', 'text': 'Your lucky number is ' + str(number), } } } return response ================================================ FILE: aws-python-alexa-skill/package.json ================================================ { "name": "aws-alexa-skill", "version": "1.0.0", "description": "This example demonstrates how to use an AWS Lambdas for your custom Alexa skill.", "author": "", "license": "MIT" } ================================================ FILE: aws-python-alexa-skill/serverless.yml ================================================ service: aws-python-alexa-skill frameworkVersion: ">=1.4.0 <2.0.0" provider: name: aws runtime: python2.7 functions: luckyNumber: handler: handler.lucky_number events: - alexaSkill ================================================ FILE: aws-python-auth0-custom-authorizers-api/.gitignore ================================================ node_modules .serverless secrets.json public_key /frontend/misc.md vendored ================================================ FILE: aws-python-auth0-custom-authorizers-api/Makefile ================================================ NAME ?= aws-python-auth0-custom-authorizers-api ERROR_COLOR = \033[1;31m INFO_COLOR = \033[1;32m WARN_COLOR = \033[1;33m NO_COLOR = \033[0m ################# # # Deploy Targets # ################# .PHONY: deploy deploy: vendor ## wrap sls deploy @make action=deploy sls sls: guard-action @if [ -n "$${AWS_ACCESS_KEY_ID}" -a -n "$${AWS_SECRET_ACCESS_KEY}" ] ; then \ printf "$(WARN_COLOR)WARN:$(NO_COLOR) Found AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY vars, using those for sls "$${action}".\n" ; \ sls "$${action}" ; \ elif [ -n "$${AWS_DEFAULT_PROFILE}" ] ; then \ printf "$(WARN_COLOR)WARN:$(NO_COLOR) Found AWS_DEFAULT_PROFILE var, using it for sls "$${action}".\n" ; \ sls "$${action}" --aws-profile=$${AWS_DEFAULT_PROFILE} ; \ elif [ -n "$${profile}" ] ; then \ printf "$(INFO_COLOR)INFO:$(NO_COLOR) Found 'profile=$${profile}' argument, using it for sls "$${action}".\n" ; \ sls "$${action}" --aws-profile=$${profile} --verbose --aws-s3-accelerate ; \ elif [ -z "$${profile}" ] ; then \ printf "$(WARN_COLOR)WARN:$(NO_COLOR) No AWS profile specified, using default.\n" ; \ sls "$${action}" ; \ else \ printf "$(ERROR_COLOR)ERROR:$(NO_COLOR) No AWS credentials found or passed.\n" ; \ cd .. && make sls_help ; \ exit 1 ; \ fi ################# # # Docker Targets # ################# check-docker: @if which docker &>/dev/null ; then \ printf "$(INFO_COLOR)OK:$(NO_COLOR) Docker is valid\n" ; \ else \ printf "$(ERROR_COLOR)ERROR:$(NO_COLOR) docker not found. Please install and configure docker!\n" ; \ exit 1 ; \ fi # Docker stuff clean-it-containers: ## stops and removes _ALL_ containers @-docker rm -f $$(docker ps -qa) # neat trick so that vendor target only runs when requirements.txt is newer than vendored folder VENDORED_FOLDER := vendored .PHONY: vendor vendor: check-docker $(VENDORED_FOLDER) $(VENDORED_FOLDER): requirements.txt ## install requirements into $(VENDORED_FOLDER) when requirements.txt is newer than the folder rm -rf $(VENDORED_FOLDER) @-docker rm lambdapy36 &>/dev/null docker run --name lambdapy36 -it -v $(shell pwd):/python-auth0 lambci/lambda:build-python3.6 /bin/sh -c "pip install -r /python-auth0/requirements.txt -t /python-auth0/vendored/" ################# # # Cleanup # ################# clean: clean-backend-deploy clean-tox clean-pyc clean-backend-deploy: rm -rf $(VENDORED_FOLDER) clean-pyc: find . -name '*.pyc' -exec rm -f {} + find . -name '*.pyo' -exec rm -f {} + find . -name '*~' -exec rm -f {} + find . -name '__pycache__' -exec rm -fr {} + clean-tox: rm -rf .tox rm -rf .hypothesis rm -rf .py36* rm -rf test-results rm -rf coverage-reports ################# # # Helper Targets # ################# # Add an implicit guard for parameter input validation; use as target dependency guard-VARIABLE_NAME, e.g. guard-AWS_ACCESS_KEY_ID guard-%: @if [ "${${*}}" = "" ]; then \ printf \ "$(ERROR_COLOR)ERROR:$(NO_COLOR) Variable [$(ERROR_COLOR)$*$(NO_COLOR)] not set.\n"; \ exit 1; \ fi help: ## Prints the names and descriptions of available targets @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: aws-python-auth0-custom-authorizers-api/README.md ================================================ # API Gateway Custom Authorizer Function + Auth0 This is an example of how to protect API endpoints with [auth0](https://auth0.com/), JSON Web Tokens (jwt) and a [custom authorizer lambda function](https://serverless.com/framework/docs/providers/aws/events/apigateway#http-endpoints-with-custom-authorizers). Custom Authorizers allow you to run an AWS Lambda Function before your targeted AWS Lambda Function. This is useful for Microservice Architectures or when you simply want to do some Authorization before running your business logic. ## Use cases - Protect API routes for authorized users - Rate limiting APIs ## Setup 1. You must have Python 3! Once you do, run `pip install -r requirements.txt` to install Python web token dependencies 2. Install Docker. Why Docker? Because it's the only way to ensure that the Python package that is created on your local machine and uploaded to AWS will actually run in AWS's lambda containers. 2. Setup an [auth0 client](https://auth0.com/docs/clients) and get your `client id` and `client secrets` from auth0. 3. Plugin your `AUTH0_CLIENT_ID` and `AUTH0_CLIENT_SECRET` in a new file called `secrets.json`. These will be used by the JSON web token decoder to validate private api access. 4. Copy the `public_key-example` file to a new file named `public_key` and follow the instructions in that file 5. Deploy the Lambda Authorizer to AWS with `make deploy` and grab the public and private endpoints from the `endpoints:` section of the `make` command output 6. Plugin your `AUTH0_CLIENT_ID`, `AUTH0_DOMAIN`, and the `PUBLIC_ENDPOINT` + `PRIVATE_ENDPOINT` from aws in top of the `frontend/app.js` file. ```js /* frontend/app.js */ // replace these values in app.js const AUTH0_CLIENT_ID = 'your-auth0-client-id-here'; const AUTH0_DOMAIN = 'your-auth0-domain-here.auth0.com'; const PUBLIC_ENDPOINT = 'https://your-aws-endpoint-here.amazonaws.com/dev/api/public'; const PRIVATE_ENDPOINT = 'https://your-aws-endpoint-here.us-east-1.amazonaws.com/dev/api/private'; ``` 7. You can either run your frontend locally or deploy your frontend to host of your choosing. However in either case, make sure to configure the `Allowed Callback URL` and `Allowed Origins` in your auth0 client in the [auth0 dashboard](https://manage.auth0.com). An example of how to run your frontend locally: ``` cd frontend; python -m http.server ``` ## Custom authorizer functions [Custom authorizers functions](https://aws.amazon.com/blogs/compute/introducing-custom-authorizers-in-amazon-api-gateway/) are executed before a Lambda function is executed and return an Error or a Policy document. The Custom authorizer function is passing an `event` object to API Gateway as below: ```javascript { "type": "TOKEN", "authorizationToken": "", "methodArn": "arn:aws:execute-api:::///" } ``` You will have to change this policy to accommodate your needs. The default reply provided, will only authorize one endpoint! ## Frontend The frontend is a bare bones vanilla javascript implementation. You can replace it with whatever frontend framework you like =) If you do implement in another framework, please consider adding it our [growing list of examples](https://github.com/serverless/examples/)! API calls are made with the browser's native `fetch` api. ================================================ FILE: aws-python-auth0-custom-authorizers-api/frontend/app.css ================================================ html, body { padding: 0px; margin: 0px; } body { font-family: "proxima-nova", sans-serif; text-align: center; font-size: 14px; font-weight: 100; } h1, h2, h3 { font-weight: 100; } #message { min-height: 40px; font-size: 22px; } #logo img { width: 100px; margin-bottom: 10px; margin-top: 50px; } .btn { font-size: 16px; letter-spacing: 1px; border: 0; background-color: #16214D; color: white; min-width: 80px; min-height: 30px; display: flex; align-items: center; justify-content: center; cursor: pointer; min-width: 200px; } .btn:hover { background-color: #44C7F4; } .btn:focus { outline: none !important; } .btn:disabled { background-color: #333; color: #666; } .api-actions, .user-actions { display: flex; align-items: center; justify-content: center; margin-bottom: 30px; } .api-actions button:last-of-type, .user-actions button:last-of-type { margin-left: 20px; } @media (max-width: 768px) { .api-actions, .user-actions { flex-direction: column; } .api-actions button:last-of-type, .user-actions button:last-of-type { margin-left: 0px; margin-top: 20px; } } ================================================ FILE: aws-python-auth0-custom-authorizers-api/frontend/app.js ================================================ /* global window document localStorage fetch alert */ // Fill in with your values const AUTH0_CLIENT_ID = 'your-auth0-client-id-here'; const AUTH0_DOMAIN = 'your-auth0-domain-here.auth0.com'; const AUTH0_CALLBACK_URL = window.location.href; // eslint-disable-line const PUBLIC_ENDPOINT = 'https://your-aws-endpoint-here.amazonaws.com/dev/api/public'; const PRIVATE_ENDPOINT = 'https://your-aws-endpoint-here.us-east-1.amazonaws.com/dev/api/private'; // initialize auth0 lock const lock = new Auth0Lock(AUTH0_CLIENT_ID, AUTH0_DOMAIN, { // eslint-disable-line no-undef auth: { params: { scope: 'openid email', }, responseType: 'token id_token', }, }); function updateUI() { const isLoggedIn = localStorage.getItem('id_token'); if (isLoggedIn) { // swap buttons document.getElementById('btn-login').style.display = 'none'; document.getElementById('btn-logout').style.display = 'inline'; const profile = JSON.parse(localStorage.getItem('profile')); // show username document.getElementById('nick').textContent = profile.email; } } // Handle login lock.on('authenticated', (authResult) => { console.log(authResult); lock.getUserInfo(authResult.accessToken, (error, profile) => { if (error) { // Handle error return; } document.getElementById('nick').textContent = profile.nickname; localStorage.setItem('accessToken', authResult.accessToken); localStorage.setItem('id_token', authResult.idToken); localStorage.setItem('profile', JSON.stringify(profile)); updateUI(); }); }); updateUI(); // Handle login document.getElementById('btn-login').addEventListener('click', () => { lock.show(); }); // Handle logout document.getElementById('btn-logout').addEventListener('click', () => { localStorage.removeItem('id_token'); localStorage.removeItem('access_token'); localStorage.removeItem('profile'); document.getElementById('btn-login').style.display = 'flex'; document.getElementById('btn-logout').style.display = 'none'; document.getElementById('nick').textContent = ''; }); // Handle public api call document.getElementById('btn-public').addEventListener('click', () => { // call public API fetch(PUBLIC_ENDPOINT, { cache: 'no-store', method: 'POST', }) .then(response => response.json()) .then((data) => { console.log('Message:', data); document.getElementById('message').textContent = ''; document.getElementById('message').textContent = data.message; }) .catch((e) => { console.log('error', e); }); }); // Handle private api call document.getElementById('btn-private').addEventListener('click', () => { // Call private API with JWT in header const token = localStorage.getItem('id_token'); /* // block request from happening if no JWT token present if (!token) { document.getElementById('message').textContent = '' document.getElementById('message').textContent = 'You must login to call this protected endpoint!' return false }*/ // Do request to private endpoint fetch(PRIVATE_ENDPOINT, { method: 'POST', headers: { Authorization: `Bearer ${token}`, }, }) .then(response => response.json()) .then((data) => { console.log('Token:', data); document.getElementById('message').textContent = ''; document.getElementById('message').textContent = data.message; }) .catch((e) => { console.log('error', e); }); }); ================================================ FILE: aws-python-auth0-custom-authorizers-api/frontend/index.html ================================================

Auth0 Serverless Example

Welcome

View the source on github
================================================ FILE: aws-python-auth0-custom-authorizers-api/lambda_handlers.py ================================================ import json import os import jwt from cryptography.hazmat.backends import default_backend from cryptography.x509 import load_pem_x509_certificate # Set by serverless.yml AUTH0_CLIENT_ID = os.getenv('AUTH0_CLIENT_ID') AUTH0_CLIENT_PUBLIC_KEY = os.getenv('AUTH0_CLIENT_PUBLIC_KEY') def auth(event, context): whole_auth_token = event.get('authorizationToken') if not whole_auth_token: raise Exception('Unauthorized') print('Client token: ' + whole_auth_token) print('Method ARN: ' + event['methodArn']) token_parts = whole_auth_token.split(' ') auth_token = token_parts[1] token_method = token_parts[0] if not (token_method.lower() == 'bearer' and auth_token): print("Failing due to invalid token_method or missing auth_token") raise Exception('Unauthorized') try: principal_id = jwt_verify(auth_token, AUTH0_CLIENT_PUBLIC_KEY) policy = generate_policy(principal_id, 'Allow', event['methodArn']) return policy except Exception as e: print(f'Exception encountered: {e}') raise Exception('Unauthorized') def public_endpoint(event, context): return create_200_response('Hi ⊂◉‿◉つ from Public API') def private_endpoint(event, context): return create_200_response('Hi ⊂◉‿◉つ from Private API. Only logged in users can see this') def jwt_verify(auth_token, public_key): public_key = format_public_key(public_key) pub_key = convert_certificate_to_pem(public_key) payload = jwt.decode(auth_token, pub_key, algorithms=['RS256'], audience=AUTH0_CLIENT_ID) return payload['sub'] def generate_policy(principal_id, effect, resource): return { 'principalId': principal_id, 'policyDocument': { 'Version': '2012-10-17', 'Statement': [ { "Action": "execute-api:Invoke", "Effect": effect, "Resource": resource } ] } } def convert_certificate_to_pem(public_key): cert_str = public_key.encode() cert_obj = load_pem_x509_certificate(cert_str, default_backend()) pub_key = cert_obj.public_key() return pub_key def format_public_key(public_key): public_key = public_key.replace('\n', ' ').replace('\r', '') public_key = public_key.replace('-----BEGIN CERTIFICATE-----', '-----BEGIN CERTIFICATE-----\n') public_key = public_key.replace('-----END CERTIFICATE-----', '\n-----END CERTIFICATE-----') return public_key def create_200_response(message): headers = { # Required for CORS support to work 'Access-Control-Allow-Origin': '*', # Required for cookies, authorization headers with HTTPS 'Access-Control-Allow-Credentials': True, } return create_aws_lambda_response(200, {'message': message}, headers) def create_aws_lambda_response(status_code, message, headers): return { 'statusCode': status_code, 'headers': headers, 'body': json.dumps(message) } ================================================ FILE: aws-python-auth0-custom-authorizers-api/package.json ================================================ { "name": "aws-auth0-api-gateway", "version": "1.0.0", "description": "Demonstration of protecting API gateway endpoints with auth0", "license": "MIT", "dependencies": { "serverless": "^1.28.0" }, "devDependencies": { "serverless-offline": "^3.18.0" } } ================================================ FILE: aws-python-auth0-custom-authorizers-api/public_key-example ================================================ -----BEGIN CERTIFICATE----- PUBLIC KEY - can be found in https://manage.auth0.com -> clients -> advanced settings -> Certificates Replace this file with public_key -----END CERTIFICATE----- ================================================ FILE: aws-python-auth0-custom-authorizers-api/requirements.txt ================================================ cryptography==2.3 PyJWT==1.6.4 ================================================ FILE: aws-python-auth0-custom-authorizers-api/secrets.example.json ================================================ { "AUTH0_CLIENT_ID": "your-client-id", "AUTH0_CLIENT_SECRET": "your-client-secret" } ================================================ FILE: aws-python-auth0-custom-authorizers-api/serverless.yml ================================================ service: aws-custom-authorizer-auth0 package: exclude: - ./** include: - vendored/** - lambda_handlers.py provider: name: aws runtime: python3.6 region: us-east-1 environment: AUTH0_CLIENT_ID: ${file(./secrets.json):AUTH0_CLIENT_ID} AUTH0_CLIENT_PUBLIC_KEY: ${file(./public_key)} PYTHONPATH: "/var/runtime:/var/task/vendored" functions: auth: handler: lambda_handlers.auth cors: true publicEndpoint: handler: lambda_handlers.public_endpoint events: - http: path: api/public method: post cors: true privateEndpoint: handler: lambda_handlers.private_endpoint events: - http: path: api/private method: post # See custom authorizer docs here: http://bit.ly/2gXw9pO authorizer: auth cors: true resources: Resources: # This response is needed for custom authorizer failures cors support ¯\_(ツ)_/¯ GatewayResponse: Type: 'AWS::ApiGateway::GatewayResponse' Properties: ResponseParameters: gatewayresponse.header.Access-Control-Allow-Origin: "'*'" gatewayresponse.header.Access-Control-Allow-Headers: "'*'" ResponseType: EXPIRED_TOKEN RestApiId: Ref: 'ApiGatewayRestApi' StatusCode: '401' AuthFailureGatewayResponse: Type: 'AWS::ApiGateway::GatewayResponse' Properties: ResponseParameters: gatewayresponse.header.Access-Control-Allow-Origin: "'*'" gatewayresponse.header.Access-Control-Allow-Headers: "'*'" ResponseType: UNAUTHORIZED RestApiId: Ref: 'ApiGatewayRestApi' StatusCode: '401' ================================================ FILE: aws-python-flask-api/README.md ================================================ # Serverless Framework Python Flask API on AWS This template demonstrates how to develop and deploy a simple Python Flask API service running on AWS Lambda using the Serverless Framework. This template configures a single function, `api`, which is responsible for handling all incoming requests thanks to configured `http` events. To learn more about `http` event configuration options, please refer to [http event docs](https://www.serverless.com/framework/docs/providers/aws/events/apigateway/). As the events are configured in a way to accept all incoming requests, `Flask` framework is responsible for routing and handling requests internall y. The implementation takes advantage of `serverless-wsgi`, which allows you to wrap WSGI applications such as Flask apps. To learn more about `serverless-wsgi`, please refer to corresponding [GitHub repository](https://github.com/logandk/serverless-wsgi). Additionally, the template relies on `serverless-python-requirements` plugin for packaging dependencies from `requirements.txt` file. For more details about `serverless-python-requirements` configuration, please refer to corresponding [GitHub repository](https://github.com/UnitedIncome/serverless-python-requirements). ## Usage ### Deployment This example is made to work with the Serverless Framework dashboard, which includes advanced features such as CI/CD, monitoring, metrics, etc. In order to deploy with dashboard, you need to first login with: ``` serverless login ``` install dependencies with: ``` npm install ``` and ``` pip install -r requirements.txt ``` and then perform deployment with: ``` serverless deploy ``` After running deploy, you should see output similar to: ``` Deploying "aws-python-flask-api" to stage "dev" (us-east-1) Using Python specified in "runtime": python3.12 Packaging Python WSGI handler... ✔ Service deployed to stack aws-python-flask-api-dev (104s) endpoints: ANY - https://xxxxxxxxxe.execute-api.us-east-1.amazonaws.com/dev/ ANY - https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/{proxy+} functions: api: aws-python-flask-api-dev-api (41 MB) ``` _Note_: In current form, after deployment, your API is public and can be invoked by anyone. For production deployments, you might want to configure an authorizer. For details on how to do that, refer to [http event docs](https://www.serverless.com/framework/docs/providers/aws/events/apigateway/). ### Invocation After successful deployment, you can call the created application via HTTP: ``` curl https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/ ``` Which should result in the following response: ```json { "message": "Hello from root!" } ``` Calling the `/hello` path with: ``` curl https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/hello ``` Should result in the following response: ```json { "message": "Hello from path!" } ``` ### Local development Thanks to capabilities of `serverless-wsgi`, it is also possible to run your application locally, however, in order to do that, you will need to first install `werkzeug` dependency, as well as all other dependencies listed in `requirements.txt`. It is recommended to use a dedicated virtual environment for that purpose. You can install all needed dependencies with the following commands: ``` pip install werkzeug pip install -r requirements.txt ``` At this point, you can run your application locally with the following command: ``` serverless wsgi serve ``` For additional local development capabilities of `serverless-wsgi` plugin, please refer to corresponding [GitHub repository](https://github.com/logandk/serverless-wsgi). ================================================ FILE: aws-python-flask-api/app.py ================================================ from flask import Flask, jsonify, make_response app = Flask(__name__) @app.route("/") def hello_from_root(): return jsonify(message='Hello from root!') @app.route("/hello") def hello(): return jsonify(message='Hello from path!') @app.errorhandler(404) def resource_not_found(e): return make_response(jsonify(error='Not found!'), 404) ================================================ FILE: aws-python-flask-api/package.json ================================================ { "name": "aws-python-flask-api", "version": "1.0.0", "description": "Example of a Python Flask API service with traditional Serverless Framework", "author": "", "devDependencies": { "serverless-python-requirements": "^6.1.0", "serverless-wsgi": "^3.0.4" } } ================================================ FILE: aws-python-flask-api/requirements.txt ================================================ Flask==3.0.3 ================================================ FILE: aws-python-flask-api/serverless.yml ================================================ service: aws-python-flask-api frameworkVersion: "4" custom: wsgi: app: app.app provider: name: aws runtime: python3.12 # Uncomment to easily set up a custom domain. Read the docs for more details: # https://www.serverless.com/framework/docs/providers/aws/guide/domains # domain: api.example.com functions: api: handler: wsgi_handler.handler events: - http: path: / method: ANY - http: path: /{proxy+} method: ANY plugins: - serverless-wsgi - serverless-python-requirements ================================================ FILE: aws-python-flask-dynamodb-api/README.md ================================================ # Serverless Framework Python Flask API service backed by DynamoDB on AWS This template demonstrates how to develop and deploy a simple Python Flask API service, backed by DynamoDB, running on AWS Lambda using the Serverless Framework. This template configures a single function, `api`, which is responsible for handling all incoming requests thanks to configured `http` events. To learn more about `http` event configuration options, please refer to [http event docs](https://www.serverless.com/framework/docs/providers/aws/events/apigateway/). As the events are configured in a way to accept all incoming requests, `Flask` framework is responsible for routing and handling requests internally. The implementation takes advantage of `serverless-wsgi`, which allows you to wrap WSGI applications such as Flask apps. To learn more about `serverless-wsgi`, please refer to corresponding [GitHub repository](https://github.com/logandk/serverless-wsgi). The template also relies on `serverless-python-requirements` plugin for packaging dependencies from `requirements.txt` file. For more details about `serverless-python-requirements` configuration, please refer to corresponding [GitHub repository](https://github.com/UnitedIncome/serverless-python-requirements). Additionally, the template also handles provisioning of a DynamoDB database that is used for storing data about users. The Flask application exposes two endpoints, `POST /users` and `GET /user/{userId}`, which allow to create and retrieve users. ## Usage ### Prerequisites In order to package your dependencies locally with `serverless-python-requirements`, you need to have `Python3.8` installed locally. You can create and activate a dedicated virtual environment with the following command: ``` python3.8 -m venv ./venv source ./venv/bin/activate ``` Alternatively, you can also use `dockerizePip` configuration from `serverless-python-requirements`. For details on that, please refer to corresponding [GitHub repository](https://github.com/UnitedIncome/serverless-python-requirements). ### Deployment install dependencies with: ``` npm install ``` and then perform deployment with: ``` serverless deploy ``` After running deploy, you should see output similar to: ``` Deploying "aws-python-flask-dynamodb-api" to stage "dev" (us-east-1) Using Python specified in "runtime": python3.12 Packaging Python WSGI handler... ✔ Service deployed to stack aws-python-flask-dynamodb-api-dev (123s) endpoints: ANY - https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/ ANY - https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/{proxy+} functions: api: aws-python-flask-dynamodb-api-dev-api (41 MB) ``` _Note_: In current form, after deployment, your API is public and can be invoked by anyone. For production deployments, you might want to configure an authorizer. For details on how to do that, refer to [http event docs](https://www.serverless.com/framework/docs/providers/aws/events/http-api). ### Invocation After successful deployment, you can create a new user by calling the corresponding endpoint: ``` curl --request POST 'https://xxxxxx.execute-api.us-east-1.amazonaws.com/dev/users' --header 'Content-Type: application/json' --data-raw '{"name": "John", "userId": "someUserId"}' ``` Which should result in the following response: ```json { "userId": "someUserId", "name": "John" } ``` You can later retrieve the user by `userId` by calling the following endpoint: ``` curl https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/users/someUserId ``` Which should result in the following response: ```json { "userId": "someUserId", "name": "John" } ``` ### Local development Thanks to capabilities of `serverless-wsgi`, it is also possible to run your application locally, however, in order to do that, you will need to first install `werkzeug`, `boto3` dependencies, as well as all other dependencies listed in `requirements.txt`. It is recommended to use a dedicated virtual environment for that purpose. You can install all needed dependencies with the following commands: ``` pip install werkzeug boto3 pip install -r requirements.txt ``` Additionally, you will need to emulate DynamoDB locally, which can be done by using `serverless-dynamodb-local` plugin. In order to do that, execute the following commands: ``` serverless plugin install -n serverless-dynamodb-local serverless dynamodb install ``` It will add the plugin to `devDependencies` in `package.json` file as well as to `plugins` section in `serverless.yml`. Additionally, it will also install DynamoDB locally. You should also add the following config to `custom` section in `serverless.yml`: ```yml custom: (...) dynamodb: start: migrate: true stages: - dev ``` Additionally, we need to reconfigure DynamoDB Client to connect to our local instance of DynamoDB. We can take advantage of `IS_OFFLINE` environment variable set by `serverless-wsgi` plugin and replace: ```python dynamodb_client = boto3.client('dynamodb') ``` with ```python dynamodb_client = boto3.client('dynamodb') if os.environ.get('IS_OFFLINE'): dynamodb_client = boto3.client('dynamodb', region_name='localhost', endpoint_url='http://localhost:8000') ``` Now you can start DynamoDB local with the following command: ``` serverless dynamodb start ``` At this point, you can run your application locally with the following command: ``` serverless wsgi serve ``` For additional local development capabilities of `serverless-wsgi` and `serverless-dynamodb-local` plugins, please refer to corresponding GitHub repositories: - https://github.com/logandk/serverless-wsgi - https://github.com/99x/serverless-dynamodb-local ================================================ FILE: aws-python-flask-dynamodb-api/app.py ================================================ import os import boto3 from flask import Flask, jsonify, make_response, request app = Flask(__name__) dynamodb_client = boto3.client('dynamodb') if os.environ.get('IS_OFFLINE'): dynamodb_client = boto3.client( 'dynamodb', region_name='localhost', endpoint_url='http://localhost:8000' ) USERS_TABLE = os.environ['USERS_TABLE'] @app.route('/users/') def get_user(user_id): result = dynamodb_client.get_item( TableName=USERS_TABLE, Key={'userId': {'S': user_id}} ) item = result.get('Item') if not item: return jsonify({'error': 'Could not find user with provided "userId"'}), 404 return jsonify( {'userId': item.get('userId').get('S'), 'name': item.get('name').get('S')} ) @app.route('/users', methods=['POST']) def create_user(): user_id = request.json.get('userId') name = request.json.get('name') if not user_id or not name: return jsonify({'error': 'Please provide both "userId" and "name"'}), 400 dynamodb_client.put_item( TableName=USERS_TABLE, Item={'userId': {'S': user_id}, 'name': {'S': name}} ) return jsonify({'userId': user_id, 'name': name}) @app.errorhandler(404) def resource_not_found(e): return make_response(jsonify(error='Not found!'), 404) ================================================ FILE: aws-python-flask-dynamodb-api/package.json ================================================ { "name": "aws-python-flask-dynamodb-api", "version": "1.0.0", "description": "Example of a Python Flask API service backed by DynamoDB with traditional Serverless Framework", "author": "", "devDependencies": { "serverless-python-requirements": "^6.1.0", "serverless-wsgi": "^3.0.4" } } ================================================ FILE: aws-python-flask-dynamodb-api/requirements.txt ================================================ Flask==3.0.3 ================================================ FILE: aws-python-flask-dynamodb-api/serverless.yml ================================================ service: aws-python-flask-dynamodb-api frameworkVersion: "4" stages: default: params: tableName: "users-table-${sls:stage}" plugins: - serverless-wsgi - serverless-python-requirements custom: wsgi: app: app.app provider: name: aws runtime: python3.12 # Uncomment to easily set up a custom domain. Read the docs for more details: # https://www.serverless.com/framework/docs/providers/aws/guide/domains # domain: api.example.com iam: role: statements: - Effect: Allow Action: - dynamodb:Query - dynamodb:Scan - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem Resource: - Fn::GetAtt: [UsersTable, Arn] environment: USERS_TABLE: ${param:tableName} functions: api: handler: wsgi_handler.handler events: - http: path: / method: ANY - http: path: /{proxy+} method: ANY resources: Resources: UsersTable: Type: AWS::DynamoDB::Table Properties: AttributeDefinitions: - AttributeName: userId AttributeType: S KeySchema: - AttributeName: userId KeyType: HASH ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 TableName: ${param:tableName} ================================================ FILE: aws-python-http-api/.gitignore ================================================ # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # Serverless directories .serverless ================================================ FILE: aws-python-http-api/README.md ================================================ # Serverless Framework Python HTTP API on AWS This template demonstrates how to make a simple HTTP API with Python running on AWS Lambda and API Gateway using the Serverless Framework. This template does not include any kind of persistence (database). For more advanced examples, check out the [serverless/examples repository](https://github.com/serverless/examples/) which includes DynamoDB, Mongo, Fauna and other examples. ## Usage ### Deployment ``` serverless deploy ``` After deploying, you should see output similar to: ``` Deploying "aws-python-http-api" to stage "dev" (us-east-1) ✔ Service deployed to stack aws-python-http-api-dev (85s) endpoint: GET - https://6ewcye3q4d.execute-api.us-east-1.amazonaws.com/ functions: hello: aws-python-http-api-dev-hello (2.3 kB) ``` _Note_: In current form, after deployment, your API is public and can be invoked by anyone. For production deployments, you might want to configure an authorizer. For details on how to do that, refer to [http event docs](https://www.serverless.com/framework/docs/providers/aws/events/apigateway/). ### Invocation After successful deployment, you can call the created application via HTTP: ``` curl https://xxxxxxx.execute-api.us-east-1.amazonaws.com/ ``` Which should result in response similar to the following (removed `input` content for brevity): ```json { "message": "Go Serverless v4.0! Your function executed successfully!" } ``` ### Local development You can invoke your function locally by using the following command: ``` serverless invoke local --function hello ``` Which should result in response similar to the following: ```json { "statusCode": 200, "body": "{\n \"message\": \"Go Serverless v4.0! Your function executed successfully!\"}" } ``` Alternatively, it is also possible to emulate API Gateway and Lambda locally by using `serverless-offline` plugin. In order to do that, execute the following command: ``` serverless plugin install -n serverless-offline ``` It will add the `serverless-offline` plugin to `devDependencies` in `package.json` file as well as will add it to `plugins` in `serverless.yml`. After installation, you can start local emulation with: ``` serverless offline ``` To learn more about the capabilities of `serverless-offline`, please refer to its [GitHub repository](https://github.com/dherault/serverless-offline). ### Bundling dependencies In case you would like to include 3rd party dependencies, you will need to use a plugin called `serverless-python-requirements`. You can set it up by running the following command: ``` serverless plugin install -n serverless-python-requirements ``` Running the above will automatically add `serverless-python-requirements` to `plugins` section in your `serverless.yml` file and add it as a `devDependency` to `package.json` file. The `package.json` file will be automatically created if it doesn't exist beforehand. Now you will be able to add your dependencies to `requirements.txt` file (`Pipfile` and `pyproject.toml` is also supported but requires additional configuration) and they will be automatically injected to Lambda package during build process. For more details about the plugin's configuration, please refer to [official documentation](https://github.com/UnitedIncome/serverless-python-requirements). ================================================ FILE: aws-python-http-api/handler.py ================================================ import json def hello(event, context): body = { "message": "Go Serverless v4.0! Your function executed successfully!", } response = {"statusCode": 200, "body": json.dumps(body)} return response ================================================ FILE: aws-python-http-api/serverless.yml ================================================ service: aws-python-http-api frameworkVersion: "4" provider: name: aws runtime: python3.12 # Uncomment to easily set up a custom domain. Read the docs for more details: # https://www.serverless.com/framework/docs/providers/aws/guide/domains # domain: api.example.com functions: hello: handler: handler.hello events: - httpApi: path: / method: get ================================================ FILE: aws-python-http-api-with-dynamodb/.gitignore ================================================ .serverless *.pyc *.pyo ================================================ FILE: aws-python-http-api-with-dynamodb/README.md ================================================ # Serverless HTTP API This example demonstrates how to setup an HTTP API allowing you to create, list, get, update and delete Todos. DynamoDB is used to store the data. This is just an example and of course you could use any data storage as a backend. ## Structure This service has a separate directory for all the todo operations. For each operation exactly one file exists e.g. `todos/delete.py`. In each of these files there is exactly one function defined. The idea behind the `todos` directory is that in case you want to create a service containing multiple resources e.g. users, notes, comments you could do so in the same service. While this is certainly possible you might consider creating a separate service for each resource. It depends on the use-case and your preference. ## Use-cases - API for a Web Application - API for a Mobile Application ## Setup ```bash npm install -g serverless ``` ## Deploy In order to deploy the endpoint simply run ```bash serverless deploy ``` The expected result should be similar to: ```bash Serverless: Packaging service… Serverless: Uploading CloudFormation file to S3… Serverless: Uploading service .zip file to S3… Serverless: Updating Stack… Serverless: Checking Stack update progress… Serverless: Stack update finished… Service Information service: serverless-http-api-dynamodb stage: dev region: us-east-1 api keys: None endpoints: POST - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos GET - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos GET - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos/{id} PUT - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos/{id} DELETE - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos/{id} functions: update: serverless-http-api-dynamodb-dev-update get: serverless-http-api-dynamodb-dev-get list: serverless-http-api-dynamodb-dev-list create: serverless-http-api-dynamodb-dev-create delete: serverless-http-api-dynamodb-dev-delete ``` ## Usage You can create, retrieve, update, or delete todos with the following commands: ### Create a Todo ```bash curl -X POST https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos --data '{ "text": "Learn Serverless" }' -H "Content-Type: application/json" ``` No output ### List all Todos ```bash curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos ``` Example output: ```bash [{"text":"Deploy my first service","id":"ac90feaa11e6-9ede-afdfa051af86","checked":true,"updatedAt":1479139961304},{"text":"Learn Serverless","id":"206793aa11e6-9ede-afdfa051af86","createdAt":1479139943241,"checked":false,"updatedAt":1479139943241}]% ``` ### Get one Todo ```bash # Replace the part with a real id from your todos table curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos/ ``` Example Result: ```bash {"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":false,"updatedAt":1479138570824}% ``` ### Update a Todo ```bash # Replace the part with a real id from your todos table curl -X PUT https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos/ --data '{ "text": "Learn Serverless", "checked": true }' -H "Content-Type: application/json" ``` Example Result: ```bash {"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":true,"updatedAt":1479138570824}% ``` ### Delete a Todo ```bash # Replace the part with a real id from your todos table curl -X DELETE https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos/ ``` No output ## Scaling ### AWS Lambda By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 1000. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit). ### DynamoDB When you create a table, you specify how much provisioned throughput capacity you want to reserve for reads and writes. DynamoDB will reserve the necessary resources to meet your throughput needs while ensuring consistent, low-latency performance. You can change the provisioned throughput and increasing or decreasing capacity as needed. This is can be done via settings in the `serverless.yml`. ```yaml ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 ``` In case you expect a lot of traffic fluctuation we recommend to checkout this guide on how to auto scale DynamoDB [https://aws.amazon.com/blogs/aws/auto-scale-dynamodb-with-dynamic-dynamodb/](https://aws.amazon.com/blogs/aws/auto-scale-dynamodb-with-dynamic-dynamodb/) ================================================ FILE: aws-python-http-api-with-dynamodb/package.json ================================================ { "name": "aws-http-with-dynamodb", "version": "1.0.0", "description": "Serverless HTTP API", "author": "", "license": "MIT" } ================================================ FILE: aws-python-http-api-with-dynamodb/serverless.yml ================================================ service: serverless-http-api-dynamodb frameworkVersion: '2' provider: name: aws runtime: python3.8 environment: DYNAMODB_TABLE: ${self:service}-${sls:stage} httpApi: cors: true iam: role: statements: - Effect: Allow Action: - dynamodb:Query - dynamodb:Scan - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem Resource: "arn:aws:dynamodb:${aws:region}:*:table/${self:provider.environment.DYNAMODB_TABLE}" functions: create: handler: todos/create.create events: - httpApi: path: /todos method: post list: handler: todos/list.list events: - httpApi: path: /todos method: get get: handler: todos/get.get events: - httpApi: path: /todos/{id} method: get update: handler: todos/update.update events: - httpApi: path: /todos/{id} method: put delete: handler: todos/delete.delete events: - httpApi: path: /todos/{id} method: delete resources: Resources: TodosDynamoDbTable: Type: 'AWS::DynamoDB::Table' DeletionPolicy: Retain Properties: AttributeDefinitions: - AttributeName: id AttributeType: S KeySchema: - AttributeName: id KeyType: HASH BillingMode: PAY_PER_REQUEST TableName: ${self:provider.environment.DYNAMODB_TABLE} ================================================ FILE: aws-python-http-api-with-dynamodb/todos/__init__.py ================================================ ================================================ FILE: aws-python-http-api-with-dynamodb/todos/create.py ================================================ import json import logging import os import time import uuid import boto3 dynamodb = boto3.resource('dynamodb') def create(event, context): data = json.loads(event['body']) if 'text' not in data: logging.error("Validation Failed") raise Exception("Couldn't create the todo item.") timestamp = str(time.time()) table = dynamodb.Table(os.environ['DYNAMODB_TABLE']) item = { 'id': str(uuid.uuid1()), 'text': data['text'], 'checked': False, 'createdAt': timestamp, 'updatedAt': timestamp, } # write the todo to the database table.put_item(Item=item) # create a response response = { "statusCode": 200, "body": json.dumps(item) } return response ================================================ FILE: aws-python-http-api-with-dynamodb/todos/decimalencoder.py ================================================ import decimal import json # This is a workaround for: http://bugs.python.org/issue16535 class DecimalEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, decimal.Decimal): return int(obj) return super(DecimalEncoder, self).default(obj) ================================================ FILE: aws-python-http-api-with-dynamodb/todos/delete.py ================================================ import os import boto3 dynamodb = boto3.resource('dynamodb') def delete(event, context): table = dynamodb.Table(os.environ['DYNAMODB_TABLE']) # delete the todo from the database table.delete_item( Key={ 'id': event['pathParameters']['id'] } ) # create a response response = { "statusCode": 200 } return response ================================================ FILE: aws-python-http-api-with-dynamodb/todos/get.py ================================================ import os import json from todos import decimalencoder import boto3 dynamodb = boto3.resource('dynamodb') def get(event, context): table = dynamodb.Table(os.environ['DYNAMODB_TABLE']) # fetch todo from the database result = table.get_item( Key={ 'id': event['pathParameters']['id'] } ) # create a response response = { "statusCode": 200, "body": json.dumps(result['Item'], cls=decimalencoder.DecimalEncoder) } return response ================================================ FILE: aws-python-http-api-with-dynamodb/todos/list.py ================================================ import json import os from todos import decimalencoder import boto3 dynamodb = boto3.resource('dynamodb') def list(event, context): table = dynamodb.Table(os.environ['DYNAMODB_TABLE']) # fetch all todos from the database result = table.scan() # create a response response = { "statusCode": 200, "body": json.dumps(result['Items'], cls=decimalencoder.DecimalEncoder) } return response ================================================ FILE: aws-python-http-api-with-dynamodb/todos/update.py ================================================ import json import time import logging import os from todos import decimalencoder import boto3 dynamodb = boto3.resource('dynamodb') def update(event, context): data = json.loads(event['body']) if 'text' not in data or 'checked' not in data: logging.error("Validation Failed") raise Exception("Couldn't update the todo item.") return timestamp = int(time.time() * 1000) table = dynamodb.Table(os.environ['DYNAMODB_TABLE']) # update the todo in the database result = table.update_item( Key={ 'id': event['pathParameters']['id'] }, ExpressionAttributeNames={ '#todo_text': 'text', }, ExpressionAttributeValues={ ':text': data['text'], ':checked': data['checked'], ':updatedAt': timestamp, }, UpdateExpression='SET #todo_text = :text, ' 'checked = :checked, ' 'updatedAt = :updatedAt', ReturnValues='ALL_NEW', ) # create a response response = { "statusCode": 200, "body": json.dumps(result['Attributes'], cls=decimalencoder.DecimalEncoder) } return response ================================================ FILE: aws-python-http-api-with-pynamodb/.gitignore ================================================ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ # Serverless .serverless/ .requirements ================================================ FILE: aws-python-http-api-with-pynamodb/README.md ================================================ # Serverless HTTP API This example demonstrates how to setup an HTTP API allowing you to create, list, get, update and delete Todos. DynamoDB is used to store the data. This is just an example and of course you could use any data storage as a backend. ## Structure This service has a separate directory for all the todo operations. For each operation exactly one file exists e.g. `todos/delete.py`. In each of these files there is exactly one function defined. The idea behind the `todos` directory is that in case you want to create a service containing multiple resources e.g. users, notes, comments you could do so in the same service. While this is certainly possible you might consider creating a separate service for each resource. It depends on the use-case and your preference. ## Use-cases - API for a Web Application - API for a Mobile Application ## Setup ```bash npm install ``` ## Deploy In order to deploy the endpoint simply run ```bash serverless deploy ``` The expected result should be similar to: ```bash Serverless: Packaging service… Serverless: Uploading CloudFormation file to S3… Serverless: Uploading service .zip file to S3… Serverless: Updating Stack… Serverless: Checking Stack update progress… Serverless: Stack update finished… Service Information service: serverless-http-api-pynamodb stage: dev region: us-east-1 api keys: None endpoints: POST - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos GET - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos GET - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos/{id} PUT - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos/{id} DELETE - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/todos/{id} functions: update: serverless-http-api-pynamodb-dev-update get: serverless-http-api-pynamodb-dev-get list: serverless-http-api-pynamodb-dev-list create: serverless-http-api-pynamodb-dev-create delete: serverless-http-api-pynamodb-dev-delete ``` ## Usage You can create, retrieve, update, or delete todos with the following commands: ### Create a Todo ```bash curl -X POST https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos --data '{ "text": "Learn Serverless" }' -H "Content-Type: application/json" ``` No output ### List all Todos ```bash curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos ``` Example output: ```bash [{"text":"Deploy my first service","id":"ac90feaa11e6-9ede-afdfa051af86","checked":true,"updatedAt":1479139961304},{"text":"Learn Serverless","id":"206793aa11e6-9ede-afdfa051af86","createdAt":1479139943241,"checked":false,"updatedAt":1479139943241}]% ``` ### Get one Todo ```bash # Replace the part with a real id from your todos table curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos/ ``` Example Result: ```bash {"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":false,"updatedAt":1479138570824}% ``` ### Update a Todo ```bash # Replace the part with a real id from your todos table curl -X PUT https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos/ --data '{ "text": "Learn Serverless", "checked": true }' -H "Content-Type: application/json" ``` Example Result: ```bash {"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":true,"updatedAt":1479138570824}% ``` ### Delete a Todo ```bash # Replace the part with a real id from your todos table curl -X DELETE https://XXXXXXX.execute-api.us-east-1.amazonaws.com/todos/ ``` No output ## Scaling ### AWS Lambda By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 1000. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit). ### DynamoDB When you create a table, you specify how much provisioned throughput capacity you want to reserve for reads and writes. DynamoDB will reserve the necessary resources to meet your throughput needs while ensuring consistent, low-latency performance. You can change the provisioned throughput and increasing or decreasing capacity as needed. This is can be done via settings in the `serverless.yml`. ```yaml ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 ``` In case you expect a lot of traffic fluctuation we recommend to checkout this guide on how to auto scale DynamoDB [https://aws.amazon.com/blogs/aws/auto-scale-dynamodb-with-dynamic-dynamodb/](https://aws.amazon.com/blogs/aws/auto-scale-dynamodb-with-dynamic-dynamodb/) ================================================ FILE: aws-python-http-api-with-pynamodb/package.json ================================================ { "name": "aws-http-with-pynamodb", "version": "1.0.0", "description": "Serverless CRUD service exposing an HTTP API", "author": "", "license": "MIT", "dependencies": { "serverless-python-requirements": "^5" } } ================================================ FILE: aws-python-http-api-with-pynamodb/requirements.txt ================================================ pynamodb==4.3.1 boto3 #no-deploy botocore #no-deploy ================================================ FILE: aws-python-http-api-with-pynamodb/serverless.yml ================================================ service: serverless-http-api-pynamodb frameworkVersion: '2' plugins: - serverless-python-requirements package: exclude: - node_modules/** - .idea/** - .requirements/** - env/** - README.md - package.json - package-lock.json - requirements.txt provider: name: aws runtime: python3.8 environment: DYNAMODB_TABLE: ${self:service}-${sls:stage} httpApi: cors: true iam: role: statements: - Effect: Allow Action: - dynamodb:Query - dynamodb:Scan - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem - dynamodb:DescribeTable Resource: "arn:aws:dynamodb:${aws:region}:*:table/${self:provider.environment.DYNAMODB_TABLE}" functions: create: handler: todos/create.create events: - httpApi: path: /todos method: post list: handler: todos/list.todo_list events: - httpApi: path: /todos method: get get: handler: todos/get.get events: - httpApi: path: /todos/{id} method: get update: handler: todos/update.update events: - httpApi: path: /todos/{todo_id} method: put delete: handler: todos/delete.delete events: - httpApi: path: /todos/{todo_id} method: delete resources: Resources: TodosDynamoDbTable: Type: 'AWS::DynamoDB::Table' DeletionPolicy: Retain Properties: AttributeDefinitions: - AttributeName: todo_id AttributeType: S KeySchema: - AttributeName: todo_id KeyType: HASH BillingMode: PAY_PER_REQUEST TableName: ${self:provider.environment.DYNAMODB_TABLE} ================================================ FILE: aws-python-http-api-with-pynamodb/todos/__init__.py ================================================ ================================================ FILE: aws-python-http-api-with-pynamodb/todos/create.py ================================================ import json import logging import uuid from todos.todo_model import TodoModel def create(event, context): print(event['body']) data = json.loads(event['body']) if 'text' not in data: logging.error('Validation Failed') return {'statusCode': 422, 'body': json.dumps({'error_message': 'Couldn\'t create the todo item.'})} if not data['text']: logging.error('Validation Failed - text was empty. %s', data) return {'statusCode': 422, 'body': json.dumps({'error_message': 'Couldn\'t create the todo item. As text was empty.'})} a_todo = TodoModel(todo_id=str(uuid.uuid1()), text=data['text'], checked=False) # write the todo to the database a_todo.save() # create a response return {'statusCode': 201, 'body': json.dumps(dict(a_todo))} ================================================ FILE: aws-python-http-api-with-pynamodb/todos/delete.py ================================================ import json from pynamodb.exceptions import DoesNotExist, DeleteError from todos.todo_model import TodoModel def delete(event, context): try: found_todo = TodoModel.get(hash_key=event['path']['todo_id']) except DoesNotExist: return {'statusCode': 404, 'body': json.dumps({'error_message': 'TODO was not found'})} try: found_todo.delete() except DeleteError: return {'statusCode': 400, 'body': json.dumps({'error_message': 'Unable to delete the TODO'})} # create a response return {'statusCode': 204} ================================================ FILE: aws-python-http-api-with-pynamodb/todos/get.py ================================================ import json from pynamodb.exceptions import DoesNotExist from todos.todo_model import TodoModel def get(event, context): try: found_todo = TodoModel.get(hash_key=event['path']['todo_id']) except DoesNotExist: return {'statusCode': 404, 'body': json.dumps({'error_message': 'TODO was not found'})} # create a response return {'statusCode': 200, 'body': json.dumps(dict(found_todo))} ================================================ FILE: aws-python-http-api-with-pynamodb/todos/list.py ================================================ import json from todos.todo_model import TodoModel def todo_list(event, context): # fetch all todos from the database results = TodoModel.scan() # create a response return {'statusCode': 200, 'body': json.dumps({'items': [dict(result) for result in results]})} ================================================ FILE: aws-python-http-api-with-pynamodb/todos/todo_model.py ================================================ import os from datetime import datetime from pynamodb.attributes import UnicodeAttribute, BooleanAttribute, UTCDateTimeAttribute from pynamodb.models import Model class TodoModel(Model): class Meta: table_name = os.environ['DYNAMODB_TABLE'] if 'ENV' in os.environ: host = 'http://localhost:8000' else: region = 'us-east-1' host = 'https://dynamodb.us-east-1.amazonaws.com' todo_id = UnicodeAttribute(hash_key=True, null=False) text = UnicodeAttribute(null=False) checked = BooleanAttribute(null=False) createdAt = UTCDateTimeAttribute(null=False, default=datetime.now()) updatedAt = UTCDateTimeAttribute(null=False) def save(self, conditional_operator=None, **expected_values): self.updatedAt = datetime.now() super(TodoModel, self).save() def __iter__(self): for name, attr in self._get_attributes().items(): yield name, attr.serialize(getattr(self, name)) ================================================ FILE: aws-python-http-api-with-pynamodb/todos/update.py ================================================ import json import logging from pynamodb.exceptions import DoesNotExist from todos.todo_model import TodoModel def update(event, context): # TODO: Figure out why this is behaving differently to the other endpoints # data = json.loads(event['body']) data = event['body'] if 'text' not in data and 'checked' not in data: logging.error('Validation Failed %s', data) return {'statusCode': 422, 'body': json.dumps({'error_message': 'Couldn\'t update the todo item.'})} try: found_todo = TodoModel.get(hash_key=event['path']['todo_id']) except DoesNotExist: return {'statusCode': 404, 'body': json.dumps({'error_message': 'TODO was not found'})} todo_changed = False if 'text' in data and data['text'] != found_todo.text: found_todo.text = data['text'] todo_changed = True if 'checked' in data and data['checked'] != found_todo.checked: found_todo.checked = data['checked'] todo_changed = True if todo_changed: found_todo.save() else: logging.info('Nothing changed did not update') # create a response return {'statusCode': 200, 'body': json.dumps(dict(found_todo))} ================================================ FILE: aws-python-line-echo-bot/README.md ================================================ # AWS-python-line-echo-bot This is a simple echo bot on LINE bot. (python) ## Before you start 1. LINE developer account 2. [LINE Messaging API](https://developers.line.biz/en/docs/messaging-api/getting-started/) ## Get Started 1. Install serverless via npm ```bash= $ npm install -g serverless ``` 2. Setup your AWS ceritficate ```bash= $ export AWS_ACCESS_KEY_ID= $ export AWS_SECRET_ACCESS_KEY= ``` 3. Setup you line bot secret & key ```python= line_bot_api = LineBotApi('YOUR_CHANNEL_ACCESS_TOKEN') handler = WebhookHandler('YOUR_CHANNEL_SECRET') ``` 4. Deploy the webhook function ```bash= $ npm install $ serverless deploy ``` ![Echo bot](https://i.imgur.com/Tn1XS13.png) ================================================ FILE: aws-python-line-echo-bot/handler.py ================================================ import json from linebot import ( LineBotApi, WebhookHandler ) from linebot.models import ( MessageEvent, TextMessage, TextSendMessage, ) def webhook(event, context): line_bot_api = LineBotApi('YOUR_CHANNEL_ACCESS_TOKEN') handler = WebhookHandler('YOUR_CHANNEL_SECRET') msg = json.loads(event['body']) line_bot_api.reply_message( msg['events'][0]['replyToken'], TextSendMessage(text=msg['events'][0]['message']['text']) ) response = { "statusCode": 200, "body": json.dumps({"message": 'ok'}) } return response ================================================ FILE: aws-python-line-echo-bot/package.json ================================================ { "name": "aws-python-line-echo-bot", "description": "this is echo bot on LINE message", "version": "0.1.0", "dependencies": {}, "devDependencies": { "serverless-python-requirements": "^4.3.0" }, "main": "handler.py", "autor": "NiJia", "license": "MIT" } ================================================ FILE: aws-python-line-echo-bot/requirements.txt ================================================ line-bot-sdk==1.12.1 ================================================ FILE: aws-python-line-echo-bot/serverless.yml ================================================ service: aws-python-line-echo-bot provider: name: aws runtime: python3.7 functions: line_bot: handler: handler.webhook events: - http: path: /webhook method: POST plugins: - serverless-python-requirements ================================================ FILE: aws-python-line-echo-bot/setup.cfg ================================================ [install] prefix= ================================================ FILE: aws-python-pynamodb-s3-sigurl/.gitignore ================================================ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ # Serverless .serverless/ .requirements ================================================ FILE: aws-python-pynamodb-s3-sigurl/README.md ================================================ # Serverless REST API This example demonstrates how to setup a [RESTful Web Service](https://en.wikipedia.org/wiki/Representational_state_transfer#Applied_to_web_services) using [Presigned URLs](http://boto3.readthedocs.io/en/latest/guide/s3.html?highlight=presigned#generating-presigned-urls) to manage asset uploads and downloads. The initial POST creates an asset entry in dynamo and returns a presigned upload URL. This is used to upload the asset without needing any credentials. An s3 event triggers another lambda method to mark the asset as "RECEIVED". One can then initiate a PUT to the asset's REST path to mark it as "UPLOADED" To download an asset, do a GET to the asset path to get a presigned download URL with an optional TTL. This URL can be used to retrieve the asset with no additional credentials. Also provides "LIST" and "DELETE" methods. DynamoDB is used to store the index and tracking data referring to the assets on s3. This is just an example and of course you could use any data storage as a backend. ## Structure This service has a separate directory for all the assets operations. For each operation exactly one file exists e.g. `assets/delete.py`. In each of these files there is exactly one function defined. ### Model The idea behind the `assets` directory is that in case you want to create a service containing multiple resources e.g. users, notes, comments you could do so in the same service. While this is certainly possible you might consider creating a separate service for each resource. It depends on the use-case and your preference. ### API GW Integration model All methods use `lambda` integration as that reduces the API GW interference in the payload. ### Logging The log_cfg.py is an alternate way to setup the python logging to be more friendly wth AWS lambda. The lambda default logging config is to not print any source file or line number which makes it harder to correleate with the source. Adding the import: ```python from log_cfg import logger ``` at the start of every event handler ensures that the format of the log messages are consistent, customizable and all in one place. Default format uses: ```python '%(asctime)-15s %(process)d-%(thread)d %(name)s [%(filename)s:%(lineno)d] :%(levelname)8s: %(message)s' ``` ### Notes Initial scaffold copied from the aws-python-rest-api-with-pynamodb example. The PUT method to mark the asset as UPLOADED is somewhat redundant as the S3 event that marks uploads as RECEIVED should be sufficient for most cases. However the goal was to use a PUT method to mark it received, so the PUT marks a RECEIVED asset as UPLOADED. That said, there is no distinction between UPLOADED vs RECEIVED anywhere in the example. The DELETE method does a `soft delete` which marks the asset as deleted without removing the s3 key. If the file on s3 is deleted, an event is generated which does fully delete the asset in dynamo as well. ## Setup ```bash npm install ``` ## Deploy In order to deploy the endpoint simply run ```bash serverless deploy ``` The expected result should be similar to: ```bash %> sls deploy Serverless: Parsing Python requirements.txt Serverless: Installing required Python packages for runtime python3.6... Serverless: Linking required Python packages... Serverless: Packaging service... Serverless: Excluding development dependencies... Serverless: Unlinking required Python packages... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading artifacts... Serverless: Uploading service .zip file to S3 (7.14 MB)... Serverless: Validating template... Serverless: Updating Stack... Serverless: Checking Stack update progress... ............................................ Serverless: Stack update finished... Service Information service: aws-python-pynamodb-s3-sigurl stage: dev region: us-east-1 stack: aws-python-pynamodb-s3-sigurl-dev api keys: None endpoints: POST - https://1xith51inb.execute-api.us-east-1.amazonaws.com/dev/asset GET - https://1xith51inb.execute-api.us-east-1.amazonaws.com/dev/asset GET - https://1xith51inb.execute-api.us-east-1.amazonaws.com/dev/asset/{asset_id} PUT - https://1xith51inb.execute-api.us-east-1.amazonaws.com/dev/asset/{asset_id} DELETE - https://1xith51inb.execute-api.us-east-1.amazonaws.com/dev/asset/{asset_id} functions: create: aws-python-pynamodb-s3-sigurl-dev-create bucket: aws-python-pynamodb-s3-sigurl-dev-bucket list: aws-python-pynamodb-s3-sigurl-dev-list get: aws-python-pynamodb-s3-sigurl-dev-get update: aws-python-pynamodb-s3-sigurl-dev-update delete: aws-python-pynamodb-s3-sigurl-dev-delete ``` ## Usage You can create, retrieve, update, or delete assets with the following commands: The $URL is the base URL specified in the POST endpoint above. `jsonpp` used to format the output for visibility but is not required for use. ### Get an asset pre-signed upload URL ```bash %> curl -sX POST $URL | jsonpp { "statusCode": 201, "body": { "upload_url": "", "id": "1a5ea69a-d30c-11e7-90d0-129b5a655d2d" } } ``` ### Upload a file to the URL ```bash %> curl -sX PUT --upload-file file.txt "" ``` ### Upload a file after pre-signed URL has expired ```bash %> curl -sX PUT --upload-file file.txt "" AccessDenied Request has expired 2027T01:03:04Z 2027T01:05:41Z D4EFA3C1A8DDD525 vS12oM24ZidzjG0JZon/y/8XD8whCKD/0JZappUNOekOJ3Eqp10Q5ne0emPVM/Mx6K1lYr0bi6c= ``` ### Mark asset as uploaded ```bash %> curl -sX PUT "$URL/1a5ea69a-d30c-11e7-90d0-129b5a655d2d" | jsonpp { "statusCode": 202, "body": { "status": "UPLOADED" } } ``` ### Get download URL for asset: ```bash %> curl -sX GET "$URL/1a5ea69a-d30c-11e7-90d0-129b5a655d2d" | jsonpp { "statusCode": 202, "body": { "download_url": "" } } ``` ### List all Assets ```bash %> curl -sX GET "$URL" | jsonpp { "statusCode": 200, "body": { "items": [ { "asset_id": "312abad30f-11e7-b0129b5a655d2d", "createdAt": "2027T01:05:21.830944+0000", "state": "UPLOADED", "updatedAt": "2027T01:07:05.311962+0000" }, { "asset_id": "0add1bcc-d30f-11e7-b0129b5a655d2d", "createdAt": "2027T01:05:21.830944+0000", "state": "UPLOADED", "updatedAt": "2027T01:06:19.413445+0000" }, { "asset_id": "57226ed30e-11e7-bda4-129b5a655d2d", "createdAt": "2027T01:00:20.296693+0000", "state": "CREATED", "updatedAt": "2027T01:02:57.750625+0000" } ] } } ``` ### Get one Asset download URL ```bash %> curl -sX GET "$URL/57226ed30e-11e7-bda4-129b5a655d2d" | jsonpp { "statusCode": 202, "body": { "download_url": "" } } ``` The returned URL is all that's needed to retrieve the asset file. ### Download asset Use the `download_url` returned from the above GET ```bash %> %> curl -sX GET "" --output file.txt ``` ### Download asset with expired URL Use the `download_url` returned from the above GET ```bash %> curl -sX GET "" AccessDenied Request has expired 2027T03:15:54Z 2027T03:17:25Z 09B6B5DD49895A40 mlN7TDikYBhehryCiGXtROuNCZL+/50kfvA0Ui2NP2JPVyTCY9hIbQxlsayB2rdxefhHfKn77mI= ``` ### Delete an asset ```bash %> curl -sX DELETE "$URL/312abad30f-11e7-b0129b5a655d2d" | jsonpp { "statusCode": 204 } ``` ## Scaling ### AWS Lambda By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 100. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit). ### AWS Lambda Edge Lambda is constrained to one region. The One can configure AWS cloudfront to pass events to lambda instances in the closest region using [Lambda Edge](https://aws.amazon.com/about-aws/whats-new/2017/07/lambda-at-edge-now-generally-available/). ### DynamoDB When you create a table, you specify how much provisioned throughput capacity you want to reserve for reads and writes. DynamoDB will reserve the necessary resources to meet your throughput needs while ensuring consistent, low-latency performance. You can change the provisioned throughput and increasing or decreasing capacity as needed. This is can be done via settings in the `serverless.yml`. ```yaml ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 ``` Dynamo now also supports an auto-scaling option to eliminate the need for manual capacity scaling. In case you expect a lot of traffic fluctuation we recommend to checkout this guide on how to auto scale DynamoDB [https://aws.amazon.com/blogs/aws/auto-scale-dynamodb-with-dynamic-dynamodb/](https://aws.amazon.com/blogs/aws/auto-scale-dynamodb-with-dynamic-dynamodb/) ## Troubleshoot If you experience any issue while building requirements, please refer to the [serverless-python-requirements](https://github.com/UnitedIncome/serverless-python-requirements) readme and use an alternate method to build requirements (pipenv, docker, binary path override). ================================================ FILE: aws-python-pynamodb-s3-sigurl/asset/__init__.py ================================================ ================================================ FILE: aws-python-pynamodb-s3-sigurl/asset/asset_model.py ================================================ from datetime import datetime from enum import Enum import boto3 import os from pynamodb.attributes import UnicodeAttribute, UTCDateTimeAttribute from pynamodb.models import Model from log_cfg import logger BUCKET = os.environ['S3_BUCKET'] KEY_BASE = os.environ['S3_KEY_BASE'] class State(Enum): """ Manage asset states in dynamo with a string field Could have used an int as well, or used a custom serializer which is a bit cleaner. """ CREATED = 1 RECEIVED = 2 UPLOADED = 3 DELETED = 4 class AssetModel(Model): class Meta: table_name = os.environ['DYNAMODB_TABLE'] if 'ENV' in os.environ: host = 'http://localhost:8000' else: region = os.environ['REGION'] host = os.environ['DYNAMODB_HOST'] # 'https://dynamodb.us-east-1.amazonaws.com' asset_id = UnicodeAttribute(hash_key=True) state = UnicodeAttribute(null=False, default=State.CREATED.name) createdAt = UTCDateTimeAttribute(null=False, default=datetime.now().astimezone()) updatedAt = UTCDateTimeAttribute(null=False, default=datetime.now().astimezone()) def __str__(self): return 'asset_id:{}, state:{}'.format(self.asset_id, self.state) def get_key(self): return u'{}/{}'.format(KEY_BASE, self.asset_id) def save(self, conditional_operator=None, **expected_values): try: self.updatedAt = datetime.now().astimezone() logger.debug('saving: {}'.format(self)) super(AssetModel, self).save() except Exception as e: logger.error('save {} failed: {}'.format(self.asset_id, e), exc_info=True) raise e def __iter__(self): for name, attr in self._get_attributes().items(): yield name, attr.serialize(getattr(self, name)) def get_upload_url(self, ttl=60): """ :param ttl: url duration in seconds :return: a temporary presigned PUT url """ s3 = boto3.client('s3') put_url = s3.generate_presigned_url( 'put_object', Params={ 'Bucket': BUCKET, 'Key': self.get_key() }, ExpiresIn=ttl, HttpMethod='PUT' ) logger.debug('upload URL: {}'.format(put_url)) return put_url def get_download_url(self, ttl=60): """ :param ttl: url duration in seconds :return: a temporary presigned download url """ s3 = boto3.client('s3') if self.state != State.UPLOADED.name: raise AssertionError( 'Asset {} is marked as {}, must be marked {} to retrieve.'.format( self.asset_id, self.state, State.UPLOADED.name ) ) get_url = s3.generate_presigned_url( 'get_object', Params={ 'Bucket': BUCKET, 'Key': self.get_key(), }, ExpiresIn=ttl, HttpMethod='GET' ) logger.debug('download URL: {}'.format(get_url)) return get_url def mark_received(self): """ Mark asset as having been received via the s3 objectCreated:Put event """ self.state = State.RECEIVED.name logger.debug('mark asset received: {}'.format(self.asset_id)) self.save() def mark_uploaded(self): """ Mark asset as having been uploaded via a PUT to the asset's REST path """ uploaded_states = [State.RECEIVED.name, State.UPLOADED.name] if self.state not in uploaded_states: raise AssertionError('State: \"{}\" must be one of {}'.format(self.state, uploaded_states)) self.state = State.UPLOADED.name logger.debug('mark asset uploaded: {}'.format(self.asset_id)) self.save() def mark_deleted(self): """ Mark asset as deleted (soft delete) """ self.state = State.DELETED.name logger.debug('mark asset deleted: {}'.format(self.asset_id)) self.save() ================================================ FILE: aws-python-pynamodb-s3-sigurl/asset/bucket.py ================================================ import http.client as httplib import os from pynamodb.exceptions import DoesNotExist, DeleteError, UpdateError from asset.asset_model import AssetModel from log_cfg import logger def event(event, context): """ Triggered by s3 events, object create and remove """ # Sample event: # # _event = {'Records': [{'eventVersion': '2.0', 'eventSource': 'aws:s3', 'awsRegion': 'us-east-1', # 'eventTime': '2017-11-25T23:57:38.988Z', 'eventName': 'ObjectCreated:Put', # 'userIdentity': {'principalId': 'AWS:AROAJWJG5IVL3URF4WKKK:su-xx-test-create'}, # 'requestParameters': {'sourceIPAddress': '75.82.111.45'}, # 'responseElements': {'x-amz-request-id': '9E39B8F9A3D22C83', # 'x-amz-id-2': 'GiWcmOHnxnxOJa64k5rkgTsiiwo+JOR3p2DvuQ6txQXl9jC0jNhO+gbDwwP/3WKAl4oPbVZsTE4='}, # 's3': {'s3SchemaVersion': '1.0', 'configurationId': 'dad7b639-0cd8-4e47-a2ae-91cc5bf866c8', # 'bucket': {'name': 'su-xx', 'ownerIdentity': {'principalId': 'AEZOG5WRKFUM2'}, # 'arn': 'arn:aws:s3:::su-xx'}, # 'object': {'key': 'test/bbc498ea-d23b-11e7-af42-2a31486da301', 'size': 11060, # 'eTag': 'd50cb2e8d7ad6768d46b3d47ba9b241e', # 'sequencer': '005A1A0372C5A1D292'}}}]} logger.debug('event: {}'.format(event)) event_name = event['Records'][0]['eventName'] key = event['Records'][0]['s3']['object']['key'] asset_id = key.replace('{}/'.format(os.environ['S3_KEY_BASE']), '') try: if 'ObjectCreated:Put' == event_name: try: asset = AssetModel.get(hash_key=asset_id) asset.mark_received() except UpdateError: return { 'statusCode': httplib.BAD_REQUEST, 'body': { 'error_message': 'Unable to update ASSET'} } elif 'ObjectRemoved:Delete' == event_name: try: asset = AssetModel.get(hash_key=asset_id) asset.delete() except DeleteError: return { 'statusCode': httplib.BAD_REQUEST, 'body': { 'error_message': 'Unable to delete ASSET {}'.format(asset) } } except DoesNotExist: return { 'statusCode': httplib.NOT_FOUND, 'body': { 'error_message': 'ASSET {} not found'.format(asset_id) } } return {'statusCode': httplib.ACCEPTED} ================================================ FILE: aws-python-pynamodb-s3-sigurl/asset/create.py ================================================ import uuid import http.client as httplib from asset.asset_model import AssetModel from log_cfg import logger def create(event, context): """ No body needed here as POST is a request for a pre-signed upload URL. Create an entry for it in dynamo and return upload URL """ # Sample events using different lambda integrations: # # _lambda_proxy_event = {'resource': '/asset', 'path': '/asset', 'httpMethod': 'POST', # 'headers': {'Accept': '*/*', 'CloudFront-Forwarded-Proto': 'https', # 'CloudFront-Is-Desktop-Viewer': 'true', 'CloudFront-Is-Mobile-Viewer': 'false', # 'CloudFront-Is-SmartTV-Viewer': 'false', 'CloudFront-Is-Tablet-Viewer': 'false', # 'CloudFront-Viewer-Country': 'US', # 'Host': 'c1xblyjsid.execute-api.us-east-1.amazonaws.com', # 'User-Agent': 'curl/7.56.1', # 'Via': '1.1 5c75b37c7e0aa5868b6499a5c4448d1f.cloudfront.net (CloudFront)', # 'X-Amz-Cf-Id': 'XG5WkkaGYGdbA9KAm7Hsbl5t7D7KmALE4Q2LdOwbXYoCFJZxyyiARw==', # 'X-Amzn-Trace-Id': 'Root=1-5a1b28a6-2b6e5ef6657e0f5f2d671017', # 'X-Forwarded-For': '75.82.111.45, 216.137.44.44', 'X-Forwarded-Port': '443', # 'X-Forwarded-Proto': 'https'}, 'queryStringParameters': None, # 'pathParameters': None, 'stageVariables': None, # 'requestContext': {'requestTime': '26/Nov/2017:20:48:38 +0000', 'path': '/dev/asset', # 'accountId': '818300131735', 'protocol': 'HTTP/1.1', # 'resourceId': 'wpjmgf', 'stage': 'dev', 'requestTimeEpoch': 1511729318077, # 'requestId': '2d827060-d2eb-11e7-96f5-9b58ecc94e3f', # 'identity': {'cognitoIdentityPoolId': None, 'accountId': None, # 'cognitoIdentityId': None, 'caller': None, 'apiKey': '', # 'sourceIp': '75.82.111.45', 'accessKey': None, # 'cognitoAuthenticationType': None, # 'cognitoAuthenticationProvider': None, 'userArn': None, # 'userAgent': 'curl/7.56.1', 'user': None}, # 'resourcePath': '/asset', 'httpMethod': 'POST', 'apiId': 'c1xblyjsid'}, # 'body': None, 'isBase64Encoded': False} # # _lambda_event = {'body': {}, 'method': 'POST', 'principalId': '', 'stage': 'dev', 'cognitoPoolClaims': {'sub': ''}, # 'headers': {'Accept': '*/*', 'CloudFront-Forwarded-Proto': 'https', # 'CloudFront-Is-Desktop-Viewer': 'true', 'CloudFront-Is-Mobile-Viewer': 'false', # 'CloudFront-Is-SmartTV-Viewer': 'false', 'CloudFront-Is-Tablet-Viewer': 'false', # 'CloudFront-Viewer-Country': 'US', # 'Host': 'c1xblyjsid.execute-api.us-east-1.amazonaws.com', 'User-Agent': 'curl/7.56.1', # 'Via': '1.1 022c901b294fedd7074704d46fce9819.cloudfront.net (CloudFront)', # 'X-Amz-Cf-Id': 'BifKUMLw8qO30TNbJ4QObNGq6WVxiL9nTv9eMbRtAIqqHIqQDkZEVw==', # 'X-Amzn-Trace-Id': 'Root=1-5a1b387c-47ab478111bbb2eb6bd6530c', # 'X-Forwarded-For': '75.82.111.45, 216.137.44.14', 'X-Forwarded-Port': '443', # 'X-Forwarded-Proto': 'https'}, 'query': {}, 'path': {}, # 'identity': {'cognitoIdentityPoolId': '', 'accountId': '', 'cognitoIdentityId': '', 'caller': '', # 'apiKey': '', 'sourceIp': '75.82.111.45', 'accessKey': '', # 'cognitoAuthenticationType': '', 'cognitoAuthenticationProvider': '', 'userArn': '', # 'userAgent': 'curl/7.56.1', 'user': ''}, 'stageVariables': {}} logger.debug('event: {}'.format(event)) asset = AssetModel() asset.asset_id = uuid.uuid1().__str__() asset.save() upload_url = asset.get_upload_url() # No timeout specified here, use member param default return { "statusCode": httplib.CREATED, "body": { 'upload_url': upload_url, 'id': asset.asset_id } } ================================================ FILE: aws-python-pynamodb-s3-sigurl/asset/delete.py ================================================ import http.client as httplib from pynamodb.exceptions import DoesNotExist, DeleteError from asset.asset_model import AssetModel from log_cfg import logger def delete(event, context): logger.debug('event: {}'.format(event)) try: asset_id = event['path']['asset_id'] asset = AssetModel.get(hash_key=asset_id) except DoesNotExist: return { 'statusCode': httplib.NOT_FOUND, 'body': { 'error_message': 'ASSET {} not found'.format(asset_id) } } try: asset.mark_deleted() except DeleteError: return { 'statusCode': httplib.BAD_REQUEST, 'body': { 'error_message': 'Unable to delete ASSET {}'.format(asset) } } return {'statusCode': httplib.NO_CONTENT} ================================================ FILE: aws-python-pynamodb-s3-sigurl/asset/get.py ================================================ import os import http.client as httplib from pynamodb.exceptions import DoesNotExist from asset.asset_model import AssetModel from log_cfg import logger def get(event, context): """ Get a presigned download URL for asset """ # Sample events using different lambda integrations: # # _lambda_event = { # 'body': {}, 'method': 'GET', 'principalId': '', 'stage': 'dev', 'cognitoPoolClaims': {'sub': ''}, # 'headers': {'Accept': '*/*', 'CloudFront-Forwarded-Proto': 'https', 'CloudFront-Is-Desktop-Viewer': 'true', # 'CloudFront-Is-Mobile-Viewer': 'false', 'CloudFront-Is-SmartTV-Viewer': 'false', # 'CloudFront-Is-Tablet-Viewer': 'false', 'CloudFront-Viewer-Country': 'US', # 'Host': 'c1xblyjsid.execute-api.us-east-1.amazonaws.com', 'User-Agent': 'curl/7.56.1', # 'Via': '1.1 57933097ddb189ecc8b3745fb94cfa94.cloudfront.net (CloudFront)', # 'X-Amz-Cf-Id': 'W95mJn3pc3G8T85Abt2Dj_wLPE_Ar_q0k56uF5yreiaNOMn6P2Nltw==', # 'X-Amzn-Trace-Id': 'Root=1-5a1b453d-1e857d3548e38a1c2827969e', # 'X-Forwarded-For': '75.82.111.45, 216.137.44.17', 'X-Forwarded-Port': '443', # 'X-Forwarded-Proto': 'https'}, 'query': {}, # 'path': {'asset_id': '0e4e06c6-d2fc-11e7-86c6-6672893a702e'}, # 'identity': {'cognitoIdentityPoolId': '', 'accountId': '', 'cognitoIdentityId': '', 'caller': '', # 'apiKey': '', 'sourceIp': '75.82.111.45', 'accessKey': '', 'cognitoAuthenticationType': '', # 'cognitoAuthenticationProvider': '', 'userArn': '', 'userAgent': 'curl/7.56.1', 'user': ''}, # 'stageVariables': {}} # # _lambda_event_with_timeout = { # 'body': {}, 'method': 'GET', 'principalId': '', 'stage': 'dev', # 'cognitoPoolClaims': {'sub': ''}, # 'headers': {'Accept': '*/*', 'CloudFront-Forwarded-Proto': 'https', # 'CloudFront-Is-Desktop-Viewer': 'true', # 'CloudFront-Is-Mobile-Viewer': 'false', # 'CloudFront-Is-SmartTV-Viewer': 'false', # 'CloudFront-Is-Tablet-Viewer': 'false', 'CloudFront-Viewer-Country': 'US', # 'Host': 'c1xblyjsid.execute-api.us-east-1.amazonaws.com', # 'User-Agent': 'curl/7.56.1', # 'Via': '1.1 7acf1813f9ec06038d676de15fcfc28f.cloudfront.net (CloudFront)', # 'X-Amz-Cf-Id': 'RBFBVYMys7aDqQ8u2Ktqvd-ZNwy-Kg7LPZ9LBTe-42nnx1wh0b5bGg==', # 'X-Amzn-Trace-Id': 'Root=1-5a1b4655-785e402d33e13e9d533281ef', # 'X-Forwarded-For': '75.82.111.45, 216.137.44.103', # 'X-Forwarded-Port': '443', 'X-Forwarded-Proto': 'https'}, # 'query': {'timeout': '1000000'}, # 'path': {'asset_id': '0e4e06c6-d2fc-11e7-86c6-6672893a702e'}, # 'identity': {'cognitoIdentityPoolId': '', 'accountId': '', 'cognitoIdentityId': '', # 'caller': '', 'apiKey': '', 'sourceIp': '75.82.111.45', 'accessKey': '', # 'cognitoAuthenticationType': '', 'cognitoAuthenticationProvider': '', # 'userArn': '', 'userAgent': 'curl/7.56.1', 'user': ''}, # 'stageVariables': {}} logger.debug('event: {}'.format(event)) try: ttl = os.environ['URL_DEFAULT_TTL'] try: ttl = int(event['query']['timeout']) except KeyError or ValueError: pass asset_id = event['path']['asset_id'] asset = AssetModel.get(hash_key=asset_id) download_url = asset.get_download_url(ttl) except DoesNotExist: return { 'statusCode': httplib.NOT_FOUND, 'body': { 'error_message': 'ASSET {} not found'.format(asset_id) } } except AssertionError as e: return { 'statusCode': httplib.FORBIDDEN, 'body': { 'error_message': 'Unable to download: {}'.format(e) } } return { "statusCode": httplib.ACCEPTED, "body": { 'download_url': download_url } } ================================================ FILE: aws-python-pynamodb-s3-sigurl/asset/list.py ================================================ import http.client as httplib from asset.asset_model import AssetModel from log_cfg import logger def asset_list(event, context): logger.debug('event: {}, context: {}'.format(event, context)) results = AssetModel.scan() return { 'statusCode': httplib.OK, 'body': { 'items': [dict(result) for result in results] } } ================================================ FILE: aws-python-pynamodb-s3-sigurl/asset/update.py ================================================ import http.client as httplib from pynamodb.exceptions import DoesNotExist from asset.asset_model import AssetModel from log_cfg import logger def update(event, context): logger.debug('event: {}'.format(event)) try: asset_id = event['path']['asset_id'] asset = AssetModel.get(hash_key=asset_id) asset.mark_uploaded() except AssertionError as e: return { 'statusCode': httplib.PRECONDITION_FAILED, 'body': { 'error_message': 'ASSET {} state incorrect: {}'.format(asset_id, e) } } except DoesNotExist: return { 'statusCode': httplib.NOT_FOUND, 'body': { 'error_message': 'ASSET {} not found'.format(asset_id) } } return { "statusCode": httplib.ACCEPTED, "body": { 'status': asset.state } } ================================================ FILE: aws-python-pynamodb-s3-sigurl/log_cfg.py ================================================ import logging import sys """ All lambda methods use this loging config. Provides a single place where all log config/level/formatting is setup so that one can see source file, line numbers, and any other desired log fields. """ logger = logging.getLogger() for h in logger.handlers: logger.removeHandler(h) h = logging.StreamHandler(sys.stdout) # use whatever format you want here FORMAT = '%(asctime)-15s %(process)d-%(thread)d %(name)s [%(filename)s:%(lineno)d] :%(levelname)8s: %(message)s' h.setFormatter(logging.Formatter(FORMAT)) logger.addHandler(h) logger.setLevel(logging.DEBUG) # Suppress the more verbose modules logging.getLogger('__main__').setLevel(logging.DEBUG) logging.getLogger('botocore').setLevel(logging.WARN) logging.getLogger('pynamodb').setLevel(logging.INFO) ================================================ FILE: aws-python-pynamodb-s3-sigurl/package.json ================================================ { "name": "aws-python-pynamodb-s3-sigurl", "version": "1.0.0", "description": "Serverless signed uploader REST API using pynamodb, s3 generated events, custom log format, and DRY serverless.yml with custom section", "author": "Bruce Edge", "license": "MIT", "dependencies": { "serverless-python-requirements": "^5.0.1" } } ================================================ FILE: aws-python-pynamodb-s3-sigurl/requirements.txt ================================================ pynamodb>=3.1.0 boto3 #no-deploy botocore #no-deploy ================================================ FILE: aws-python-pynamodb-s3-sigurl/serverless.yml ================================================ service: aws-python-pynamodb-s3-sigurl frameworkVersion: ">=2.24.0" plugins: - serverless-python-requirements package: exclude: - node_modules/** - .idea/** - .requirements/** - env/** - README.md - package.json - package-lock.json - requirements.txt # DRY constants: define all compound/generated names in one place # Override args are: .. defaults: # --app_acroym signed-uploader # --s3_bucket self:custom.app_acronym # --s3_key_base self:custom.stage # --region us-east-1 # --stage test # --deletion_policy delete custom: app_acronym: sig-s3-uploader default_stage: test stage: ${opt:stage, self:custom.default_stage} stack_name: ${self:custom.app_acronym}-${self:custom.stage} region: ${opt:region, self:provider.region} deletion_policy: Delete dynamodb_table: ${self:custom.stack_name} dynamodb_arn: arn:aws:dynamodb:${self:custom.region}:*:table/${self:custom.dynamodb_table} dynamodb_host: https://dynamodb.${self:custom.region}.amazonaws.com # Default to using app_acronym as bucket name s3_bucket: ${opt:s3_bucket, self:custom.app_acronym} # default to using ${stage} as key base path, keeps stages from namespace collisions s3_key_base: ${opt:s3_key_base, self:custom.stage} s3_bucket_arn: arn:aws:s3:::${self:custom.s3_bucket} s3_role_resource: ${self:custom.s3_bucket_arn}/${self:custom.s3_key_base}/* # Put this here rather than in code (presigned URL TTL) url_default_ttl: 60 provider: name: aws runtime: python3.6 region: us-east-1 environment: DYNAMODB_TABLE: ${self:custom.dynamodb_table} DYNAMODB_HOST: ${self:custom.dynamodb_host} REGION: ${self:custom.region} S3_BUCKET: ${self:custom.s3_bucket} S3_KEY_BASE: ${self:custom.s3_key_base} URL_DEFAULT_TTL: ${self:custom.url_default_ttl} iam: role: statements: - Effect: Allow Action: - dynamodb:Query - dynamodb:Scan - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem - dynamodb:DescribeTable Resource: ${self:custom.dynamodb_arn} - Effect: Allow Action: - s3:* Resource: ${self:custom.s3_role_resource} functions: create: name: ${self:custom.stack_name}-create description: Generate a presigned URL for PUT upload handler: asset/create.create events: - http: path: asset method: post cors: true integration: lambda bucket: handler: asset/bucket.event name: ${self:custom.stack_name}-bucket description: Called by s3 create/remove events to manage asset state in dynamo events: - s3: bucket: ${self:custom.s3_bucket} event: s3:ObjectCreated:* rules: - prefix: ${self:custom.s3_key_base} - s3: bucket: ${self:custom.s3_bucket} event: s3:ObjectRemoved:* rules: - prefix: ${self:custom.s3_key_base} list: handler: asset/list.asset_list name: ${self:custom.stack_name}-list description: List all assets events: - http: path: asset method: get cors: true integration: lambda get: handler: asset/get.get name: ${self:custom.stack_name}-get description: Get a presigned download url for events: - http: path: asset/{asset_id} method: get cors: true integration: lambda request: paths: asset_id: true parameters: paths: timeout: true update: handler: asset/update.update name: ${self:custom.stack_name}-update description: Mark an RECEIVED asset as UPLOADED, fail if not RECEIVED events: - http: path: asset/{asset_id} method: put cors: true integration: lambda request: paths: asset_id: true delete: handler: asset/delete.delete name: ${self:custom.stack_name}-delete description: Delete an asset by events: - http: path: asset/{asset_id} method: delete cors: true integration: lambda request: paths: asset_id: true resources: Resources: # Comment assetDynamoDbTable if using existing table assetDynamoDbTable: Type: 'AWS::DynamoDB::Table' DeletionPolicy: ${self:custom.deletion_policy} Properties: AttributeDefinitions: - AttributeName: asset_id AttributeType: S KeySchema: - AttributeName: asset_id KeyType: HASH ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 TableName: ${self:custom.dynamodb_table} ================================================ FILE: aws-python-rest-api/.gitignore ================================================ # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # Serverless directories .serverless ================================================ FILE: aws-python-rest-api/README.md ================================================ This template demonstrates how to make a simple REST API with Python running on AWS Lambda and API Gateway using the traditional Serverless Framework. # Serverless Framework Python REST API on AWS This template demonstrates how to make a simple REST API with Python running on AWS Lambda and API Gateway using the traditional Serverless Framework. This template does not include any kind of persistence (database). For a more advanced examples check out the [examples repo](https://github.com/serverless/examples/) which includes DynamoDB, Mongo, Fauna and other examples. ## Usage ### Deployment This example is made to work with the Serverless Framework dashboard which includes advanced features like CI/CD, monitoring, metrics, etc. ``` $ serverless login $ serverless deploy ``` To deploy without the dashboard you will need to remove `org` and `app` fields from the `serverless.yml`, and you won’t have to run `sls login` before deploying. After running deploy, you should see output similar to: ```bash Serverless: Packaging service... Serverless: Excluding development dependencies... Serverless: Creating Stack... Serverless: Checking Stack create progress... ........ Serverless: Stack create finished... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading artifacts... Serverless: Uploading service aws-python-rest-api.zip file to S3 (711.23 KB)... Serverless: Validating template... Serverless: Updating Stack... Serverless: Checking Stack update progress... ................................. Serverless: Stack update finished... Service Information service: aws-python-rest-api stage: dev region: us-east-1 stack: aws-python-rest-api-dev resources: 12 api keys: None endpoints: ANY - https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/ functions: api: aws-python-rest-api-dev-hello layers: None ``` _Note_: In current form, after deployment, your API is public and can be invoked by anyone. For production deployments, you might want to configure an authorizer. For details on how to do that, refer to [http event docs](https://www.serverless.com/framework/docs/providers/aws/events/apigateway/). ### Invocation After successful deployment, you can call the created application via HTTP: ```bash curl https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/ ``` Which should result in response similar to the following (removed `input` content for brevity): ```json { "message": "Go Serverless v2.0! Your function executed successfully!", "input": { ... } } ``` ### Local development You can invoke your function locally by using the following command: ```bash serverless invoke local --function hello ``` Which should result in response similar to the following: ``` { "statusCode": 200, "body": "{\n \"message\": \"Go Serverless v2.0! Your function executed successfully!\",\n \"input\": \"\"\n}" } ``` Alternatively, it is also possible to emulate API Gateway and Lambda locally by using `serverless-offline` plugin. In order to do that, execute the following command: ```bash serverless plugin install -n serverless-offline ``` It will add the `serverless-offline` plugin to `devDependencies` in `package.json` file as well as will add it to `plugins` in `serverless.yml`. After installation, you can start local emulation with: ``` serverless offline ``` To learn more about the capabilities of `serverless-offline`, please refer to its [GitHub repository](https://github.com/dherault/serverless-offline). ### Bundling dependencies In case you would like to include 3rd party dependencies, you will need to use a plugin called `serverless-python-requirements`. You can set it up by running the following command: ```bash serverless plugin install -n serverless-python-requirements ``` Running the above will automatically add `serverless-python-requirements` to `plugins` section in your `serverless.yml` file and add it as a `devDependency` to `package.json` file. The `package.json` file will be automatically created if it doesn't exist beforehand. Now you will be able to add your dependencies to `requirements.txt` file (`Pipfile` and `pyproject.toml` is also supported but requires additional configuration) and they will be automatically injected to Lambda package during build process. For more details about the plugin's configuration, please refer to [official documentation](https://github.com/UnitedIncome/serverless-python-requirements). ================================================ FILE: aws-python-rest-api/handler.py ================================================ import json def hello(event, context): body = { "message": "Go Serverless v2.0! Your function executed successfully!", "input": event, } response = {"statusCode": 200, "body": json.dumps(body)} return response # Use this code if you don't use the http event with the LAMBDA-PROXY # integration """ return { "message": "Go Serverless v1.0! Your function executed successfully!", "event": event } """ ================================================ FILE: aws-python-rest-api/serverless.template.yml ================================================ name: aws-python-rest-api org: serverlessinc description: Deploys a Python REST API service with traditional Serverless Framework keywords: aws, serverless, faas, lambda, python repo: https://github.com/serverless/examples/aws-python-rest-api license: MIT ================================================ FILE: aws-python-rest-api/serverless.yml ================================================ service: aws-python-rest-api frameworkVersion: '2' provider: name: aws runtime: python3.8 lambdaHashingVersion: '20201221' functions: hello: handler: handler.hello events: - http: path: / method: get ================================================ FILE: aws-python-rest-api-with-dynamodb/.gitignore ================================================ .serverless *.pyc *.pyo ================================================ FILE: aws-python-rest-api-with-dynamodb/README.md ================================================ # Serverless REST API This example demonstrates how to setup a [RESTful Web Services](https://en.wikipedia.org/wiki/Representational_state_transfer#Applied_to_web_services) allowing you to create, list, get, update and delete Todos. DynamoDB is used to store the data. This is just an example and of course you could use any data storage as a backend. ## Structure This service has a separate directory for all the todo operations. For each operation exactly one file exists e.g. `todos/delete.py`. In each of these files there is exactly one function defined. The idea behind the `todos` directory is that in case you want to create a service containing multiple resources e.g. users, notes, comments you could do so in the same service. While this is certainly possible you might consider creating a separate service for each resource. It depends on the use-case and your preference. ## Use-cases - API for a Web Application - API for a Mobile Application ## Setup ```bash npm install -g serverless ``` ## Deploy In order to deploy the endpoint simply run ```bash serverless deploy ``` The expected result should be similar to: ```bash Serverless: Packaging service… Serverless: Uploading CloudFormation file to S3… Serverless: Uploading service .zip file to S3… Serverless: Updating Stack… Serverless: Checking Stack update progress… Serverless: Stack update finished… Service Information service: serverless-rest-api-with-dynamodb stage: dev region: us-east-1 api keys: None endpoints: POST - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/dev/todos GET - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/dev/todos GET - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/dev/todos/{id} PUT - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/dev/todos/{id} DELETE - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/dev/todos/{id} functions: serverless-rest-api-with-dynamodb-dev-update: arn:aws:lambda:us-east-1:488110005556:function:serverless-rest-api-with-dynamodb-dev-update serverless-rest-api-with-dynamodb-dev-get: arn:aws:lambda:us-east-1:488110005556:function:serverless-rest-api-with-dynamodb-dev-get serverless-rest-api-with-dynamodb-dev-list: arn:aws:lambda:us-east-1:488110005556:function:serverless-rest-api-with-dynamodb-dev-list serverless-rest-api-with-dynamodb-dev-create: arn:aws:lambda:us-east-1:488110005556:function:serverless-rest-api-with-dynamodb-dev-create serverless-rest-api-with-dynamodb-dev-delete: arn:aws:lambda:us-east-1:488110005556:function:serverless-rest-api-with-dynamodb-dev-delete ``` ## Usage You can create, retrieve, update, or delete todos with the following commands: ### Create a Todo ```bash curl -X POST https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/todos --data '{ "text": "Learn Serverless" }' ``` No output ### List all Todos ```bash curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/todos ``` Example output: ```bash [{"text":"Deploy my first service","id":"ac90feaa11e6-9ede-afdfa051af86","checked":true,"updatedAt":1479139961304},{"text":"Learn Serverless","id":"206793aa11e6-9ede-afdfa051af86","createdAt":1479139943241,"checked":false,"updatedAt":1479139943241}]% ``` ### Get one Todo ```bash # Replace the part with a real id from your todos table curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/todos/ ``` Example Result: ```bash {"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":false,"updatedAt":1479138570824}% ``` ### Update a Todo ```bash # Replace the part with a real id from your todos table curl -X PUT https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/todos/ --data '{ "text": "Learn Serverless", "checked": true }' ``` Example Result: ```bash {"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":true,"updatedAt":1479138570824}% ``` ### Delete a Todo ```bash # Replace the part with a real id from your todos table curl -X DELETE https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/todos/ ``` No output ## Scaling ### AWS Lambda By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 100. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit). ### DynamoDB When you create a table, you specify how much provisioned throughput capacity you want to reserve for reads and writes. DynamoDB will reserve the necessary resources to meet your throughput needs while ensuring consistent, low-latency performance. You can change the provisioned throughput and increasing or decreasing capacity as needed. This is can be done via settings in the `serverless.yml`. ```yaml ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 ``` In case you expect a lot of traffic fluctuation we recommend to checkout this guide on how to auto scale DynamoDB [https://aws.amazon.com/blogs/aws/auto-scale-dynamodb-with-dynamic-dynamodb/](https://aws.amazon.com/blogs/aws/auto-scale-dynamodb-with-dynamic-dynamodb/) ================================================ FILE: aws-python-rest-api-with-dynamodb/package.json ================================================ { "name": "aws-rest-with-dynamodb", "version": "1.0.0", "description": "Serverless CRUD service exposing a REST HTTP interface", "author": "", "license": "MIT" } ================================================ FILE: aws-python-rest-api-with-dynamodb/serverless.yml ================================================ service: serverless-rest-api-with-dynamodb frameworkVersion: ">=2.24.0" provider: name: aws runtime: python3.8 environment: DYNAMODB_TABLE: ${self:service}-${opt:stage, self:provider.stage} iam: role: statements: - Effect: Allow Action: - dynamodb:Query - dynamodb:Scan - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}" functions: create: handler: todos/create.create events: - http: path: todos method: post cors: true list: handler: todos/list.list events: - http: path: todos method: get cors: true get: handler: todos/get.get events: - http: path: todos/{id} method: get cors: true update: handler: todos/update.update events: - http: path: todos/{id} method: put cors: true delete: handler: todos/delete.delete events: - http: path: todos/{id} method: delete cors: true resources: Resources: TodosDynamoDbTable: Type: 'AWS::DynamoDB::Table' DeletionPolicy: Retain Properties: AttributeDefinitions: - AttributeName: id AttributeType: S KeySchema: - AttributeName: id KeyType: HASH ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 TableName: ${self:provider.environment.DYNAMODB_TABLE} ================================================ FILE: aws-python-rest-api-with-dynamodb/todos/__init__.py ================================================ ================================================ FILE: aws-python-rest-api-with-dynamodb/todos/create.py ================================================ import json import logging import os import time import uuid import boto3 dynamodb = boto3.resource('dynamodb') def create(event, context): data = json.loads(event['body']) if 'text' not in data: logging.error("Validation Failed") raise Exception("Couldn't create the todo item.") timestamp = str(time.time()) table = dynamodb.Table(os.environ['DYNAMODB_TABLE']) item = { 'id': str(uuid.uuid1()), 'text': data['text'], 'checked': False, 'createdAt': timestamp, 'updatedAt': timestamp, } # write the todo to the database table.put_item(Item=item) # create a response response = { "statusCode": 200, "body": json.dumps(item) } return response ================================================ FILE: aws-python-rest-api-with-dynamodb/todos/decimalencoder.py ================================================ import decimal import json # This is a workaround for: http://bugs.python.org/issue16535 class DecimalEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, decimal.Decimal): return int(obj) return super(DecimalEncoder, self).default(obj) ================================================ FILE: aws-python-rest-api-with-dynamodb/todos/delete.py ================================================ import os import boto3 dynamodb = boto3.resource('dynamodb') def delete(event, context): table = dynamodb.Table(os.environ['DYNAMODB_TABLE']) # delete the todo from the database table.delete_item( Key={ 'id': event['pathParameters']['id'] } ) # create a response response = { "statusCode": 200 } return response ================================================ FILE: aws-python-rest-api-with-dynamodb/todos/get.py ================================================ import os import json from todos import decimalencoder import boto3 dynamodb = boto3.resource('dynamodb') def get(event, context): table = dynamodb.Table(os.environ['DYNAMODB_TABLE']) # fetch todo from the database result = table.get_item( Key={ 'id': event['pathParameters']['id'] } ) # create a response response = { "statusCode": 200, "body": json.dumps(result['Item'], cls=decimalencoder.DecimalEncoder) } return response ================================================ FILE: aws-python-rest-api-with-dynamodb/todos/list.py ================================================ import json import os from todos import decimalencoder import boto3 dynamodb = boto3.resource('dynamodb') def list(event, context): table = dynamodb.Table(os.environ['DYNAMODB_TABLE']) # fetch all todos from the database result = table.scan() # create a response response = { "statusCode": 200, "body": json.dumps(result['Items'], cls=decimalencoder.DecimalEncoder) } return response ================================================ FILE: aws-python-rest-api-with-dynamodb/todos/update.py ================================================ import json import time import logging import os from todos import decimalencoder import boto3 dynamodb = boto3.resource('dynamodb') def update(event, context): data = json.loads(event['body']) if 'text' not in data or 'checked' not in data: logging.error("Validation Failed") raise Exception("Couldn't update the todo item.") return timestamp = int(time.time() * 1000) table = dynamodb.Table(os.environ['DYNAMODB_TABLE']) # update the todo in the database result = table.update_item( Key={ 'id': event['pathParameters']['id'] }, ExpressionAttributeNames={ '#todo_text': 'text', }, ExpressionAttributeValues={ ':text': data['text'], ':checked': data['checked'], ':updatedAt': timestamp, }, UpdateExpression='SET #todo_text = :text, ' 'checked = :checked, ' 'updatedAt = :updatedAt', ReturnValues='ALL_NEW', ) # create a response response = { "statusCode": 200, "body": json.dumps(result['Attributes'], cls=decimalencoder.DecimalEncoder) } return response ================================================ FILE: aws-python-rest-api-with-faunadb/.gitignore ================================================ .serverless .requirements venv *.pyc *.pyo ================================================ FILE: aws-python-rest-api-with-faunadb/README.md ================================================ # Serverless REST API This example demonstrates how to setup a [RESTful Web Services](https://en.wikipedia.org/wiki/Representational_state_transfer#Applied_to_web_services) allowing you to create, list, get, update and delete Todos. FaunaDB is used to store the data. ## Structure This service has a separate directory for all the todo operations. For each operation exactly one file exists e.g. `todos/delete.py`. In each of these files there is exactly one function defined. The idea behind the `todos` directory is that in case you want to create a service containing multiple resources e.g. users, notes, comments you could do so in the same service. While this is certainly possible you might consider creating a separate service for each resource. It depends on the use-case and your preference. ## Use-cases - API for a Web Application - API for a Mobile Application ## FaunaDB Secret Visit https://fauna.com/serverless-cloud-sign-up to obtain a `FAUNADB_SECRET` to use in `serverless.yml`. ## Setup With your FaunaDB Secret in hand, set it in `serverless.yml` ```yml environment: FAUNADB_SECRET: YOUR-SECRET-HERE ``` To avoid the error message `DistutilsOptionError: must supply either home or prefix/exec-prefix -- not both` first is necessary create a python virtual environment ```bash virtualenv -p `which python` venv source venv/bin/activate ``` In order to make it easy to package in this example we're using the node plugin `serverless-python-requirements`, so install it with ```bash npm install ``` ## Deploy In order to deploy the endpoint simply run ```bash serverless deploy ``` The expected result should be similar to: ```bash Serverless: Installing required Python packages... Serverless: Linking required Python packages... Serverless: Packaging service... Serverless: Unlinking required Python packages... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading service .zip file to S3 (2.33 MB)... Serverless: Updating Stack... Serverless: Checking Stack update progress... ...................................... Serverless: Stack update finished... Serverless: Removing old service versions... Service Information service: serverless-rest-api-with-faunadb stage: dev region: us-east-1 api keys: None endpoints: POST - https://bo19b9b32h.execute-api.us-east-1.amazonaws.com/dev/todos GET - https://bo19b9b32h.execute-api.us-east-1.amazonaws.com/dev/todos GET - https://bo19b9b32h.execute-api.us-east-1.amazonaws.com/dev/todos/{id} PUT - https://bo19b9b32h.execute-api.us-east-1.amazonaws.com/dev/todos/{id} DELETE - https://bo19b9b32h.execute-api.us-east-1.amazonaws.com/dev/todos/{id} functions: create: serverless-rest-api-with-faunadb-dev-create list: serverless-rest-api-with-faunadb-dev-list get: serverless-rest-api-with-faunadb-dev-get update: serverless-rest-api-with-faunadb-dev-update delete: serverless-rest-api-with-faunadb-dev-delete ``` ## Setup schema Before you execute any command, first you have to setup a FaunaDB schema with the command: ```bash serverless invoke --function schema ``` ## Usage You can create, retrieve, update, or delete todos with the following commands: ### Create a Todo ```bash curl -X POST https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/todos --data '{ "text": "Learn Serverless" }' ``` No output ### List all Todos ```bash curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/todos ``` Example output: ```json [{"text": "Deploy my first service", "id": "159546695821033477", "checked": true, "updatedAt": 1479139961304}, {"text": "Learn Serverless", "id": "159547069624745989", "createdAt": 1479139943241, "checked": false, "updatedAt": 1479139943241}] ``` ### Get one Todo ```bash # Replace the part with a real id from your todos class curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/todos/ ``` Example Result: ```json {"text": "Learn Serverless", "id": "159547069624745989", "createdAt": 1479138570824, "checked": false, "updatedAt": 1479138570824} ``` ### Update a Todo ```bash # Replace the part with a real id from your todos class curl -X PUT https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/todos/ --data '{ "text": "Learn Serverless", "checked": true }' ``` Example Result: ```json {"text": "Learn Serverless", "id": "159547069624745989", "createdAt": 1479138570824, "checked": true, "updatedAt": 1479138570824} ``` ### Delete a Todo ```bash # Replace the part with a real id from your todos class curl -X DELETE https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/todos/ ``` No output ## Scaling ### AWS Lambda By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 100. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit). ================================================ FILE: aws-python-rest-api-with-faunadb/package.json ================================================ { "name": "aws-rest-with-faunadb", "version": "1.0.0", "description": "Serverless CRUD service exposing a REST HTTP interface", "author": "", "license": "MIT", "dependencies": { "serverless-python-requirements": "^2.0.0-beta.6" } } ================================================ FILE: aws-python-rest-api-with-faunadb/requirements.txt ================================================ faunadb==0.1.2 ================================================ FILE: aws-python-rest-api-with-faunadb/serverless.yml ================================================ service: serverless-rest-api-with-faunadb frameworkVersion: ">=1.1.0 <2.0.0" package: exclude: - node_modules/** - venv/** plugins: - serverless-python-requirements provider: name: aws runtime: python2.7 region: us-east-1 stage: dev environment: FAUNADB_SECRET: YOUR-SECRET-HERE functions: create: handler: todos/create.create events: - http: path: todos method: post cors: true list: handler: todos/list.list events: - http: path: todos method: get cors: true get: handler: todos/get.get events: - http: path: todos/{id} method: get cors: true update: handler: todos/update.update events: - http: path: todos/{id} method: put cors: true delete: handler: todos/delete.delete events: - http: path: todos/{id} method: delete cors: true schema: handler: todos/schema.schema ================================================ FILE: aws-python-rest-api-with-faunadb/todos/__init__.py ================================================ import json import logging import os from faunadb.client import FaunaClient from faunadb.objects import Ref from faunadb import query client = FaunaClient(secret=os.environ['FAUNADB_SECRET']) TODOS = Ref('classes/todos') ALL_TODOS = query.index('all_todos') ================================================ FILE: aws-python-rest-api-with-faunadb/todos/create.py ================================================ import json import logging from todos.makeresult import make_result from todos import client, TODOS from faunadb import query def create(event, context): data = json.loads(event['body']) if 'text' not in data: logging.error("Validation Failed") raise Exception("Couldn't create the todo item.") data = { 'text': data['text'], 'checked': False, 'createdAt': query.time('now'), 'updatedAt': query.time('now') } # write the todo to the database created = client.query(query.create(TODOS, {'data': data})) # create a response response = { "statusCode": 200, "body": json.dumps(make_result(created)) } return response ================================================ FILE: aws-python-rest-api-with-faunadb/todos/delete.py ================================================ from todos import client, TODOS from faunadb.objects import Ref from faunadb import query def delete(event, context): # delete the todo from the database client.query(query.delete(Ref(TODOS, event['pathParameters']['id']))) # create a response response = { "statusCode": 200 } return response ================================================ FILE: aws-python-rest-api-with-faunadb/todos/get.py ================================================ import json from todos.makeresult import make_result from todos import client, TODOS from faunadb.objects import Ref from faunadb import query def get(event, context): # fetch todo from the database ref = Ref(TODOS, event['pathParameters']['id']) fetched = client.query(query.get(ref)) # create a response response = { "statusCode": 200, "body": json.dumps(make_result(fetched)) } return response ================================================ FILE: aws-python-rest-api-with-faunadb/todos/list.py ================================================ import json from todos.makeresult import make_result from todos import client, ALL_TODOS from faunadb import query def list(event, context): # fetch all todos from the database results = client.query( query.map_expr(lambda ref: query.get(ref), query.paginate(query.match(ALL_TODOS)))) # create a response response = { "statusCode": 200, "body": json.dumps(map(make_result, results['data'])) } return response ================================================ FILE: aws-python-rest-api-with-faunadb/todos/makeresult.py ================================================ import time def _to_epoch(fauna_time): return int(time.mktime(fauna_time.to_datetime().timetuple()) * 1000) def make_result(value): return { 'id': value['ref'].id(), 'text': value['data']['text'], 'checked': value['data']['checked'], 'createdAt': _to_epoch(value['data']['createdAt']), 'updatedAt': _to_epoch(value['data']['updatedAt']) } ================================================ FILE: aws-python-rest-api-with-faunadb/todos/schema.py ================================================ from todos import client, TODOS, ALL_TODOS from faunadb import query def schema(event, context): create_todos = query.create_class({ 'name': 'todos' }) create_all_todos = query.create_index({ 'name': 'all_todos', 'source': TODOS }) client.query(query.if_expr( query.exists(TODOS), query.get(TODOS), create_todos )) client.query(query.if_expr( query.exists(ALL_TODOS), query.get(ALL_TODOS), create_all_todos )) # create a response response = { "statusCode": 200 } return response ================================================ FILE: aws-python-rest-api-with-faunadb/todos/update.py ================================================ import json import logging from todos.makeresult import make_result from todos import client, TODOS from faunadb.objects import Ref from faunadb import query def update(event, context): data = json.loads(event['body']) if 'text' not in data or 'checked' not in data: logging.error("Validation Failed") raise Exception("Couldn't update the todo item.") data = { 'text': data['text'], 'checked': data['checked'], 'updatedAt': query.time('now') } # update the todo in the database ref = Ref(TODOS, event['pathParameters']['id']) updated = client.query(query.update(ref, {'data': data})) # create a response response = { "statusCode": 200, "body": json.dumps(make_result(updated)) } return response ================================================ FILE: aws-python-rest-api-with-pymongo/README.md ================================================ # aws-python-rest-api-with-pymongo ## Create the Mongo Atlas backend 1. Follow `Part 1: Cluster Creation` of [this artice](https://medium.com/swlh/creating-a-mongodb-cluster-and-inserting-a-document-with-python-ac90cc9d979c) to create a cluster on Mongo Atlas' Free Tier. ## Deploy the Serverless API to AWS 1. Install Serverless ``` npm install -g serverless ``` 2. Install `serverless-python-requirements` ``` npm i --save serverless-python-requirements ``` 3. Define necessary environment variables Append this to your ~/.bash_profile ``` export MONGO_DB_USER= export MONGO_DB_PASS= export MONGO_DB_NAME=SampleDatabase export MONGO_COLLECTION_NAME=SampleCollection export MONGO_DB_URL= ``` 4. Deploy the API ``` sls deploy ``` Your results should look something like this: ``` Serverless: Stack update finished... Service Information service: serverless-pymongo-item-api stage: dev region: us-east-1 stack: serverless-pymongo-item-api-dev resources: 28 api keys: None endpoints: POST - https://0xfyi15qci.execute-api.us-east-1.amazonaws.com/dev/item GET - https://0xfyi15qci.execute-api.us-east-1.amazonaws.com/dev/item GET - https://0xfyi15qci.execute-api.us-east-1.amazonaws.com/dev/item/{id} DELETE - https://0xfyi15qci.execute-api.us-east-1.amazonaws.com/dev/item/{id} functions: create: serverless-pymongo-item-api-dev-create list: serverless-pymongo-item-api-dev-list get: serverless-pymongo-item-api-dev-get delete: serverless-pymongo-item-api-dev-delete layers: None Serverless: Removing old service artifacts from S3... Serverless: Run the "serverless" command to setup monitoring, troubleshooting and testing. ``` ## Test the API by Creating and Querying items Substitute your endpoints into these curl commands to test the Create, Read, and Delete operations ### CREATE ``` curl --request POST \ --url https://0xfyi15qci.execute-api.us-east-1.amazonaws.com/dev/item \ --header 'content-type: application/json' \ --data '{ "attribute_1": "Pet", "attribute_2": "Rock" }' ``` #### Expected Response 204 status ``` { "_id": "c6f03ca0-f792-11e9-9534-260a4b91bfe9", "data": { "attribute_1": "Pet", "attribute_2": "Rock" } } ``` ### GET ``` curl --request GET \ --url https://0xfyi15qci.execute-api.us-east-1.amazonaws.com/dev/item/c6f03ca0-f792-11e9-9534-260a4b91bfe9 \ --header 'content-type: application/json' ``` #### Expected Response 200 status ``` { "_id": "c6f03ca0-f792-11e9-9534-260a4b91bfe9", "data": { "attribute_1": "Pet", "attribute_2": "Rock" } } ``` ### LIST ``` curl --request GET \ --url https://0xfyi15qci.execute-api.us-east-1.amazonaws.com/dev/item \ --header 'content-type: application/json' ``` ### Expected Response 200 status ``` { "response_items": [ { "_id": "c6f03ca0-f792-11e9-9534-260a4b91bfe9", "data": { "attribute_1": "Pet", "attribute_2": "Rock" } }, { "_id": "717c5f36-f799-11e9-a921-1e0e685be73c", "data": { "attribute_1": "Pete", "attribute_2": "Rock" } } ], "filter": null } ``` ## Delete ``` curl --request DELETE \ --url https://0xfyi15qci.execute-api.us-east-1.amazonaws.com/dev/item/c6f03ca0-f792-11e9-9534-260a4b91bfe9 \ --header 'content-type: application/json' ``` ### Expected Response 204 status ================================================ FILE: aws-python-rest-api-with-pymongo/item/__init__.py ================================================ ================================================ FILE: aws-python-rest-api-with-pymongo/item/create.py ================================================ import json import os import uuid import pymongo # Fetch mongo env vars usr = os.environ['MONGO_DB_USER'] pwd = os.environ['MONGO_DB_PASS'] mongo_db_name = os.environ['MONGO_DB_NAME'] mongo_collection_name = os.environ['MONGO_COLLECTION_NAME'] url = os.environ['MONGO_DB_URL'] # Connection String client = pymongo.MongoClient("mongodb+srv://" + usr + ":" + pwd + "@" + url + "/test?retryWrites=true&w=majority") db = client[mongo_db_name] collection = db[mongo_collection_name] def create(event, context): # get request body data = json.loads(event['body']) # create item to insert item = { '_id': str(uuid.uuid1()), 'data': data, } # write item to database collection.insert_one(item) # create response response = { "statusCode": 200, "body": json.dumps(item) } # return response return response ================================================ FILE: aws-python-rest-api-with-pymongo/item/delete.py ================================================ import os import pymongo # Fetch mongo env vars usr = os.environ['MONGO_DB_USER'] pwd = os.environ['MONGO_DB_PASS'] mongo_db_name = os.environ['MONGO_DB_NAME'] mongo_collection_name = os.environ['MONGO_COLLECTION_NAME'] url = os.environ['MONGO_DB_URL'] # Connection String client = pymongo.MongoClient("mongodb+srv://" + usr + ":" + pwd + "@" + url + "/test?retryWrites=true&w=majority") db = client[mongo_db_name] collection = db[mongo_collection_name] def delete(event, context): # get item_id to delete from path parameter item_id = event['pathParameters']['id'] # delete item from the database del_resp = collection.delete_one({"_id": item_id}) # if no item return 404 if del_resp.deleted_count == 0: response = { "statusCode": 404, } return response # create a response response = { "statusCode": 204, } return response ================================================ FILE: aws-python-rest-api-with-pymongo/item/get.py ================================================ import json import os import pymongo # Fetch mongo env vars usr = os.environ['MONGO_DB_USER'] pwd = os.environ['MONGO_DB_PASS'] mongo_db_name = os.environ['MONGO_DB_NAME'] mongo_collection_name = os.environ['MONGO_COLLECTION_NAME'] url = os.environ['MONGO_DB_URL'] # Connection String client = pymongo.MongoClient("mongodb+srv://" + usr + ":" + pwd + "@" + url + "/test?retryWrites=true&w=majority") db = client[mongo_db_name] collection = db[mongo_collection_name] def get(event, context): # get item_id to delete from path parameter item_id = event['pathParameters']['id'] # delete item from the database item = collection.find_one({"_id": item_id}) # create a response response = { "statusCode": 200, "body": json.dumps(item) } # return response return response ================================================ FILE: aws-python-rest-api-with-pymongo/item/list.py ================================================ import json import os import pymongo # Fetch mongo env vars usr = os.environ['MONGO_DB_USER'] pwd = os.environ['MONGO_DB_PASS'] mongo_db_name = os.environ['MONGO_DB_NAME'] mongo_collection_name = os.environ['MONGO_COLLECTION_NAME'] url = os.environ['MONGO_DB_URL'] # Connection String client = pymongo.MongoClient("mongodb+srv://" + usr + ":" + pwd + "@" + url + "/test?retryWrites=true&w=majority") db = client[mongo_db_name] collection = db[mongo_collection_name] def list(event, context): # create response body object response_body = {} # create array for reponse items response_body['response_items'] = [] # return path parameters with filter key response_body['filter'] = event['multiValueQueryStringParameters'] # build query with any path parameters query = {} if event['multiValueQueryStringParameters'] is not None: for parameter in event['multiValueQueryStringParameters']: query[parameter] = event['multiValueQueryStringParameters'][parameter][0] # create list of items cursor = collection.find(query) for document in cursor: response_body['response_items'].append(document) # create response response = { "statusCode": 200, "body": json.dumps(response_body) } # return response return response ================================================ FILE: aws-python-rest-api-with-pymongo/package.json ================================================ { "name": "aws-python-rest-api-with-pymongo", "version": "1.0.0", "description": "Serverless pymongo example", "author": "", "license": "MIT", "dependencies": { "serverless-python-requirements": "^5.0.0" } } ================================================ FILE: aws-python-rest-api-with-pymongo/requirements.txt ================================================ pymongo dnspython ================================================ FILE: aws-python-rest-api-with-pymongo/serverless.yml ================================================ service: serverless-pymongo-item-api frameworkVersion: ">=2.24.0" plugins: - serverless-python-requirements provider: name: aws runtime: python3.7 environment: MONGO_DB_USER: ${env:MONGO_DB_USER} MONGO_DB_PASS: ${env:MONGO_DB_PASS} MONGO_DB_NAME: ${env:MONGO_DB_NAME} MONGO_DB_URL: ${env:MONGO_DB_URL} MONGO_COLLECTION_NAME: ${env:MONGO_COLLECTION_NAME} iam: role: managedPolicies: - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" functions: create: handler: item/create.create events: - http: path: item method: post cors: true list: handler: item/list.list events: - http: path: item method: get cors: true get: handler: item/get.get events: - http: path: item/{id} method: get cors: true delete: handler: item/delete.delete events: - http: path: item/{id} method: delete cors: true ================================================ FILE: aws-python-rest-api-with-pynamodb/.gitignore ================================================ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ # Serverless .serverless/ .requirements ================================================ FILE: aws-python-rest-api-with-pynamodb/README.md ================================================ # Serverless REST API This example demonstrates how to setup a [RESTful Web Services](https://en.wikipedia.org/wiki/Representational_state_transfer#Applied_to_web_services) allowing you to create, list, get, update and delete Todos. DynamoDB is used to store the data. This is just an example and of course you could use any data storage as a backend. ## Structure This service has a separate directory for all the todo operations. For each operation exactly one file exists e.g. `todos/delete.py`. In each of these files there is exactly one function defined. The idea behind the `todos` directory is that in case you want to create a service containing multiple resources e.g. users, notes, comments you could do so in the same service. While this is certainly possible you might consider creating a separate service for each resource. It depends on the use-case and your preference. ## Use-cases - API for a Web Application - API for a Mobile Application ## Setup ```bash npm install ``` ## Deploy In order to deploy the endpoint simply run ```bash serverless deploy ``` The expected result should be similar to: ```bash Serverless: Packaging service… Serverless: Uploading CloudFormation file to S3… Serverless: Uploading service .zip file to S3… Serverless: Updating Stack… Serverless: Checking Stack update progress… Serverless: Stack update finished… Service Information service: serverless-rest-api-with-pynamodb stage: dev region: us-east-1 api keys: None endpoints: POST - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/dev/todos GET - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/dev/todos GET - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/dev/todos/{id} PUT - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/dev/todos/{id} DELETE - https://45wf34z5yf.execute-api.us-east-1.amazonaws.com/dev/todos/{id} functions: serverless-rest-api-with-pynamodb-dev-update: arn:aws:lambda:us-east-1:488110005556:function:serverless-rest-api-with-pynamodb-dev-update serverless-rest-api-with-pynamodb-dev-get: arn:aws:lambda:us-east-1:488110005556:function:serverless-rest-api-with-pynamodb-dev-get serverless-rest-api-with-pynamodb-dev-list: arn:aws:lambda:us-east-1:488110005556:function:serverless-rest-api-with-pynamodb-dev-list serverless-rest-api-with-pynamodb-dev-create: arn:aws:lambda:us-east-1:488110005556:function:serverless-rest-api-with-pynamodb-dev-create serverless-rest-api-with-pynamodb-dev-delete: arn:aws:lambda:us-east-1:488110005556:function:serverless-rest-api-with-pynamodb-dev-delete ``` ## Usage You can create, retrieve, update, or delete todos with the following commands: ### Create a Todo ```bash curl -X POST https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/todos --data '{ "text": "Learn Serverless" }' ``` No output ### List all Todos ```bash curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/todos ``` Example output: ```bash [{"text":"Deploy my first service","id":"ac90feaa11e6-9ede-afdfa051af86","checked":true,"updatedAt":1479139961304},{"text":"Learn Serverless","id":"206793aa11e6-9ede-afdfa051af86","createdAt":1479139943241,"checked":false,"updatedAt":1479139943241}]% ``` ### Get one Todo ```bash # Replace the part with a real id from your todos table curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/todos/ ``` Example Result: ```bash {"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":false,"updatedAt":1479138570824}% ``` ### Update a Todo ```bash # Replace the part with a real id from your todos table curl -X PUT https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/todos/ --data '{ "text": "Learn Serverless", "checked": true }' ``` Example Result: ```bash {"text":"Learn Serverless","id":"ee6490d0-aa11e6-9ede-afdfa051af86","createdAt":1479138570824,"checked":true,"updatedAt":1479138570824}% ``` ### Delete a Todo ```bash # Replace the part with a real id from your todos table curl -X DELETE https://XXXXXXX.execute-api.us-east-1.amazonaws.com/dev/todos/ ``` No output ## Scaling ### AWS Lambda By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 100. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit). ### DynamoDB When you create a table, you specify how much provisioned throughput capacity you want to reserve for reads and writes. DynamoDB will reserve the necessary resources to meet your throughput needs while ensuring consistent, low-latency performance. You can change the provisioned throughput and increasing or decreasing capacity as needed. This is can be done via settings in the `serverless.yml`. ```yaml ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 ``` In case you expect a lot of traffic fluctuation we recommend to checkout this guide on how to auto scale DynamoDB [https://aws.amazon.com/blogs/aws/auto-scale-dynamodb-with-dynamic-dynamodb/](https://aws.amazon.com/blogs/aws/auto-scale-dynamodb-with-dynamic-dynamodb/) ================================================ FILE: aws-python-rest-api-with-pynamodb/package.json ================================================ { "name": "aws-rest-with-pynamodb", "version": "1.0.0", "description": "Serverless CRUD service exposing a REST HTTP interface", "author": "", "license": "MIT", "dependencies": { "serverless-python-requirements": "^2.4.1" } } ================================================ FILE: aws-python-rest-api-with-pynamodb/requirements.txt ================================================ pynamodb==4.3.1 boto3 #no-deploy botocore #no-deploy ================================================ FILE: aws-python-rest-api-with-pynamodb/serverless.yml ================================================ service: serverless-rest-api-with-pynamodb frameworkVersion: ">=2.24.0" plugins: - serverless-python-requirements package: exclude: - node_modules/** - .idea/** - .requirements/** - env/** - README.md - package.json - package-lock.json - requirements.txt provider: name: aws runtime: python2.7 region: eu-central-1 environment: DYNAMODB_TABLE: ${self:service}-${opt:stage, self:provider.stage} iam: role: statements: - Effect: Allow Action: - dynamodb:Query - dynamodb:Scan - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem - dynamodb:DescribeTable Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}" functions: create: handler: todos/create.create events: - http: path: todos method: post cors: true list: handler: todos/list.todo_list events: - http: path: todos method: get cors: true get: handler: todos/get.get events: - http: path: todos/{todo_id} method: get cors: true integration: lambda request: paths: todo_id: true update: handler: todos/update.update events: - http: path: todos/{todo_id} method: put cors: true integration: lambda request: paths: todo_id: true delete: handler: todos/delete.delete events: - http: path: todos/{todo_id} method: delete cors: true integration: lambda request: paths: todo_id: true resources: Resources: TodosDynamoDbTable: Type: 'AWS::DynamoDB::Table' DeletionPolicy: Retain Properties: AttributeDefinitions: - AttributeName: todo_id AttributeType: S KeySchema: - AttributeName: todo_id KeyType: HASH ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 TableName: ${self:provider.environment.DYNAMODB_TABLE} ================================================ FILE: aws-python-rest-api-with-pynamodb/todos/__init__.py ================================================ ================================================ FILE: aws-python-rest-api-with-pynamodb/todos/create.py ================================================ import json import logging import uuid from todos.todo_model import TodoModel def create(event, context): data = json.loads(event['body']) if 'text' not in data: logging.error('Validation Failed') return {'statusCode': 422, 'body': json.dumps({'error_message': 'Couldn\'t create the todo item.'})} if not data['text']: logging.error('Validation Failed - text was empty. %s', data) return {'statusCode': 422, 'body': json.dumps({'error_message': 'Couldn\'t create the todo item. As text was empty.'})} a_todo = TodoModel(todo_id=str(uuid.uuid1()), text=data['text'], checked=False) # write the todo to the database a_todo.save() # create a response return {'statusCode': 201, 'body': json.dumps(dict(a_todo))} ================================================ FILE: aws-python-rest-api-with-pynamodb/todos/delete.py ================================================ import json from pynamodb.exceptions import DoesNotExist, DeleteError from todos.todo_model import TodoModel def delete(event, context): try: found_todo = TodoModel.get(hash_key=event['path']['todo_id']) except DoesNotExist: return {'statusCode': 404, 'body': json.dumps({'error_message': 'TODO was not found'})} try: found_todo.delete() except DeleteError: return {'statusCode': 400, 'body': json.dumps({'error_message': 'Unable to delete the TODO'})} # create a response return {'statusCode': 204} ================================================ FILE: aws-python-rest-api-with-pynamodb/todos/get.py ================================================ import json from pynamodb.exceptions import DoesNotExist from todos.todo_model import TodoModel def get(event, context): try: found_todo = TodoModel.get(hash_key=event['path']['todo_id']) except DoesNotExist: return {'statusCode': 404, 'body': json.dumps({'error_message': 'TODO was not found'})} # create a response return {'statusCode': 200, 'body': json.dumps(dict(found_todo))} ================================================ FILE: aws-python-rest-api-with-pynamodb/todos/list.py ================================================ import json from todos.todo_model import TodoModel def todo_list(event, context): # fetch all todos from the database results = TodoModel.scan() # create a response return {'statusCode': 200, 'body': json.dumps({'items': [dict(result) for result in results]})} ================================================ FILE: aws-python-rest-api-with-pynamodb/todos/todo_model.py ================================================ import os from datetime import datetime from pynamodb.attributes import UnicodeAttribute, BooleanAttribute, UTCDateTimeAttribute from pynamodb.models import Model class TodoModel(Model): class Meta: table_name = os.environ['DYNAMODB_TABLE'] if 'ENV' in os.environ: host = 'http://localhost:8000' else: region = 'eu-central-1' host = 'https://dynamodb.eu-central-1.amazonaws.com' todo_id = UnicodeAttribute(hash_key=True, null=False) text = UnicodeAttribute(null=False) checked = BooleanAttribute(null=False) createdAt = UTCDateTimeAttribute(null=False, default=datetime.now()) updatedAt = UTCDateTimeAttribute(null=False) def save(self, conditional_operator=None, **expected_values): self.updatedAt = datetime.now() super(TodoModel, self).save() def __iter__(self): for name, attr in self._get_attributes().items(): yield name, attr.serialize(getattr(self, name)) ================================================ FILE: aws-python-rest-api-with-pynamodb/todos/update.py ================================================ import json import logging from pynamodb.exceptions import DoesNotExist from todos.todo_model import TodoModel def update(event, context): # TODO: Figure out why this is behaving differently to the other endpoints # data = json.loads(event['body']) data = event['body'] if 'text' not in data and 'checked' not in data: logging.error('Validation Failed %s', data) return {'statusCode': 422, 'body': json.dumps({'error_message': 'Couldn\'t update the todo item.'})} try: found_todo = TodoModel.get(hash_key=event['path']['todo_id']) except DoesNotExist: return {'statusCode': 404, 'body': json.dumps({'error_message': 'TODO was not found'})} todo_changed = False if 'text' in data and data['text'] != found_todo.text: found_todo.text = data['text'] todo_changed = True if 'checked' in data and data['checked'] != found_todo.checked: found_todo.checked = data['checked'] todo_changed = True if todo_changed: found_todo.save() else: logging.info('Nothing changed did not update') # create a response return {'statusCode': 200, 'body': json.dumps(dict(found_todo))} ================================================ FILE: aws-python-scheduled-cron/.gitignore ================================================ .serverless *.pyc *.pyo ================================================ FILE: aws-python-scheduled-cron/README.md ================================================ # Serverless Framework Python Scheduled Cron on AWS This template demonstrates how to develop and deploy a simple cron-like service running on AWS Lambda using the Serverless Framework. Detailed information about cron expressions in available in official [AWS docs](https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html#CronExpressions). ## Usage ### Deployment This example is made to work with the Serverless Framework dashboard, which includes advanced features such as CI/CD, monitoring, metrics, etc. In order to deploy with dashboard, you need to first login with: ``` serverless login ``` and then perform deployment with: ``` serverless deploy ``` After running deploy, you should see output similar to: ``` Deploying "aws-python-scheduled-cron" to stage "dev" (us-east-1) ✔ Service deployed to stack aws-python-scheduled-cron-dev (146s) functions: rateHandler: aws-python-scheduled-cron-dev-rateHandler (2.2 kB) ``` There is no additional step required. Your defined schedules becomes active right away after deployment. ### Local invocation In order to test out your functions locally, you can invoke them with the following command: ``` serverless invoke local --function rateHandler ``` After invocation, you should see output similar to: ``` INFO:handler:Your cron function ran at 15:02:43.203145 ``` ### Bundling dependencies In case you would like to include 3rd party dependencies, you will need to use a plugin called `serverless-python-requirements`. You can set it up by running the following command: ``` serverless plugin install -n serverless-python-requirements ``` Running the above will automatically add `serverless-python-requirements` to `plugins` section in your `serverless.yml` file and add it as a `devDependency` to `package.json` file. The `package.json` file will be automatically created if it doesn't exist beforehand. Now you will be able to add your dependencies to `requirements.txt` file (`Pipfile` and `pyproject.toml` is also supported but requires additional configuration) and they will be automatically injected to Lambda package during build process. For more details about the plugin's configuration, please refer to [official documentation](https://github.com/UnitedIncome/serverless-python-requirements). ================================================ FILE: aws-python-scheduled-cron/handler.py ================================================ import datetime import logging logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) def run(event, context): current_time = datetime.datetime.now().time() logger.info("Your cron function ran at " + str(current_time)) ================================================ FILE: aws-python-scheduled-cron/serverless.yml ================================================ service: aws-python-scheduled-cron frameworkVersion: "4" provider: name: aws runtime: python3.12 functions: rateHandler: handler: handler.run events: - schedule: rate(1 minute) ================================================ FILE: aws-python-simple-http-endpoint/.gitignore ================================================ .serverless *.pyc *.pyo ================================================ FILE: aws-python-simple-http-endpoint/README.md ================================================ # Simple HTTP Endpoint Example This example demonstrates how to setup a simple HTTP GET endpoint. Once you fetch it, it will reply with the current time. While the internal function is name `currentTime` the HTTP endpoint is exposed as `time`. ## Use Cases - Wrapping an existing internal or external endpoint/service ## Deploy ```bash serverless deploy ``` The expected result should be similar to: ```bash Serverless: Packaging service... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading service .zip file to S3 (758 B)... Serverless: Updating Stack... Serverless: Checking Stack update progress... .......... Serverless: Stack update finished... Service Information service: aws-python-simple-http-endpoint stage: dev region: us-east-1 api keys: None endpoints: GET - https://f7r5srabr3.execute-api.us-east-1.amazonaws.com/time functions: currentTime: aws-python-simple-http-endpoint-dev-currentTime ``` ## Usage You can now invoke the Lambda directly and even see the resulting log via ```bash serverless invoke --function currentTime --log ``` The expected result should be similar to: ```bash { "body": "{\"message\": \"Hello, the current time is 15:40:19.009371\"}", "statusCode": 200 } -------------------------------------------------------------------- START RequestId: a26699d3-b3ee-11e6-98f33f952e8294 Version: $LATEST END RequestId: a26699d3-b3ee-11e6-98f33f952e8294 REPORT RequestId: a26699d3-b3ee-11e6-98f33f952e8294 Duration: 0.23 ms Billed Duration: 100 ms Memory Size: 1024 MB Max Memory Used: 15 MB ``` Finally you can send an HTTP request directly to the endpoint using a tool like curl ```bash curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/time ``` The expected result should be similar to: ```bash {"message": "Hello, the current time is 15:38:53.668501"}% ``` ## Scaling By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 1000. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit). ================================================ FILE: aws-python-simple-http-endpoint/handler.py ================================================ import json import datetime def endpoint(event, context): current_time = datetime.datetime.now().time() body = { "message": "Hello, the current time is " + str(current_time) } response = { "statusCode": 200, "body": json.dumps(body) } return response ================================================ FILE: aws-python-simple-http-endpoint/package.json ================================================ { "name": "aws-simple-http-endpoint", "version": "1.0.0", "description": "Example demonstrates how to setup a simple HTTP GET endpoint with python", "author": "", "license": "MIT" } ================================================ FILE: aws-python-simple-http-endpoint/serverless.yml ================================================ service: aws-python-simple-http-endpoint frameworkVersion: '2' provider: name: aws runtime: python3.8 functions: currentTime: handler: handler.endpoint events: - httpApi: path: /time method: get ================================================ FILE: aws-python-sqs-worker/.gitignore ================================================ # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # Serverless directories .serverless ================================================ FILE: aws-python-sqs-worker/README.md ================================================ # Serverless Framework Python SQS Producer-Consumer on AWS This template demonstrates how to develop and deploy a simple SQS-based producer-consumer service running on AWS Lambda using the Serverless Framework and the [Lift](https://github.com/getlift/lift) plugin. It allows to accept messages, for which computation might be time or resource intensive, and offload their processing to an asynchronous background process for a faster and more resilient system. ## Anatomy of the template This template defines one function `producer` and one Lift construct - `jobs`. The producer function is triggered by `http` event type, accepts JSON payloads and sends it to a SQS queue for asynchronous processing. The SQS queue is created by the `jobs` queue construct of the Lift plugin. The queue is set up with a "dead-letter queue" (to receive failed messages) and a `worker` Lambda function that processes the SQS messages. To learn more: - about `http` event configuration options, refer to [http event docs](https://www.serverless.com/framework/docs/providers/aws/events/apigateway/) - about the `queue` construct, refer to [the `queue` documentation in Lift](https://github.com/getlift/lift/blob/master/docs/queue.md) - about the Lift plugin in general, refer to [the Lift project](https://github.com/getlift/lift) - about SQS processing with AWS Lambda, please refer to the official [AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html) ### Deployment Install dependencies with: ``` npm install ``` Then deploy: ``` serverless deploy ``` After running deploy, you should see output similar to: ```bash Serverless: Packaging service... Serverless: Excluding development dependencies... Serverless: Creating Stack... Serverless: Checking Stack create progress... ........ Serverless: Stack create finished... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading artifacts... Serverless: Uploading service aws-python-sqs-worker.zip file to S3 (21.44 MB)... Serverless: Validating template... Serverless: Updating Stack... Serverless: Checking Stack update progress... ................................................ Serverless: Stack update finished... Service Information service: aws-python-sqs-worker stage: dev region: us-east-1 stack: aws-python-sqs-worker-dev resources: 17 api keys: None endpoints: POST - https://xxxx.execute-api.us-east-1.amazonaws.com/produce functions: producer: aws-python-sqs-worker-dev-producer jobsWorker: aws-python-sqs-worker-dev-jobsWorker layers: None jobs: queueUrl: https://sqs.us-east-1.amazonaws.com/xxxx/aws-python-sqs-worker-dev-jobs ``` _Note_: In current form, after deployment, your API is public and can be invoked by anyone. For production deployments, you might want to configure an authorizer. For details on how to do that, refer to [http event docs](https://www.serverless.com/framework/docs/providers/aws/events/apigateway/). ### Invocation After successful deployment, you can now call the created API endpoint with `POST` request to invoke `producer` function: ```bash curl --request POST 'https://xxxxxx.execute-api.us-east-1.amazonaws.com/produce' --header 'Content-Type: application/json' --data-raw '{"name": "John"}' ``` In response, you should see output similar to: ```bash {"message": "Message accepted!"} ``` ### Bundling dependencies In case you would like to include 3rd party dependencies, you will need to use a plugin called `serverless-python-requirements`. You can set it up by running the following command: ```bash serverless plugin install -n serverless-python-requirements ``` Running the above will automatically add `serverless-python-requirements` to `plugins` section in your `serverless.yml` file and add it as a `devDependency` to `package.json` file. The `package.json` file will be automatically created if it doesn't exist beforehand. Now you will be able to add your dependencies to `requirements.txt` file (`Pipfile` and `pyproject.toml` is also supported but requires additional configuration) and they will be automatically injected to Lambda package during build process. For more details about the plugin's configuration, please refer to [official documentation](https://github.com/UnitedIncome/serverless-python-requirements). ================================================ FILE: aws-python-sqs-worker/handler.py ================================================ import json import logging import os import boto3 logger = logging.getLogger() logger.setLevel(logging.DEBUG) QUEUE_URL = os.getenv('QUEUE_URL') SQS = boto3.client('sqs') def producer(event, context): status_code = 200 message = '' if not event.get('body'): return {'statusCode': 400, 'body': json.dumps({'message': 'No body was found'})} try: message_attrs = { 'AttributeName': {'StringValue': 'AttributeValue', 'DataType': 'String'} } SQS.send_message( QueueUrl=QUEUE_URL, MessageBody=event['body'], MessageAttributes=message_attrs, ) message = 'Message accepted!' except Exception as e: logger.exception('Sending message to SQS queue failed!') message = str(e) status_code = 500 return {'statusCode': status_code, 'body': json.dumps({'message': message})} def consumer(event, context): for record in event['Records']: logger.info(f'Message body: {record["body"]}') logger.info( f'Message attribute: {record["messageAttributes"]["AttributeName"]["stringValue"]}' ) ================================================ FILE: aws-python-sqs-worker/package.json ================================================ { "name": "aws-python-sqs-worker", "version": "1.0.0", "description": "Serverless Framework Python SQS Producer-Consumer on AWS", "author": "", "license": "MIT", "devDependencies": { "serverless-lift": "^1.1.2" } } ================================================ FILE: aws-python-sqs-worker/serverless.template.yml ================================================ name: aws-python-sqs-worker org: serverlessinc description: Deploys a Python SQS Producer-Consumer service with traditional Serverless Framework keywords: aws, serverless, faas, lambda, python, sqs repo: https://github.com/serverless/examples/aws-python-sqs-worker license: MIT ================================================ FILE: aws-python-sqs-worker/serverless.yml ================================================ service: aws-python-sqs-worker frameworkVersion: '4' provider: name: aws runtime: python3.12 constructs: jobs: type: queue worker: handler: handler.consumer functions: producer: handler: handler.producer events: - httpApi: method: post path: /produce environment: QUEUE_URL: ${construct:jobs.queueUrl} plugins: - serverless-lift package: patterns: - '!node_modules/**' ================================================ FILE: aws-python-telegram-bot/.gitignore ================================================ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # dotenv .env # virtualenv .venv venv/ ENV/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ hackernews/db.sqlite3 # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # 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 # nyc test coverage .nyc_output # Grunt intermediate storage (http://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/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env # Token file serverless.env.yml ================================================ FILE: aws-python-telegram-bot/LICENSE ================================================ MIT License Copyright (c) 2017 Jonatas Baldin 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. ================================================ FILE: aws-python-telegram-bot/README.md ================================================ # Serverless Telegram Bot This example demonstrates how to setup an echo Telegram Bot using the Serverless Framework ⚡🤖 ## Usage ### What do I need? - A AWS key configured locally, see [here](https://serverless.com/framework/docs/providers/aws/guide/credentials/). - NodeJS. I tested with v8.9.0. - A Telegram account. ### Installing ``` # Install the Serverless Framework $ npm install serverless -g # Install the necessary plugins $ npm install # Get a bot from Telegram, sending this message to @BotFather $ /newbot # Put the token received into a file called serverless.env.yml, like this $ cat serverless.env.yml TELEGRAM_TOKEN: # Deploy it! $ serverless deploy # With the URL returned in the output, configure the Webhook $ curl -X POST https://.amazonaws.com/dev/set_webhook ``` Now, just start a conversation with the bot :) ================================================ FILE: aws-python-telegram-bot/handler.py ================================================ import json import telegram import os import logging # Logging is cool! logger = logging.getLogger() if logger.handlers: for handler in logger.handlers: logger.removeHandler(handler) logging.basicConfig(level=logging.INFO) OK_RESPONSE = { 'statusCode': 200, 'headers': {'Content-Type': 'application/json'}, 'body': json.dumps('ok') } ERROR_RESPONSE = { 'statusCode': 400, 'body': json.dumps('Oops, something went wrong!') } def configure_telegram(): """ Configures the bot with a Telegram Token. Returns a bot instance. """ TELEGRAM_TOKEN = os.environ.get('TELEGRAM_TOKEN') if not TELEGRAM_TOKEN: logger.error('The TELEGRAM_TOKEN must be set') raise NotImplementedError return telegram.Bot(TELEGRAM_TOKEN) def webhook(event, context): """ Runs the Telegram webhook. """ bot = configure_telegram() logger.info('Event: {}'.format(event)) if event.get('httpMethod') == 'POST' and event.get('body'): logger.info('Message received') update = telegram.Update.de_json(json.loads(event.get('body')), bot) chat_id = update.message.chat.id text = update.message.text if text == '/start': text = """Hello, human! I am an echo bot, built with Python and the Serverless Framework. You can take a look at my source code here: https://github.com/jonatasbaldin/serverless-telegram-bot. If you have any issues, please drop a tweet to my creator: https://twitter.com/jonatsbaldin. Happy botting!""" bot.sendMessage(chat_id=chat_id, text=text) logger.info('Message sent') return OK_RESPONSE return ERROR_RESPONSE def set_webhook(event, context): """ Sets the Telegram bot webhook. """ logger.info('Event: {}'.format(event)) bot = configure_telegram() url = 'https://{}/{}/'.format( event.get('headers').get('Host'), event.get('requestContext').get('stage'), ) webhook = bot.set_webhook(url) if webhook: return OK_RESPONSE return ERROR_RESPONSE ================================================ FILE: aws-python-telegram-bot/package.json ================================================ { "name": "serverless-telegram-bot", "description": "This example demonstrates how to setup an echo Telegram Bot using the Serverless Framework ⚡🤖 ", "version": "0.1.0", "repository": { "type": "git", "url": "git+https://github.com/jonatasbaldin/serverless-telegram-bot.git" }, "keywords": [ "python", "aws", "lambda", "serverless" ], "author": "jonatasbaldin", "license": "MIT", "dependencies": { "serverless-python-requirements": "^3.0.9" }, "bugs": { "url": "https://github.com/jonatasbaldin/serverless-telegram-bot/issues" }, "homepage": "https://github.com/jonatasbaldin/serverless-telegram-bot#readme" } ================================================ FILE: aws-python-telegram-bot/requirements.txt ================================================ python-telegram-bot==8.1.1 ================================================ FILE: aws-python-telegram-bot/serverless.yml ================================================ service: serverless-telegram-bot provider: name: aws runtime: python3.6 profile: ckl environment: TELEGRAM_TOKEN: ${file(./serverless.env.yml):TELEGRAM_TOKEN, ''} functions: webhook: handler: handler.webhook events: - http: POST / set_webhook: handler: handler.set_webhook events: - http: POST /set_webhook plugins: - serverless-python-requirements ================================================ FILE: aws-ruby-cron-with-dynamodb/Gemfile ================================================ source 'https://rubygems.org' gem 'aws-sdk-dynamodb' gem 'faker' ================================================ FILE: aws-ruby-cron-with-dynamodb/README.md ================================================ # AWS Ruby scheduled cron example backed by DynamoDB This is an example of creating a function that runs as a cron job using the serverless `schedule` event. With the usage of the `AWS Lambda` function, it creates a record to the `DynamoDB` each and every 30 minutes. For more information on the `schedule` event check out the Serverless docs on [schedule](https://serverless.com/framework/docs/providers/aws/events/schedule/). ## Diagram ![diagram](./images/aws-serverless-ruby-diagram.png) ## Setup `npm install` to install all needed packages. ## Deployment In order to deploy the service run: ```bash sls deploy ``` for deploying with a specific `profile` (located in `~/.aws/credentials`) you can simply use the command: ```bash AWS_PROFILE=YOUR_PROFILE_NAME sls deploy ``` for deploying to the specific stage, let's say `staging` do: ```bash sls deploy --stage staging ``` The expected result should be similar to: ```bash Serverless: Packaging service... Serverless: Excluding development dependencies... Serverless: Clearing previous build ruby layer build [ '2.1' ] Serverless: Installing gem using local bundler Serverless: Zipping the gemfiles to aws-ruby-cron-with-dynamodb/.serverless/ruby_layer/gemLayer.zip Serverless: Configuring Layer and GEM_PATH to the functions Serverless: Creating Stack... Serverless: Checking Stack create progress... ........ Serverless: Stack create finished... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading artifacts... Serverless: Uploading service aws-ruby-cron-with-dynamodb.zip file to S3 (1.45 KB)... Serverless: Uploading service gemLayer.zip file to S3 (2.64 MB)... Serverless: Validating template... Serverless: Updating Stack... Serverless: Checking Stack update progress... ........................... Serverless: Stack update finished... Service Information service: aws-ruby-cron-with-dynamodb stage: dev region: us-east-1 stack: aws-ruby-cron-with-dynamodb-dev resources: 10 api keys: None endpoints: None functions: create-meal-order: aws-ruby-cron-with-dynamodb-dev-create-meal-order layers: gem: arn:aws:lambda:us-east-1:862403288926:layer:aws-ruby-cron-with-dynamodb-dev-ruby-bundle:1 ``` ## Configuration Within the `serverless.yml` in functions section ``` functions: create-meal-order: handler: src/handlers/create_meal_order/handler.run events: - schedule: rate(30 minutes) ``` You can setup your own schedule for cron. The default value is setup for 30 minutes. For more information about cron syntax, please check the next section. ## Cron syntax ```pseudo cron(Minutes Hours Day-of-month Month Day-of-week Year) ``` All fields are required and time zone is UTC only. | Field | Values | Wildcards | | ------------- |:--------------:|:-------------:| | Minutes | 0-59 | , - * / | | Hours | 0-23 | , - * / | | Day-of-month | 1-31 | , - * ? / L W | | Month | 1-12 or JAN-DEC| , - * / | | Day-of-week | 1-7 or SUN-SAT | , - * ? / L # | | Year | 192199 | , - * / | Read the [AWS cron expression syntax](http://docs.aws.amazon.com/lambda/latest/dg/tutorial-scheduled-events-schedule-expressions.html) docs for more info on how to setup cron. ## Usage After the deployment, there is no much to do on your end. To see the newly created records based on schedule time, you can either go to the created dynamoDB table within your region: ![dynamodb-results](./images/dynamodb-results.png) *Important*: The DynamoDB table name is a combination of service name and stage. For the `dev` stage it will be: ``` aws-ruby-cron-with-dynamodb-dev ``` or via cloudwatch logs: - from lambda the `create-meal-order` function: ![view-logs-in-cloud-watch](./images/view-logs-in-cloud-watch.png) - directly from CloudWatch logs `create-meal-order` function: ![logs-result](./images/logs-result.png) ## Log retention The log retention is setup for 30 days. To change it simply change the value of this attribute in `serverless.yml` file: ``` bash logRetentionInDays: 30 ``` ## Structure | Path | Explanation | |----------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `./src` | All code for the project. | | `./src/handlers/create_meal_order` | Lambda function for creating a meal order. | | `./src/common/` | Space for common, reusable pieces of code. | | `./src/common/adapters/dynamo_db_adapter.rb` | Adapter for communication with DynamoDB with the usage of AWS SDK for Ruby. Only used for creating new records. | | `./src/common/services/create_meal_order_service.rb` | TThe service object pattern is widely used within ruby/rails developers. A class that is responsible for doing only one thing. In our case is creating a meal order to the DynamoDB. | ## Serverless plugin For this example, there are two serverless plugins used: | Plugin | Explanation | |-----------------------|------------------------------------------------------------------------------------------------| | [serverless-ruby-layer](https://www.npmjs.com/package/serverless-ruby-layer) | For bundling ruby gems from `Gemfile` and deploys them to the lambda layer. | | [serverless-export-env](https://www.npmjs.com/package/serverless-export-env) | or exporting the environment variables defined in `serverless.yml` into a `.env` file, so we can access these environment variables. For the purpose of this project, mostly for the DynamoDB Table name, region, a profile. | ## Ruby gems | Gem | Explanation | |--------------------|--------------------------------------------------------------------------------------------------------------------------------| | `aws-sdk-dynamodb` | It's a part of the AWS SDK for Ruby. Used for DynamoDB, in the case of this example - the creation of the new record. | | `faker` | For generating fake meal order name. | ## Remove service Please keep in mind, that the service will create a record each and every 30 minutes, you don't want to necessarily make it happened for the example project. To remove the service do: ``` sls remove ``` And the stack will be removed from the AWS. ================================================ FILE: aws-ruby-cron-with-dynamodb/package.json ================================================ { "name": "serverless-ruby-dynamodb-cron", "version": "1.0.0", "description": "This is an example of creating a function that runs as a cron job using the serverless 'schedule' event. With the usage of the AWS Lambda function, it creates a record to the DynamoDB each and every 30 minutes.", "author": "Daniel Aniszkiewicz", "license": "MIT", "dependencies": { "serverless-ruby-layer": "^1.5.0" }, "devDependencies": { "serverless-export-env": "2.0.0" } } ================================================ FILE: aws-ruby-cron-with-dynamodb/serverless.yml ================================================ service: aws-ruby-cron-with-dynamodb frameworkVersion: '2' provider: name: aws region: us-east-1 runtime: ruby2.7 memorySize: 256 timeout: 10 environment: stage: ${sls:stage} region: ${self:provider.region} service: ${self:service} tableName: ${self:custom.tableName} logRetentionInDays: 30 tags: Application: ${self:service} Stage: ${sls:stage} lambdaHashingVersion: 20201221 iam: role: statements: - Effect: Allow Action: - dynamodb:PutItem Resource: - !GetAtt Table.Arn functions: create-meal-order: handler: src/handlers/create_meal_order/handler.run events: - schedule: rate(30 minutes) resources: Resources: Table: Type: AWS::DynamoDB::Table Properties: TableName: ${self:custom.tableName} Tags: - Key: Application Value: ${self:service} - Key: Stage Value: ${sls:stage} BillingMode: PAY_PER_REQUEST AttributeDefinitions: - AttributeName: id AttributeType: S KeySchema: - AttributeName: id KeyType: HASH plugins: - serverless-ruby-layer - serverless-export-env custom: tableName: ${self:service}-${sls:stage} rubyLayer: include_functions: - create-meal-order ================================================ FILE: aws-ruby-cron-with-dynamodb/src/common/adapters/dynamo_db_adapter.rb ================================================ # frozen_string_literal: true require 'aws-sdk-dynamodb' require 'logger' class DynamoDBAdapter def initialize @client = Aws::DynamoDB::Client.new end def save_item(item) @client.put_item(table_item(item)) logger.info("Created dynamoDB item with id=#{item[:id]}") rescue Aws::DynamoDB::Errors::ServiceError => error logger.error(error) end private def logger @logger ||= Logger.new($stdout) end def table_item(item) { table_name: ENV['tableName'], item: item } end end ================================================ FILE: aws-ruby-cron-with-dynamodb/src/common/services/create_meal_order_service.rb ================================================ # frozen_string_literal: true require 'date' require 'securerandom' require 'faker' require_relative '../adapters/dynamo_db_adapter' class CreateMealOrderService MealOrderSchema = Struct.new(:id, :dish_name, :created_at) def call order = build_order save(order) end def save(order) DynamoDBAdapter.new.save_item(order) end private def build_order MealOrderSchema.new(SecureRandom.uuid, Faker::Food.dish, DateTime.now.iso8601).to_h end end ================================================ FILE: aws-ruby-cron-with-dynamodb/src/handlers/create_meal_order/handler.rb ================================================ # frozen_string_literal: true require_relative '../../common/services/create_meal_order_service' def run(event:, context:) CreateMealOrderService.new.call end ================================================ FILE: aws-ruby-line-bot/.gitignore ================================================ .serverless vendor node_modules package-lock.json .bundle/ ================================================ FILE: aws-ruby-line-bot/Gemfile ================================================ source 'https://rubygems.org' gem 'line-bot-api' ================================================ FILE: aws-ruby-line-bot/README.md ================================================ # AWS-ruby-line-echo-bot Follow this [project](https://github.com/serverless/examples/tree/master/aws-python-line-echo-bot), I use my first language(ruby) to build this on serverless, so you can use this project in others case. # Before you start 1. LINE developer account 2. [LINE Messaging API](https://developers.line.biz/en/docs/messaging-api/getting-started/) # Get Started 1. Install serverless via npm ```bash= $ npm install -g serverless ``` 2. Setup your **AWS** ceritficate ```bash= export AWS_ACCESS_KEY_ID= export AWS_SECRET_ACCESS_KEY= ``` 3. Insert you line bot secret & key ```python= config.channel_secret = "YOUR_LINE_CHANNEL_SECRET" config.channel_token = "YOUR_LINE_CHANNEL_TOKEN" ``` 4. Deploy the webhook function ```bash= npm install serverless deploy ``` Now you can test your chatbot, have fun! ![Echo bot](https://i.imgur.com/ekiLRHS.png) # References - [Plugin hook](https://github.com/serverless/serverless/issues/5567#issuecomment-444671106) ================================================ FILE: aws-ruby-line-bot/handler.rb ================================================ require 'line/bot' def webhook(event:, context:) client ||= Line::Bot::Client.new { |config| config.channel_secret = "YOUR_LINE_CHANNEL_SECRET" config.channel_token = "YOUR_LINE_CHANNEL_TOKEN" } event = JSON.parse(event["body"]) reply_token = event["events"][0]["replyToken"] message = { type: 'text', text: event["events"][0]["message"]["text"] } response = client.reply_message(reply_token, message) { statusCode: 200, body: JSON.generate({message: "OK"}) } end ================================================ FILE: aws-ruby-line-bot/package.json ================================================ { "name": "aws-ruby-line-bot", "version": "1.0.1", "description": "Example demonstrates how to setup a simple Line echo bot on AWS", "author": "NiJia", "license": "MIT", "devDependencies": { "serverless-hooks-plugin": "^1.1.0" } } ================================================ FILE: aws-ruby-line-bot/serverless.yml ================================================ service: aws-ruby-line-bot frameworkVersion: ">=2.1.0 <3.0.0" provider: name: aws runtime: ruby2.7 functions: webhook: handler: handler.webhook events: - http: path: webhook method: post plugins: - serverless-hooks-plugin custom: hooks: package:initialize: - bundle install --deployment ================================================ FILE: aws-ruby-simple-http-endpoint/.gitignore ================================================ .serverless vendor ================================================ FILE: aws-ruby-simple-http-endpoint/README.md ================================================ # Simple HTTP Endpoint Example Inspired by the [aws-node-simple-http-endpoint](https://github.com/serverless/examples/tree/master/aws-node-simple-http-endpoint), in Ruby! ## Use Cases - Wrapping an existing internal or external endpoint/service ## Deploy In order to deploy the endpoint, simply run: ```bash sls deploy ``` The expected result should be similar to: ```bash Serverless: Packaging service... Serverless: Excluding development dependencies... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading artifacts... Serverless: Uploading service .zip file to S3 (849 B)... Serverless: Validating template... Serverless: Updating Stack... Serverless: Checking Stack update progress... ............................... Serverless: Stack update finished... Service Information service: serverless-ruby-simple-http-endpoint stage: dev region: us-east-1 stack: serverless-ruby-simple-http-endpoint-dev api keys: None endpoints: GET - https://spmfbzc6ja.execute-api.us-east-1.amazonaws.com/time functions: current_time: serverless-ruby-simple-http-endpoint-dev-current_time layers: None Serverless: Removing old service artifacts from S3... ``` ## Usage Send an HTTP request directly to the endpoint using a tool like curl: ```bash curl https://XXXXXXX.execute-api.us-east-1.amazonaws.com/time ``` ## Scaling By default, AWS Lambda limits the total concurrent executions across all functions within a given region to 1000. The default limit is a safety limit that protects you from costs due to potential runaway or recursive functions during initial development and testing. To increase this limit above the default, follow the steps in [To request a limit increase for concurrent executions](http://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html#increase-concurrent-executions-limit). ================================================ FILE: aws-ruby-simple-http-endpoint/handler.rb ================================================ def endpoint(event:, context:) hash = {date: Time.new} { statusCode: 200, body: JSON.generate(hash) } end ================================================ FILE: aws-ruby-simple-http-endpoint/package.json ================================================ { "name": "aws-ruby-simple-http-endpoint", "version": "1.0.1", "description": "Example demonstrates how to setup a simple HTTP GET endpoint", "author": "", "license": "MIT" } ================================================ FILE: aws-ruby-simple-http-endpoint/serverless.yml ================================================ service: serverless-ruby-simple-http-endpoint frameworkVersion: '2' provider: name: aws runtime: ruby2.7 functions: current_time: handler: handler.endpoint events: - httpApi: path: /time method: get ================================================ FILE: aws-ruby-sinatra-dynamodb-api/.gitignore ================================================ .bundle .dynamodb .serverless node_modules package-lock.json Gemfile.lock ================================================ FILE: aws-ruby-sinatra-dynamodb-api/.ruby-version ================================================ 2.7.2 ================================================ FILE: aws-ruby-sinatra-dynamodb-api/Gemfile ================================================ source 'https://rubygems.org' gem 'sinatra' gem 'sinatra-contrib' gem 'aws-sdk-dynamodb' ================================================ FILE: aws-ruby-sinatra-dynamodb-api/README.md ================================================ # Serverless Framework Ruby Sinatra API service backed by DynamoDB on AWS Inspired and based off of [aws-python-flask-api](https://github.com/serverless/examples/tree/master/aws-python-flask-api). Credit goes to @pgrzesik for the inspiration of this example and writing of this README that was shamelessly copied and updated to accomodated Ruby/Sinatra. ## Anatomy of the template This template configures a single function, `api`, which is responsible for handling all incoming requests thanks to configured `http` events. To learn more about `http` event configuration options, please refer to [http event docs](https://www.serverless.com/framework/docs/providers/aws/events/apigateway/). As the events are configured in a way to accept all incoming requests, `Sinatra` framework is responsible for routing and handling requests internally. The implementation takes advantage of `serverless-rack`, which allows you to wrap Rack applications such as Sinatra apps. To learn more about `serverless-rack`, please refer to corresponding [GitHub repository](https://github.com/logandk/serverless-rack). The template also relies on `serverless-ruby-layer` plugin for packaging dependencies from the `Gemfile`. For more details about `serverless-ruby-layer` configuration, please refer to corresponding [GitHub repository](https://github.com/navarasu/serverless-ruby-layer). Additionally, the template also handles provisioning of a DynamoDB database that is used for storing data about users. The Sinatra application exposes two endpoints, `POST /users` and `GET /user/{userId}`, which allow to create and retrieve users. ## Usage ### Prerequisites Ruby install of `2.7.*` ### Deployment Install dependencies with: ``` npm install ``` and then perform deployment with: ``` serverless deploy ``` After running deploy, you should see output similar to: ```bash Serverless: Packaging Ruby Rack handler... Serverless: Backing up current bundle... Serverless: Packaging gem dependencies... Serverless: Packaging service... Serverless: Excluding development dependencies... Serverless: Restoring backed up bundle... Serverless: Creating Stack... Serverless: Checking Stack create progress... ........ Serverless: Stack create finished... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading artifacts... Serverless: Uploading service aws-ruby-sinatra-dynamodb-api.zip file to S3 (2.68 MB)... Serverless: Validating template... Serverless: Updating Stack... Serverless: Checking Stack update progress... .................................... Serverless: Stack update finished... Service Information service: aws-ruby-sinatra-dynamodb-api stage: dev region: us-east-1 stack: aws-ruby-sinatra-dynamodb-api-dev resources: 13 api keys: None endpoints: ANY - https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/ ANY - https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/{proxy+} functions: api: aws-ruby-sinatra-dynamodb-api-dev-api layers: None ``` _Note_: In current form, after deployment, your API is public and can be invoked by anyone. For production deployments, you might want to configure an authorizer. For details on how to do that, refer to [http event docs](https://www.serverless.com/framework/docs/providers/aws/events/apigateway/). ### Invocation After successful deployment, you can create a new user by calling the corresponding endpoint: ```bash curl --request POST 'https://xxxxxx.execute-api.us-east-1.amazonaws.com/dev/users' --header 'Content-Type: application/json' --data-raw '{"name": "John", "userId": "someUserId"}' ``` Which should result in the following response: ```bash {"userId":"someUserId","name":"John"} ``` You can later retrieve the user by `userId` by calling the following endpoint: ```bash curl https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/users/someUserId ``` Which should result in the following response: ```bash {"userId":"someUserId","name":"John"} ``` If you try to retrieve user that does not exist, you should receive the following response: ```bash {"error":"Could not find user with provided \"userId\""} ``` ### Local development Thanks to capabilities of `serverless-rack`, it is also possible to run your application locally. ```bash bundle install --path vendor/bundle serverless rack serve ``` Additionally, you will need to emulate DynamoDB locally, which can be done by using `serverless-dynamodb-local` plugin. In order to do that, execute the following commands: ```bash serverless plugin install -n serverless-dynamodb-local serverless dynamodb install ``` It will add the plugin to `devDependencies` in `package.json` file as well as to `plugins` section in `serverless.yml`. Additionally, it will also install DynamoDB locally. You should also add the following config to `custom` section in `serverless.yml`: ```yml custom: (...) dynamodb: start: migrate: true stages: - dev ``` We can take advantage of `IS_OFFLINE` environment variable set by `serverless-rack` plugin which will create a local DynamoDB client in `api.rb`: ```ruby client_options = if ENV['IS_OFFLINE'] { region: 'localhost', endpoint: 'http://localhost:8000', credentials: Aws::Credentials.new( 'DEFAULT_ACCESS_KEY', 'DEFAULT_SECRET' ) } else {} end dynamodb_client = Aws::DynamoDB::Client.new(client_options) ``` Now you can start DynamoDB local with the following command: ```bash serverless dynamodb start ``` At this point, you can run your application locally with the following command: ```bash serverless rack serve ``` For additional local development capabilities of `serverless-rack` and `serverless-dynamodb-local` plugins, please refer to corresponding GitHub repositories: - https://github.com/logandk/serverless-rack - https://github.com/99x/serverless-dynamodb-local ================================================ FILE: aws-ruby-sinatra-dynamodb-api/api.rb ================================================ # frozen_string_literal: true require 'sinatra' require 'sinatra/json' require 'aws-sdk-dynamodb' client_options = if ENV['IS_OFFLINE'] { region: 'localhost', endpoint: 'http://localhost:8000', credentials: Aws::Credentials.new( 'DEFAULT_ACCESS_KEY', 'DEFAULT_SECRET' ) } else {} end dynamodb_client = Aws::DynamoDB::Client.new(client_options) get '/users/:user_id' do result = dynamodb_client.get_item( key: { 'userId': params[:user_id] }, table_name: ENV['USERS_TABLE'] ) item = result.item if item json user_id: item['userId'], name: item['name'] if item else json error: "Could not find user with userId: #{params[:user_id]}" end end post '/users' do request_payload = JSON.parse(request.body.read) user_id = request_payload['user_id'] name = request_payload['name'] return json error: "Please provide both 'user_id' and 'name'" unless user_id && name dynamodb_client.put_item( item: { 'userId': user_id, 'name': name }, table_name: ENV['USERS_TABLE'] ) json user_id: user_id, name: name end ================================================ FILE: aws-ruby-sinatra-dynamodb-api/config.ru ================================================ require './api' run Sinatra::Application ================================================ FILE: aws-ruby-sinatra-dynamodb-api/package.json ================================================ { "name": "aws-ruby-sinatra-dynamodb-api", "version": "1.0.0", "description": "Example of a Ruby Sinatra API service backed by DynamoDB with traditional Serverless Framework", "author": "", "devDependencies": { "serverless-rack": "^1.0.6", "serverless-ruby-layer": "^1.4.0" } } ================================================ FILE: aws-ruby-sinatra-dynamodb-api/serverless.yml ================================================ service: aws-ruby-sinatra-dynamodb-api frameworkVersion: "2" custom: tableName: "users-table-${self:provider.stage}" provider: name: aws runtime: ruby2.7 lambdaHashingVersion: "20201221" apiGateway: shouldStartNameWithService: true stage: dev iam: role: statements: - Effect: Allow Action: - dynamodb:Query - dynamodb:Scan - dynamodb:GetItem - dynamodb:PutItem - dynamodb:UpdateItem - dynamodb:DeleteItem Resource: - Fn::GetAtt: [UsersTable, Arn] environment: USERS_TABLE: ${self:custom.tableName} functions: api: handler: rack_adapter.handler events: - http: path: / method: ANY - http: path: /{proxy+} method: ANY plugins: - serverless-rack resources: Resources: UsersTable: Type: AWS::DynamoDB::Table Properties: AttributeDefinitions: - AttributeName: userId AttributeType: S KeySchema: - AttributeName: userId KeyType: HASH ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 TableName: ${self:custom.tableName} ================================================ FILE: aws-ruby-sqs-with-dynamodb/Gemfile ================================================ source 'https://rubygems.org' gem 'aws-sdk-dynamodb' gem 'aws-sdk-sqs' gem 'dry-schema', '~> 1.6', '>= 1.6.2' ================================================ FILE: aws-ruby-sqs-with-dynamodb/README.md ================================================ # Serverless AWS Ruby SQS with DynamoDB example This is an example of the usage of the `SQS` with `DynamoDB`, `API Gateway`, and `AWS Lambda` functions, and `Cloudwatch` for monitoring. The service is for the purpose of creating lottery coupons for people. Each and every coupon consists of the `id`, `first_name`, `last_name` and `coupon_value` as well as of `created_at` timestamp. API Gateway is triggering producing an SQS message, and then the consumer will create a record within the DynamoDB table. The incoming requests are validated with the usage of `dry-schema`. For the purpose of SQS part, the `Lift` [construct](https://github.com/getlift/lift) was used, to allow using `AWS CDK` constructs functionalities. ## Diagram ![diagram](./images/aws-serverless-diagram.png) Regarding Lift queue construct: - It creates an SQS queue with a worker running on AWS Lambda. - A `worker` Lambda function, the purpose here is to process each and every message which is in the queue. - An `SQS DLQ` the purpose here is to store all failed messages. - `Cloudwatch` for alerting via email. Applies when SQS DLQ has failed messages, and the alerting option is enabled (in our case is true). ## Setup `npm install` to install all needed packages. ## Deployment In order to deploy the service run: ```bash sls deploy ``` for deploying with a specific `profile` (located in `~/.aws/credentials`) you can simply use the command: ```bash AWS_PROFILE=YOUR_PROFILE_NAME sls deploy ``` for deploying to the specific stage, let's say `staging` do: ```bash sls deploy --stage staging ``` The expected result should be similar to: ```bash Serverless: Running "serverless" installed locally (in service node_modules) Serverless: Packaging service... Serverless: Excluding development dependencies... Serverless: Clearing previous build ruby layer build [ '2.1' ] Serverless: Installing gem using local bundler Serverless: Zipping the gemfiles to examples/aws-ruby-sqs-with-dynamodb/.serverless/ruby_layer/gemLayer.zip Serverless: Configuring Layer and GEM_PATH to the functions Serverless: Uploading CloudFormation file to S3... Serverless: Uploading artifacts... Serverless: Uploading service serverless-ruby-sqs-dynamodb.zip file to S3 (7.71 KB)... Serverless: Uploading service gemLayer.zip file to S3 (1.35 MB)... Serverless: Validating template... Serverless: Updating Stack... Serverless: Checking Stack update progress... ........................... Serverless: Stack update finished... Service Information service: serverless-ruby-sqs-dynamodb stage: dev region: your-region-here stack: serverless-ruby-sqs-dynamodb-dev resources: 22 api keys: None endpoints: POST - https://XXXXXXXXXX.execute-api.your-region-here.amazonaws.com/dev/lottery functions: createLotteryCoupon: serverless-ruby-sqs-dynamodb-dev-createLotteryCoupon lotteryQueueWorker: serverless-ruby-sqs-dynamodb-dev-lotteryQueueWorker layers: gem: arn:aws:lambda:your-region-here:XXXXXXXXXXXXXX:layer:serverless-ruby-sqs-dynamodb-dev-ruby-bundle:3 lotteryQueue: queueUrl: https://sqs.your-region-here.amazonaws.com/XXXXXXXXXXXX/serverless-ruby-sqs-dynamodb-dev-lotteryQueue ``` ## Usage After the deployment, you will obtain the API Gateway endpoint. Simply send the POST request to it with params (either `curl`, or via `Postman`): curl --request POST 'https://XXXXXXXXXX.execute-api.your-region-here.amazonaws.com/dev/lottery' --header 'Content-Type: application/json' --data-raw '{"first_name": "Daniel", "last_name": "Ani", "coupon_value": 99}' ![postman-example](./images/postman-example.png) The response to the above requests will be: ```bash { "message": "Message sent" } ``` To check created records check your DynamoDB table: ![dynamodb-results](./images/dynamodb-results.png) *Important*: The DynamoDB table name is a combination of service name and stage. For the `dev` stage it will be: ``` serverless-ruby-sqs-dynamodb-dev ``` ### For checking the CloudWatch logs: - from lambda dashboard the `createLotteryCoupon` function: ![view-logs-in-cloud-watch](./images/view-logs-in-cloud-watch.png) - directly from CloudWatch logs `createLotteryCoupon` function: ![logs-result](./images/logs-result.png) Same for the `lotteryQueueWorker` function. ## SQS It is possible to configure email alerts in case messages end up in the `dead letter` queue. Within the queue construct edit the `alarm` value to your email. Make sure, that you confirm the subscription to the AWS Notification through your email: ![aws-notification](./images/aws-notification.png) For more options, it's possible to set up more [settings](https://github.com/getlift/lift/blob/master/docs/queue.md). ## Log retention The log retention is setup for 30 days. To change it simply change the value of this attribute in `serverless.yml` file: ``` bash logRetentionInDays: 30 ``` ## Structure | Path | Explanation | |----------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `./src` | All code for the project. | | `./src/handlers/handler` | Lambda function for producing sqs messages. | | `./src/handlers/worker` | Lambda function for consuming sqs messages. | | `./src/common/` | Space for common, reusable pieces of code. | | `./src/common/adapters/dynamo_db_adapter.rb` | Adapter for communication with DynamoDB with the usage of AWS SDK for Ruby. Only used for creating new records. | | `./src/common/adapters/sqs_adapter.rb` | Adapter for communication with SQS with the usage of AWS SDK for Ruby. Only used for sending new messages to SQS. | | `./src/common/services/create_lottery_coupon_service.rb` | The service object pattern is widely used within ruby/rails developers. A class that is responsible for doing only one thing. In our case is creating a lottery coupon record to the DynamoDB. | | `./src/common/services/create_sqs_message_service.rb` | In our case is sending a message to SQS. | | `./src/common/helpers/requests_helper.rb` | Helper methods for requests. In our case, for parsing and symbolize hash keys. | | `./src/common/schemas/lottery_coupon_schema.rb` | Schemas for entities within the application. In our case, the schema with required keys and data types for lottery coupon. | | `./src/common/serializers/error_serializer.rb` | Error serializer for 422 error. It gathers the dry-schema validation errors and puts them in a proper structure. | | `./src/common/validators/lottery_coupon_validator.rb` | Validator for the lottery coupon record. It's validating the incoming request against the dry-schema structure and returns errors and information on whether the params are incorrect. | ## Serverless plugins For this example, there are two serverless plugins used: | Plugin | Explanation | |-----------------------|------------------------------------------------------------------------------------------------| | [serverless-ruby-layer](https://www.npmjs.com/package/serverless-ruby-layer) | For bundling ruby gems from `Gemfile` and deploys them to the lambda layer. | | [serverless-lift](https://www.npmjs.com/package/serverless-lift) | For using AWS CDK construct within the Serverless Framework. In this example, [queue construct](https://github.com/getlift/lift/blob/master/docs/queue.md). | ## Ruby gems | Gem | Explanation | |--------------------|--------------------------------------------------------------------------------------------------------------------------------| | `aws-sdk-dynamodb` | It's a part of the AWS SDK for Ruby. Used for DynamoDB, in the case of this example - the creation of the new record. | | `aws-sdk-sqs` | It's a part of the AWS SDK for Ruby. Used for SQS, in the case of this example - produces and handles messages from the queue. | | `dry-schema` | For the purpose of the validation of the incoming requests. Validate both schema and data types. | ## Eject from the Lift plugin A very cool aspect is the [eject](https://github.com/getlift/lift#ejecting)|. In case your project grows beyond the plugin, you can eject from `Lift` at any time, as the plugin is based on `CloudFormation`. You're not chained to Lift at all. ## Remove service To remove the service do: ```bash sls remove ``` And the stack will be removed from the AWS. ================================================ FILE: aws-ruby-sqs-with-dynamodb/package.json ================================================ { "name": "serverless-ruby-sqs-dynamodb", "version": "1.0.0", "description": "A serverless ruby example that creates DynamoDB records with the usage of SQS, API Gateway, and AWS Lambda functions.", "author": "Daniel Aniszkiewicz", "license": "MIT", "dependencies": { "serverless-lift": "^1.1.1", "serverless-ruby-layer": "^1.5.0" } } ================================================ FILE: aws-ruby-sqs-with-dynamodb/serverless.yml ================================================ service: serverless-ruby-sqs-dynamodb frameworkVersion: '2' provider: name: aws runtime: ruby2.7 memorySize: 256 lambdaHashingVersion: 20201221 iam: role: statements: - Effect: Allow Action: - dynamodb:PutItem Resource: - !GetAtt Table.Arn constructs: lotteryQueue: type: queue worker: handler: src/handlers/lottery/worker.run environment: TABLE_NAME: ${self:custom.tableName} alarm: ${self:custom.alertEmail} batchSize: 5 functions: createLotteryCoupon: handler: src/handlers/lottery/handler.run events: - http: method: post path: lottery environment: QUEUE_URL: ${construct:lotteryQueue.queueUrl} plugins: - serverless-ruby-layer - serverless-lift custom: tableName: ${self:service}-${sls:stage} rubyLayer: include_functions: - createLotteryCoupon - lotteryQueueWorker alertEmail: mail@example.com resources: Resources: Table: Type: AWS::DynamoDB::Table Properties: TableName: ${self:custom.tableName} Tags: - Key: Application Value: ${self:service} - Key: Stage Value: ${sls:stage} BillingMode: PAY_PER_REQUEST AttributeDefinitions: - AttributeName: id AttributeType: S KeySchema: - AttributeName: id KeyType: HASH ================================================ FILE: aws-ruby-sqs-with-dynamodb/src/common/adapters/dynamo_db_adapter.rb ================================================ # frozen_string_literal: true require 'aws-sdk-dynamodb' require 'logger' class DynamoDBAdapter def initialize @client = Aws::DynamoDB::Client.new end def save_item(item) @client.put_item(table_item(item)) logger.info("Created dynamoDB item with id=#{item[:id]}") rescue Aws::DynamoDB::Errors::ServiceError => error logger.error(error) end private def logger @logger ||= Logger.new($stdout) end def table_item(item) { table_name: ENV['TABLE_NAME'], item: item } end end ================================================ FILE: aws-ruby-sqs-with-dynamodb/src/common/adapters/sqs_adapter.rb ================================================ # frozen_string_literal: true require 'aws-sdk-sqs' require 'logger' class SqsAdapter def initialize @client = Aws::SQS::Client.new end def send_message(message) client.send_message( queue_url: ENV['QUEUE_URL'], message_body: message ) logger.info('Created SQS message') rescue Aws::SQS::Errors::ServiceError => error logger.error(error) end private attr_reader :client def logger @logger ||= Logger.new($stdout) end end ================================================ FILE: aws-ruby-sqs-with-dynamodb/src/common/helpers/requests_helper.rb ================================================ # frozen_string_literal: true class Requests def initialize(body) @body = body end def call transform_body(@body) end private def transform_body(body) JSON.parse(body).transform_keys(&:to_sym) end end ================================================ FILE: aws-ruby-sqs-with-dynamodb/src/common/schemas/lottery_coupon_schema.rb ================================================ # frozen_string_literal: true require 'dry/schema' LotteryCouponSchema = Dry::Schema.Params do required(:first_name).filled(:string) required(:last_name).filled(:string) required(:coupon_value).value(:integer, gt?: 0) end ================================================ FILE: aws-ruby-sqs-with-dynamodb/src/common/serializers/error_serializer.rb ================================================ # frozen_string_literal: true module ErrorSerializer def self.serialize(exception) handle_unprocessable_exception(exception) end def self.dry_errors(exception) exception.map do |field, error_message_array| { status: 422, source: { pointer: field }, detail: error_message_array[0] } end.flatten end def self.handle_unprocessable_exception(exception) dry_errors(exception) end end ================================================ FILE: aws-ruby-sqs-with-dynamodb/src/common/services/create_lottery_coupon_service.rb ================================================ # frozen_string_literal: true require 'date' require 'securerandom' require_relative '../adapters/dynamo_db_adapter' class CreateLotteryCouponService LotteryCouponSchema = Struct.new(:id, :first_name, :last_name, :coupon_value, :created_at) def initialize(attributes) @attributes = attributes end def call coupon = build_coupon save(coupon) end def save(coupon) DynamoDBAdapter.new.save_item(coupon) end private attr_reader :attributes def build_coupon LotteryCouponSchema.new(SecureRandom.uuid, attributes[:first_name], attributes[:last_name], attributes[:coupon_value], DateTime.now.iso8601).to_h end end ================================================ FILE: aws-ruby-sqs-with-dynamodb/src/common/services/create_sqs_message_service.rb ================================================ # frozen_string_literal: true require_relative '../adapters/sqs_adapter' class CreateSqsMessageService def initialize(params) @params = params end def call message = sqs_message(params) send_message(message) end private attr_reader :params def send_message(message) SqsAdapter.new.send_message(message) end def sqs_message(params) { first_name: params[:first_name], last_name: params[:last_name], coupon_value: params[:coupon_value] }.to_json end end ================================================ FILE: aws-ruby-sqs-with-dynamodb/src/common/validators/lottery_coupon_validator.rb ================================================ # frozen_string_literal: true require_relative '../schemas/lottery_coupon_schema' class LotteryCouponValidator attr_reader :result, :coupon def initialize(coupon) @coupon = coupon @result = LotteryCouponSchema.call(coupon) end def failure? result.failure? end def errors result.errors.to_h end end ================================================ FILE: aws-ruby-sqs-with-dynamodb/src/handlers/lottery/handler.rb ================================================ # frozen_string_literal: true require 'json' require_relative '../../common/validators/lottery_coupon_validator' require_relative '../../common/helpers/requests_helper' require_relative '../../common/serializers/error_serializer' require_relative '../../common/services/create_sqs_message_service' def run(event:, context:) body = transform_event_body(event['body']) validation = LotteryCouponValidator.new(body) return unprocessable_entity(validation.errors) if validation.failure? create_sqs_message(body) end def unprocessable_entity(errors) hash = { errors: serialize_errors(errors) } { statusCode: 422, body: JSON.generate(hash) } end def create_sqs_message(body) CreateSqsMessageService.new(body).call { statusCode: 200, body: JSON.generate(message: 'Message sent') } rescue => error { statusCode: 500, body: JSON.generate(errors: error) } end def serialize_errors(errors) ErrorSerializer.serialize(errors) end def transform_event_body(body) Requests.new(body).call end ================================================ FILE: aws-ruby-sqs-with-dynamodb/src/handlers/lottery/worker.rb ================================================ # frozen_string_literal: true require_relative '../../common/services/create_lottery_coupon_service' require_relative '../../common/helpers/requests_helper' def run(event:, context:) params = transform_event_body(event['Records'][0]['body']) CreateLotteryCouponService.new(params).call end def transform_event_body(body) Requests.new(body).call end ================================================ FILE: aws-ruby-sqs-with-dynamodb/src/package.json ================================================ { "name": "serverless-ruby-sqs-dynamodb", "version": "1.0.0", "description": "A serverless ruby example that creates DynamoDB records with usage of SQS, API Gateway, and AWS Lambda functions", "author": "Daniel Aniszkiewicz", "license": "MIT", "dependencies": { "serverless-lift": "^1.1.0", "serverless-ruby-layer": "^1.5.0" } } ================================================ FILE: aws-ruby-step-functions/Gemfile ================================================ source 'https://rubygems.org' gem 'aws-sdk-dynamodb' ================================================ FILE: aws-ruby-step-functions/README.md ================================================ # Serverless AWS Ruby Step Functions This is an example of using `AWS Step Functions` `Standard` Workflow Type. It uses `AWS Lambda`, `DynamoDB` (create and update, 2 separate databases), and `flows` from `Step Functions`. ## Diagram ![diagram](./images/step-functions-diagram.png) The workflow used as an example is organising a holiday for example out of town. Here we have two dynamodb tables: - `tickets` table for tickets - `parking-lot-spaces` table for parking spaces The workflow first creates a record for us to buy a ticket, and to book a parking space (as Parallel state), and then waits for the day on which the tour is to take place (Wait State timestamp as check_in_date), and it checks the weather. Depending on the weather, the workflow is successful if the weather is good. Otherwise (bad weather), both the ticket and the parking space are cancelled. This can be considered a Saga pattern. ![detailed-diagram](./images/step-functions-detailed.png) ## Setup `npm install` to install all needed packages. ## Deployment In order to deploy the service run: ```bash sls deploy ``` for deploying with a specific `profile` (located in `~/.aws/credentials`) you can simply use the command: ```bash AWS_PROFILE=YOUR_PROFILE_NAME sls deploy ``` for deploying to the specific stage, let's say `staging` do: ```bash sls deploy --stage staging ``` The expected result should be similar to: ```bash Serverless: Packaging service... Serverless: Excluding development dependencies... Serverless: Clearing previous build ruby layer build [ '2.2' ] Serverless: Installing gem using local bundler Serverless: Zipping the gemfiles to ../examples/aws-ruby-step-functions/.serverless/ruby_layer/gemLayer.zip Serverless: Configuring Layer and GEM_PATH to the functions ✓ State machine "myStateMachine" definition is valid Serverless: Uploading CloudFormation file to S3... Serverless: Uploading artifacts... Serverless: Uploading service serverless-ruby-step-functions.zip file to S3 (1.03 MB)... Serverless: Uploading service gemLayer.zip file to S3 (640.83 KB)... Serverless: Validating template... Serverless: Updating Stack... Serverless: Checking Stack update progress... ................................................................................. Serverless: Stack update finished... Service Information service: serverless-ruby-step-functions stage: dev region: us-east-1 stack: aws-ruby-step-functions-dev resources: 23 api keys: None endpoints: None functions: buy-ticket: aws-ruby-step-functions-dev-buy-ticket reserve-parking-lot-space: aws-ruby-step-functions-dev-reserve-parking-lot-space return-ticket: aws-ruby-step-functions-dev-return-ticket release-parking-space: aws-ruby-step-functions-dev-release-parking-space check-weather: aws-ruby-step-functions-dev-check-weather layers: gem: arn:aws:lambda:YOUR_REGION:XXXXXXXXXXX:layer:aws-ruby-step-functions-dev-ruby-bundle:59 ``` ## Usage After the deployment, go to the AWS Dashboard, and enter Step Functions page. You will see a newly created state machine. Open the `organize-nice-weekend-state-machine` state machine and click on `Start Execution`. You need to provide the input in the JSON schema. Example: ``` JSON { "first_name": "Daniel", "last_name": "Ani", "check_in_date": "2021-07-11T19:35:07+00:00", "check_out_date": "2021-07-11T19:35:07+00:00", "driver_plate": "RUBY" } ``` ![example-payload](./images/example_payload.png) The `check_in_date` is the most important one. Without it, the state machine will be failed (it's needed for the purpose of the Wait state). ![wait-step-example](./images/wait-state.png) Later on, simply start the excecution. You can watch live as the change between states takes place. It is also possible to view every parameter going in and out of each state. The` weather` attribute is returned randomly (either `good` or `bad`), so sometimes you need several executions of the state machine to get a failed execution. ![output-data](./images/output-data.png) To check created records check your DynamoDB tables (both for tickets, and parking lot spaces). In both cases, you will see that for failure executions, the `current_status` for records, will be changed to `canceled`. Happy Path (good weather) ![happy-path](./images/happy-path.png) Unhappy path (bad weather) ![unhappy-path](./images/unhappy-path.png) ## Log retention The log retention is setup for 30 days. To change it simply change the value of this attribute in `serverless.yml` file: ``` bash logRetentionInDays: 30 ``` ## Advanced configuration More options (like alerting in case of failed excecutions), could be found in the plugin [repository](https://github.com/serverless-operations/serverless-step-functions). ## Structure | Path | Explanation | |----------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `./src` | All code for the project. | | `./src/handlers/buy_ticket` | Lambda function for creating a ticket. | | `./src/handlers/return_ticket` | Lambda function for returning the ticket. | | `./src/handlers/reserve_parking_lot_space` | Lambda function for reserving the parking lot space. | | `./src/handlers/release_parking_space` | Lambda function for releasing the parking spaceticket. | | `./src/handlers/check_weather` | Lambda function for checking weather. | | `./src/common/` | Space for common, reusable pieces of code. | | `./src/common/adapters/dynamo_db_adapter.rb` | Adapter for communication with DynamoDB with the usage of AWS SDK for Ruby. Used for creating new records and updating existing ones. | | `./src/common/services/ticket_service.rb` | The service object pattern is widely used within ruby/rails developers. In our case used for all things related to tickets, so creating and updating records within DynamoDB. | | `./src/common/adapters/reserve_parking_service.rb` | In our case used for all things related to parking reservations, so creating and updating records within DynamoDB. | ## Serverless plugins For this example, there are two serverless plugins used: | Plugin | Explanation | |-----------------------|------------------------------------------------------------------------------------------------| | [serverless-ruby-layer](https://www.npmjs.com/package/serverless-ruby-layer) | For bundling ruby gems from `Gemfile` and deploys them to the lambda layer. | | [serverless-step-functions](https://www.npmjs.com/package/serverless-step-functions) | Serverless Framework plugin for AWS Step Functions. | ## Ruby gems | Gem | Explanation | |--------------------|--------------------------------------------------------------------------------------------------------------------------------| | `aws-sdk-dynamodb` | It's a part of the AWS SDK for Ruby. Used for DynamoDB, in the case of this example - the creation of the new record. | ## Remove service To remove the service do: ```bash sls remove ``` And the stack will be removed from the AWS. ================================================ FILE: aws-ruby-step-functions/package.json ================================================ { "name": "AWS Ruby Step Functions", "version": "1.0.0", "description": "Ruby example that make usage of AWS Step Functions with AWS Lambda, DynamoDB and Step Functions flows.", "author": "Daniel Aniszkiewicz", "license": "MIT", "dependencies": { "serverless-ruby-layer": "^1.5.0" }, "devDependencies": { "serverless-step-functions": "^3.0.0" } } ================================================ FILE: aws-ruby-step-functions/serverless.yml ================================================ service: aws-ruby-step-functions frameworkVersion: '2' provider: name: aws runtime: ruby2.7 memorySize: 256 lambdaHashingVersion: 20201221 logRetentionInDays: 30 iam: role: statements: - Effect: Allow Action: - dynamodb:UpdateItem - dynamodb:PutItem Resource: - !GetAtt TicketsTable.Arn - !GetAtt ParkingLotSpacesTable.Arn functions: buy-ticket: handler: src/handlers/buy_ticket/handler.run environment: TABLE_NAME: ${self:custom.TicketsTable} reserve-parking-lot-space: handler: src/handlers/reserve_parking_lot_space/handler.run environment: TABLE_NAME: ${self:custom.ParkingLotSpacesTable} return-ticket: handler: src/handlers/return_ticket/handler.run environment: TABLE_NAME: ${self:custom.TicketsTable} release-parking-space: handler: src/handlers/release_parking_space/handler.run environment: TABLE_NAME: ${self:custom.ParkingLotSpacesTable} check-weather: handler: src/handlers/check_weather/handler.run plugins: - serverless-ruby-layer - serverless-step-functions custom: TicketsTable: ${self:service}-tickets-${sls:stage} ParkingLotSpacesTable: ${self:service}-parking-lot-spaces-${sls:stage} StateMachineName: organize-nice-weekend-state-machine-${sls:stage} rubyLayer: include_functions: - buy-ticket - reserve-parking-lot-space - return-ticket - release-parking-space - check-weather resources: Resources: TicketsTable: Type: AWS::DynamoDB::Table Properties: TableName: ${self:custom.TicketsTable} Tags: - Key: Application Value: ${self:service} BillingMode: PAY_PER_REQUEST AttributeDefinitions: - AttributeName: id AttributeType: S KeySchema: - AttributeName: id KeyType: HASH ParkingLotSpacesTable: Type: AWS::DynamoDB::Table Properties: TableName: ${self:custom.ParkingLotSpacesTable} Tags: - Key: Application Value: ${self:service} BillingMode: PAY_PER_REQUEST AttributeDefinitions: - AttributeName: id AttributeType: S KeySchema: - AttributeName: id KeyType: HASH stepFunctions: stateMachines: myStateMachine: name: ${self:custom.StateMachineName} definition: StartAt: Organize Nice Weekend States: Organize Nice Weekend: Type: Parallel Next: Transform properties Branches: - StartAt: Buy Ticket States: Buy Ticket: Type: Task Resource: Fn::GetAtt: [buy-ticket, Arn] ResultPath: $.ticket_id End: true - StartAt: Reserve Parking Lot Space States: Reserve Parking Lot Space: Type: Task Resource: Fn::GetAtt: [reserve-parking-lot-space, Arn] ResultPath: $.reservation_parking_id End: true Transform properties: Type: Pass Next: Wait For Arrival Date Parameters: ticket_id.$: $[0].ticket_id check_in_date.$: $[0].check_in_date reservation_id.$: $[1].reservation_parking_id Wait For Arrival Date: Type: Wait TimestampPath: "$.check_in_date" Next: Check Weather Check Weather: Type: Task Resource: Fn::GetAtt: [check-weather, Arn] ResultPath: $.weather Next: Decide based on Weather Decide based on Weather: Type: Choice Choices: - Variable: "$.weather" StringMatches: 'bad' Next: Cancel Plans Default: Organize Nice Weekend Successful Cancel Plans: Type: Parallel Next: Organize Nice Weekend Cancelled Branches: - StartAt: Return Ticket States: Return Ticket: Type: Task Resource: Fn::GetAtt: [return-ticket, Arn] InputPath: $.ticket_id Catch: - ErrorEquals: - States.ALL ResultPath: $.ReturnTicketError Next: Return Ticket ResultPath: $.ReturnTicketResult End: true - StartAt: Release Parking Space States: Release Parking Space: Type: Task Resource: Fn::GetAtt: [release-parking-space, Arn] InputPath: $.reservation_id Catch: - ErrorEquals: - States.ALL ResultPath: $.ReleaseParkingSpaceError Next: Release Parking Space ResultPath: $.ReleaseParkingSpaceResult End: true Organize Nice Weekend Successful: Type: Succeed Organize Nice Weekend Cancelled: Type: Fail Cause: Plans cancelled due to error. Error: Cancel Plans Error validate: true ================================================ FILE: aws-ruby-step-functions/src/common/adapters/dynamo_db_adapter.rb ================================================ # frozen_string_literal: true require 'aws-sdk-dynamodb' require 'logger' class DynamoDBAdapter def initialize @client = Aws::DynamoDB::Client.new end def save_item(item) @client.put_item(table_item(item)) logger.info("Created dynamoDB item with id=#{item[:id]}") rescue Aws::DynamoDB::Errors => error logger.error(error) end def update_item(item_id) @client.update_item(update_item_params(item_id)) logger.info("Updated dynamoDB item with id=#{item_id}") rescue Aws::DynamoDB::Errors => error logger.error(error) end private def logger @logger ||= Logger.new($stdout) end def table_item(item) { table_name: ENV['TABLE_NAME'], item: item } end def update_item_params(item_id) { expression_attribute_values: { ':s': 'canceled' }, key: { id: item_id }, return_values: 'UPDATED_NEW', table_name: ENV['TABLE_NAME'], update_expression: 'set current_status=:s' } end end ================================================ FILE: aws-ruby-step-functions/src/common/services/reserve_parking_service.rb ================================================ # frozen_string_literal: true require 'date' require 'securerandom' require_relative '../adapters/dynamo_db_adapter' class ReserveParkingService ReserveParkingSchema = Struct.new(:id, :driver_plate, :check_in_date, :check_out_date, :current_status, :created_at) def initialize(attributes) @attributes = attributes end def reserve_parking reserveration_attributes = build_reserveration DynamoDBAdapter.new.save_item(reserveration_attributes) reserveration_attributes[:id] end def release_parking raise 'Missing item id' unless attributes[:id] DynamoDBAdapter.new.update_item(attributes[:id]) end private attr_reader :attributes def default_status 'submitted' end def build_reserveration ReserveParkingSchema.new(SecureRandom.uuid, attributes[:driver_plate], attributes[:check_in_date], attributes[:check_out_date], default_status, DateTime.now.iso8601).to_h end end ================================================ FILE: aws-ruby-step-functions/src/common/services/ticket_service.rb ================================================ # frozen_string_literal: true require 'date' require 'securerandom' require_relative '../adapters/dynamo_db_adapter' class TicketService TicketSchema = Struct.new(:id, :first_name, :last_name, :check_in_date, :check_out_date, :current_status, :created_at) def initialize(attributes) @attributes = attributes end def create_ticket ticket_attribute = build_ticket DynamoDBAdapter.new.save_item(build_ticket) ticket_attribute[:id] end def cancel_ticket raise 'Missing item id' unless attributes[:id] DynamoDBAdapter.new.update_item(attributes[:id]) end private attr_reader :attributes def default_status 'submitted' end def build_ticket TicketSchema.new(SecureRandom.uuid, attributes[:first_name], attributes[:last_name], attributes[:check_in_date], attributes[:check_out_date], default_status, DateTime.now.iso8601).to_h end end ================================================ FILE: aws-ruby-step-functions/src/handlers/buy_ticket/handler.rb ================================================ # frozen_string_literal: true require 'json' require_relative '../../common/services/ticket_service' def run(event:, context:) create_ticket(event) end def create_ticket(body) TicketService.new(body).create_ticket rescue => error { statusCode: 500, body: JSON.generate(errors: error) } end ================================================ FILE: aws-ruby-step-functions/src/handlers/check_weather/handler.rb ================================================ # frozen_string_literal: true require 'json' require_relative '../../common/services/ticket_service' def run(event:, context:) weather_options = %w[bad good] weather_options.sample end ================================================ FILE: aws-ruby-step-functions/src/handlers/release_parking_space/handler.rb ================================================ # frozen_string_literal: true require 'json' require_relative '../../common/services/reserve_parking_service' def run(event:, context:) release_parking(parking_reservation(event)) end def release_parking(parking_reservation) ReserveParkingService.new(parking_reservation).release_parking { statusCode: 200, body: JSON.generate(message: 'Parking released') } rescue => error { statusCode: 500, body: JSON.generate(errors: error) } end def parking_reservation(event) { id: event } end ================================================ FILE: aws-ruby-step-functions/src/handlers/reserve_parking_lot_space/handler.rb ================================================ # frozen_string_literal: true require 'json' require_relative '../../common/services/reserve_parking_service' def run(event:, context:) reserve_parking(event) end def reserve_parking(body) ReserveParkingService.new(body).reserve_parking rescue => error { statusCode: 500, body: JSON.generate(errors: error) } end ================================================ FILE: aws-ruby-step-functions/src/handlers/return_ticket/handler.rb ================================================ # frozen_string_literal: true require 'json' require_relative '../../common/services/ticket_service' def run(event:, context:) cancel_ticket(ticket_id(event)) end def cancel_ticket(ticket_id) TicketService.new(ticket_id).cancel_ticket { statusCode: 200, body: JSON.generate(message: 'Ticket canceled') } rescue => error { statusCode: 500, body: JSON.generate(errors: error) } end def ticket_id(event) { id: event } end ================================================ FILE: aws-ruby-step-functions-express/Gemfile ================================================ source 'https://rubygems.org' gem 'aws-sdk-ses' ================================================ FILE: aws-ruby-step-functions-express/README.md ================================================ # Serverless AWS Ruby Step Functions Express ![diagram](./images/express-workflow-draw.png) This is an example of using `AWS Step Functions` `Express` Workflow Type. It uses `AWS Lambda`, `DynamoDB`, `Amazon SES`, `API Gateway` and `flows` from `Step Functions`. ## Diagram ![diagram](./images/stepfunctions_express_graph.png) It's a simple workflow, it adds the user to the database (`DynamoDB`) and if the save is successful, we send an email notification (`AWS Lambda` + `Amazon SES`). We also have error handling. ![detailed-diagram](./images/express-diagram-visual.png) ## Setup `npm install` to install all needed packages. ## Deployment In order to deploy the service run: ```bash sls deploy ``` for deploying with a specific `profile` (located in `~/.aws/credentials`) you can simply use the command: ```bash AWS_PROFILE=YOUR_PROFILE_NAME sls deploy ``` for deploying to the specific stage, let's say `staging` do: ```bash sls deploy --stage staging ``` The expected result should be similar to: ```bash Serverless: Running "serverless" installed locally (in service node_modules) Serverless: Packaging service... Serverless: Excluding development dependencies... Serverless: Clearing previous build ruby layer build [ '2.2' ] Serverless: Installing gem using local bundler Serverless: Zipping the gemfiles to sls-examples/examples/aws-ruby-step-functions-express/.serverless/ruby_layer/gemLayer.zip Serverless: Configuring Layer and GEM_PATH to the functions ✓ State machine "myStateMachine" definition is valid Serverless: Uploading CloudFormation file to S3... Serverless: Uploading artifacts... Serverless: Uploading service aws-ruby-step-functions.zip file to S3 (971.85 KB)... Serverless: Uploading service gemLayer.zip file to S3 (553.86 KB)... Serverless: Validating template... Serverless: Updating Stack... Serverless: Checking Stack update progress... ................................ Serverless: Stack update finished... Service Information service: aws-ruby-step-functions stage: dev region: eu-west-1 stack: aws-ruby-step-functions-dev resources: 16 api keys: None endpoints: functions: send-email: aws-ruby-step-functions-dev-send-email layers: gem: arn:aws:lambda:your-region:XXXXXXXXXXX:layer:aws-ruby-step-functions-dev-ruby-bundle:2 Serverless StepFunctions OutPuts endpoints: POST - https://XXXXXXXXXXX.execute-api.your-region.amazonaws.com/dev/employees/add ``` ## Prerequistes Make sure to validate `sender` and `recipient` emails within the `Amazon SES` (most likely for testing it, it will be one `email`): ![verify-email](./images/verify-email.png) Later on within `serverless.yml` edit in `custom` section, both `sender` and `recipient`. ## Usage There are two possible ways of invoking the example: ## Via Api Gateway After the deployment, grab the POST endpoint for this service. You can make a API call either by cURL or some tools like Postman. Use payload like: ```json { "id": "The name of the employee", "jobTitle": "the name of the employee's position" } ``` As a response you will get: ```json { "executionArn": "arn:aws:states:your-region:XXXXXXXXXXX:express:add-employee-and-send-email-state-machine-dev:e9fcce40-0642-48cb-bd9a-4d3d9cea7bcc:da5ee796-5944-4426-9ca2-410a5003ceee", "startDate": 1.62885382751E9 } ``` ![dynamo](./images/postman-express-step-functions.png) After it, you can then check the Dynamo database to see if a record has been created, and check the email. ![dynamo](./images/dynamodb-results-express.png) ![email](./images/email-express-step-functions.png) ## Via AWS Dashboard After the deployment, go to the AWS Dashboard, and enter Step Functions page. You will see a newly created state machine. Open the state machine and click on `Start Execution`. You need to provide the input in the JSON schema. Example: ``` JSON { "id": "The name of the employee", "jobTitle": "the name of the employee's position" } ``` Later on, simply start the excecution. After it, you can then check the Dynamo database to see if a record has been created, and check the email. Please keep in mind, that the `Express workflow` is different from the `Standard workflow`. Check the differences [here](https://docs.aws.amazon.com/step-functions/latest/dg/concepts-standard-vs-express.html). The main point here is the fact that you can't see all transitions between states. However, you can still use Workflow Studio. ## Logs ![logs](./images/metrics-express.png) By default, the logging is disabled. You can easily enabled it. The description is [here](https://www.serverless.com/plugins/serverless-step-functions#cloudwatch-logs). ## Log retention The log retention is setup for 30 days. To change it simply change the value of this attribute in `serverless.yml` file: ``` bash logRetentionInDays: 30 ``` ## Advanced configuration More options could be found in the plugin [repository](https://github.com/serverless-operations/serverless-step-functions). ## Structure | Path | Explanation | |-----------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------| | `./src` | All code for the project. | | `./src/handlers/send-email` | Handler for lambda. | | `./src/common/` | Space for common, reusable pieces of code. | | `./src/common/adapters/ses_adapter.rb` | Adapter for the Amazon SES with the usage of AWS SDK for Ruby. Only used for sending emails. | | `./src/common/services/send_email_service.rb` | The service object pattern is widely used within ruby/rails developers. A class that is responsible for doing only one thing. In our case is creating an email. | | ## Serverless plugins For this example, there are two serverless plugins used: | Plugin | Explanation | |-----------------------|------------------------------------------------------------------------------------------------| | [serverless-ruby-layer](https://www.npmjs.com/package/serverless-ruby-layer) | For bundling ruby gems from `Gemfile` and deploys them to the lambda layer. | | [serverless-step-functions](https://www.npmjs.com/package/serverless-step-functions) | Serverless Framework plugin for AWS Step Functions. | ## Ruby gems | Gem | Explanation | |--------------------|--------------------------------------------------------------------------------------------------------------------------------| | `aws-sdk-ses` | It's a part of the AWS SDK for Ruby. Used for Amazon SES, in the case of this example - sending emails. | ## Remove service To remove the service do: ```bash sls remove ``` And the stack will be removed from the AWS. ================================================ FILE: aws-ruby-step-functions-express/package.json ================================================ { "name": "AWS Ruby Step Functions Express Workflow", "version": "1.0.0", "description": "Ruby example that make usage of AWS Step Functions Express Type with AWS Lambda, DynamoDB, Amazon SES, API Gateway, and Step Functions flows", "author": "Daniel Aniszkiewicz", "license": "MIT", "dependencies": { "serverless-ruby-layer": "^1.5.0" }, "devDependencies": { "serverless-step-functions": "^3.0.0" } } ================================================ FILE: aws-ruby-step-functions-express/serverless.yml ================================================ service: aws-ruby-step-functions frameworkVersion: '2' provider: name: aws runtime: ruby2.7 region: eu-west-1 memorySize: 256 lambdaHashingVersion: 20201221 logRetentionInDays: 30 iam: role: statements: - Effect: Allow Action: - ses:SendEmail Resource: - "arn:aws:ses:${self:provider.region}:*:*" - Effect: Allow Action: - dynamodb:PutItem Resource: - !GetAtt Table.Arn functions: send-email: handler: src/handlers/send_email/handler.run environment: sender: ${self:custom.sender} recipient: ${self:custom.recipient} plugins: - serverless-ruby-layer - serverless-step-functions resources: Resources: Table: Type: AWS::DynamoDB::Table Properties: TableName: ${self:custom.tableName} Tags: - Key: Application Value: ${self:service} - Key: Stage Value: ${sls:stage} BillingMode: PAY_PER_REQUEST AttributeDefinitions: - AttributeName: id AttributeType: S KeySchema: - AttributeName: id KeyType: HASH custom: StateMachineName: add-employee-and-send-email-state-machine-${sls:stage} tableName: ${self:service}-${sls:stage} sender: sender@email.com recipient: recipient@mail.com rubyLayer: include_functions: - send-email stepFunctions: stateMachines: myStateMachine: type: EXPRESS name: ${self:custom.StateMachineName} events: - http: path: employees/add method: POST definition: StartAt: Save to DynamoDB States: Save to DynamoDB: Type: Task Resource: arn:aws:states:::dynamodb:putItem Parameters: TableName: ${self:custom.tableName} Item: id: S.$: $.id JobTitle: S.$: $.jobTitle Next: Is Save Successful Is Save Successful: Type: Choice Choices: - Variable: "$.SdkHttpMetadata.HttpStatusCode" NumericEquals: 200 Next: SES notification Default: Fail Execution SES notification: Type: Task Resource: Fn::GetAtt: [send-email, Arn] Catch: - ErrorEquals: ["States.ALL"] Next: Fail Execution Next: Success Execution Fail Execution: Type: Fail Success Execution: Type: Succeed validate: true ================================================ FILE: aws-ruby-step-functions-express/src/common/adapters/ses_adapter.rb ================================================ # frozen_string_literal: true require 'aws-sdk-ses' require 'logger' class SesAdapter def initialize @ses_client = Aws::SES::Client.new end def send_email ses_client.send_email(mail_data) logger.info('Email was sent') rescue Aws::SES::Errors => error logger.error(error) end private attr_reader :ses_client def mail_data sender = ENV['sender'] recipient = ENV['recipient'] subject = 'Employee added' textbody = "This email was sent with Amazon SES triggers via AWS Step Functions. Added new employee." encoding = 'UTF-8' htmlbody = "

Employee added

"\ "

This email was sent with Amazon SES triggers via AWS Step Functions. Added new employee.

"\ { destination: { to_addresses: [ recipient ] }, message: { body: { html: { charset: encoding, data: htmlbody }, text: { charset: encoding, data: textbody } }, subject: { charset: encoding, data: subject } }, source: sender } end def logger @logger ||= Logger.new($stdout) end end ================================================ FILE: aws-ruby-step-functions-express/src/common/services/send_email_service.rb ================================================ # frozen_string_literal: true require_relative '../adapters/ses_adapter' class SendEmailService def call send end private def send SesAdapter.new.send_email end end ================================================ FILE: aws-ruby-step-functions-express/src/handlers/send_email/handler.rb ================================================ # frozen_string_literal: true require_relative '../../common/services/send_email_service' def run(event:, context:) SendEmailService.new.call { statusCode: 200, body: 'email send' } rescue => error { statusCode: 500, body: JSON.generate(errors: error) } end ================================================ FILE: aws-ruby-step-functions-with-callback/Gemfile ================================================ source 'https://rubygems.org' gem 'aws-sdk-comprehend' gem 'aws-sdk-dynamodb' gem 'aws-sdk-states' ================================================ FILE: aws-ruby-step-functions-with-callback/README.md ================================================ # Ruby AWS Step Functions with Callback pattern This is an example of using `AWS Step Functions` with [callback pattern](https://docs.aws.amazon.com/step-functions/latest/dg/callback-task-sample-sqs.html). It uses `AWS Lambda`, `DynamoDB`, `API Gateway`, `Amazon Comprehend`, `flows` from `Step Functions`. ## Diagram ![draw](./images/step-functions-with-callback.png) ![diagram](./images/stepfunctions-with-callback-graph.png) This Workflow is quite simple. Assuming we have a comment system in our application, we would like to check for PII data before adding the comment to the database. To do this, we use Amazon Comprehend which, based on trained ML models, checks if a given unstructured text contains sensitive data and returns information on which positions in the sentence it is located. When detecting sensitive data with Amazon Comprehend, we use the `taskToken` from AWS `step functions` to wait for the detection results and continue our workflow. Keep in mind, that `taskToken` is generated by Step Functions automatically. Then, depending on the detection results, we perform a redaction process, replacing the sensitive data with an asterisk (*) At the very end, the redacted comment is saved into the database (DynamoDB) ![detailed-diagram](./images/detailed-diagram-callback-sf.png) ## Setup `npm install` to install all needed packages. ## Deployment In order to deploy the service run: ```bash sls deploy ``` for deploying with a specific `profile` (located in `~/.aws/credentials`) you can simply use the command: ```bash AWS_PROFILE=YOUR_PROFILE_NAME sls deploy ``` for deploying to the specific stage, let's say `staging` do: ```bash sls deploy --stage staging ``` The expected result should be similar to: ```bash Serverless: Running "serverless" installed locally (in service node_modules) Serverless: Packaging service... Serverless: Excluding development dependencies... Serverless: Clearing previous build ruby layer build [ '2.2' ] Serverless: Installing gem using local bundler Serverless: Zipping the gemfiles to /.serverless/ruby_layer/gemLayer.zip Serverless: Configuring Layer and GEM_PATH to the functions ✓ State machine "WorkflowWithCallback" definition is valid Serverless: Uploading CloudFormation file to S3... Serverless: Uploading artifacts... Serverless: Uploading service aws-ruby-step-functions-with-callback.zip file to S3 (137.4 KB)... Serverless: Uploading service gemLayer.zip file to S3 (749.01 KB)... Serverless: Validating template... Serverless: Updating Stack... Serverless: Checking Stack update progress... ......................... Serverless: Stack update finished... Service Information service: aws-ruby-step-functions-with-callback stage: dev region: us-east-1 stack: aws-ruby-step-functions-with-callback-dev resources: 19 api keys: None endpoints: functions: check-comment: aws-ruby-step-functions-with-callback-dev-check-comment redact-comment: aws-ruby-step-functions-with-callback-dev-redact-comment layers: gem: arn:aws:lambda:your-region:XXXXXXXXXXX:layer:aws-ruby-step-functions-with-callback-dev-ruby-bundle:49 Serverless StepFunctions OutPuts endpoints: POST - https://XXXXXXXXXXXX.execute-api.your-region.amazonaws.com/dev/comments/add ``` ## Usage There are two possible ways of invoking the example: ## Via Api Gateway After the deployment, grab the POST endpoint for this service. You can make a API call either by cURL or some tools like Postman. Use payload like: ```json { "comment": "My pin number is 1234", "author": "Daniel" } ``` or something with more PII data included: ```json { "comment": "Good morning, everybody. My name is Van Bokhorst Serdar, and today I feel like sharing a whole lot of personal information with you. Let's start with my Email address SerdarvanBokhorst@dayrep.com. My address is 2657 Koontz Lane, Los Angeles, CA. My phone number is 818-828-6231. My Social security number is 548-95-6370. My Bank account number is 940517528812 and routing number 195991012. My credit card number is 5534816011668430, Expiration Date 6/1/2022, my C V V code is 121, and my pin 123456. Well, I think that's it. You know a whole lot about me. And I hope that Amazon comprehend is doing a good job at identifying PII entities so you can redact my personal information away from this document. Let's check.", "author": "Daniel" } ``` As a response you will get: ```json { "executionArn": "arn:aws:states:your-region:XXXXXXXXXXXX:execution:workflow-with-callback-dev:79fa0214-8533-4506-b47f-513ca8721fe0", "startDate": 1.628965530156E9 } ``` ![dynamo](./images/postman.png) After it, you can then check the Dynamo database to see if a record has been created. ![dynamo](./images/dynamodb-results.png) ## Via AWS Dashboard After the deployment, go to the AWS Dashboard, and enter Step Functions page. You will see a newly created state machine. Open the state machine and click on `Start Execution`. You need to provide the input in the JSON schema. Example: ```json { "comment": "My pin number is 1234", "author": "Daniel" } ``` or something with more PII data included: ```json { "comment": "Good morning, everybody. My name is Van Bokhorst Serdar, and today I feel like sharing a whole lot of personal information with you. Let's start with my Email address SerdarvanBokhorst@dayrep.com. My address is 2657 Koontz Lane, Los Angeles, CA. My phone number is 818-828-6231. My Social security number is 548-95-6370. My Bank account number is 940517528812 and routing number 195991012. My credit card number is 5534816011668430, Expiration Date 6/1/2022, my C V V code is 121, and my pin 123456. Well, I think that's it. You know a whole lot about me. And I hope that Amazon comprehend is doing a good job at identifying PII entities so you can redact my personal information away from this document. Let's check.", "author": "Daniel" } ``` Later on, simply start the excecution. After it, you can then check the Dynamo database to see if a record has been created. As we use `Standard` workflow type, feel free to check the executions: ![executions](./images/output.png) If there are no detections, the redaction process will not be triggered, so flow will look like this: ![without-redaction](./images/data-without-pii.png) ## Log retention The log retention is setup for 30 days. To change it simply change the value of this attribute in `serverless.yml` file: ``` bash logRetentionInDays: 30 ``` ## Advanced configuration More options (like alerting in case of failed excecutions), could be found in the plugin [repository](https://github.com/serverless-operations/serverless-step-functions). ## Structure | Path | Explanation | |-----------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------| | `./src` | All code for the project. | | `./src/handlers/send_token` | Handler for lambda. | | `./src/common/` | Space for common, reusable pieces of code. | | `./src/common/adapters/step_functions_adapter.rb` | Adapter for the AWS Step Functions with the usage of AWS SDK for Ruby. Only used for sending success and failure task_token. | | `./src/common/services/send_task_token_service.rb` | The service object pattern is widely used within ruby/rails developers. A class that is responsible for doing only one thing. In our case is handling task token. | | `./src/common/services/detection_service.rb` | Responsible for making detection with Amazon Comprehend to detect PII data. | | `./src/common/services/redaction_service.rb` | Responsible for redacting comment based on the Amazon Comprehend detection results. | ## Serverless plugins For this example, there are two serverless plugins used: | Plugin | Explanation | |-----------------------|------------------------------------------------------------------------------------------------| | [serverless-ruby-layer](https://www.npmjs.com/package/serverless-ruby-layer) | For bundling ruby gems from `Gemfile` and deploys them to the lambda layer. | | [serverless-step-functions](https://www.npmjs.com/package/serverless-step-functions) | Serverless Framework plugin for AWS Step Functions. | ## Ruby gems | Gem | Explanation | |--------------------|--------------------------------------------------------------------------------------------------------------------------------| | `aws-sdk-comprehend` | It's a part of the AWS SDK for Ruby. Used for Amazon Comprehend, in the case of this example - the detection of the PII data. | | `aws-sdk-dynamodb` | It's a part of the AWS SDK for Ruby. Used for DynamoDB, in the case of this example - the creation of the new record. | | `aws-sdk-states` | It's a part of the AWS SDK for Ruby. Used for AWS Step Functions. | ## Remove service To remove the service do: ```bash sls remove ``` And the stack will be removed from the AWS. ================================================ FILE: aws-ruby-step-functions-with-callback/package.json ================================================ { "name": "aws-ruby-step-functions-callback", "version": "1.0.0", "description": "Ruby example that make usage of AWS Step Functions with callback pattern, AWS Lambda, DynamoDB, Amazon Comprehend, API Gateway, and Step Functions flows", "author": "Daniel Aniszkiewicz", "license": "MIT", "dependencies": { "serverless-ruby-layer": "^1.5.0" }, "devDependencies": { "serverless": "^2.46.0", "serverless-step-functions": "^3.0.0" } } ================================================ FILE: aws-ruby-step-functions-with-callback/serverless.yml ================================================ service: aws-ruby-step-functions-with-callback frameworkVersion: '2' provider: name: aws region: us-east-1 runtime: ruby2.7 memorySize: 256 timeout: 10 logRetentionInDays: 30 lambdaHashingVersion: 20201221 iam: role: statements: - Effect: Allow Action: - states:SendTaskFailure - states:SendTaskSuccess Resource: - '*' - Effect: Allow Action: - comprehend:DetectPiiEntities Resource: - '*' - Effect: Allow Action: - dynamodb:PutItem Resource: - !GetAtt Table.Arn functions: check-comment: handler: src/handlers/check_comment/handler.run environment: region: ${self:provider.region} redact-comment: handler: src/handlers/redact_comment/handler.run plugins: - serverless-ruby-layer - serverless-step-functions custom: stateMachineName: workflow-with-callback-${sls:stage} tableName: ${self:service}-${sls:stage} rubyLayer: include_functions: - check-comment - redact-comment resources: Resources: Table: Type: AWS::DynamoDB::Table Properties: TableName: ${self:custom.tableName} Tags: - Key: Application Value: ${self:service} - Key: Stage Value: ${sls:stage} BillingMode: PAY_PER_REQUEST AttributeDefinitions: - AttributeName: id AttributeType: S KeySchema: - AttributeName: id KeyType: HASH stepFunctions: stateMachines: WorkflowWithCallback: name: ${self:custom.stateMachineName} events: - http: path: comments/add method: POST definition: StartAt: Pass Params States: Pass Params: Type: Pass Next: Run Detection Parameters: comment.$: $.comment author.$: $.author Run Detection: Type: Task Resource: arn:aws:states:::lambda:invoke.waitForTaskToken Parameters: FunctionName: Fn::GetAtt: [check-comment, Arn] Payload: task_token.$: $$.Task.Token comment.$: $.comment Retry: - ErrorEquals: - Lambda.ServiceException - Lambda.AWSLambdaException - Lambda.SdkClientException IntervalSeconds: 2 MaxAttempts: 6 BackoffRate: 2 ResultPath: $.result Next: Is Detection Result Is Detection Result: Type: Choice Choices: - Variable: $.result StringEquals: No result Next: Save to DB Default: Redact Comment Redact Comment: Type: Task Resource: Fn::GetAtt: [redact-comment, Arn] ResultPath: $.comment Retry: - ErrorEquals: - Lambda.ServiceException - Lambda.AWSLambdaException - Lambda.SdkClientException IntervalSeconds: 2 MaxAttempts: 6 BackoffRate: 2 Next: Save to DB Save to DB: Type: Task Resource: arn:aws:states:::dynamodb:putItem Parameters: TableName: ${self:custom.tableName} Item: id: S.$: $.author comment: S.$: $.comment End: true validate: true ================================================ FILE: aws-ruby-step-functions-with-callback/src/common/adapters/step_functions_adapter.rb ================================================ # frozen_string_literal: true require 'aws-sdk-states' require 'logger' class StepFunctionsAdapter attr_reader :sf_client, :task_token, :output def initialize(task_token:, output:) @sf_client = Aws::States::Client.new @task_token = task_token @output = output end def send_task_success sf_client.send_task_success(success_task_token_data(task_token: task_token, output: JSON.generate(output))) logger.info('Success task') end def send_task_failure sf_client.send_task_failure(failure_task_token_data(task_token: task_token, output: output)) logger.info('Failed task') end private def success_task_token_data(task_token:, output:) { task_token: task_token, output: output } end def failure_task_token_data(task_token:, output:) { task_token: task_token, error: output } end def logger @logger ||= Logger.new($stdout) end end ================================================ FILE: aws-ruby-step-functions-with-callback/src/common/services/detection_service.rb ================================================ # frozen_string_literal: true require 'aws-sdk-comprehend' class DetectionService DetectionSchema = Struct.new(:begin_offset, :end_offset) def initialize(comment) @comprehend_client = Aws::Comprehend::Client.new(region: ENV['region']) @comment = comment @result = [] end def call result = detect_pii_entities(comment) pii_detected?(result) ? build_result(result.entities) : 'No result' end attr_reader :comprehend_client, :comment, :result private def build_result(entities) structed_result = [] entities.each do |entity| structed_result << Hash[begin_offset: entity[:begin_offset], end_offset: entity[:end_offset]] end structed_result end def detect_pii_entities(content_to_be_analyzed) comprehend_client.detect_pii_entities(text: content_to_be_analyzed, language_code: 'en') end def pii_detected?(result) result.entities.any? end end ================================================ FILE: aws-ruby-step-functions-with-callback/src/common/services/redaction_service.rb ================================================ # frozen_string_literal: true class RedactionService CHARACTER_TO_CLEANUP_CONTENT = '*' def initialize(comment_to_redact, detection_result) @comment_to_redact = comment_to_redact @detection_result = detection_result end def call redact_note(detection_result, comment_to_redact) end attr_reader :comment_to_redact, :detection_result private def redact_note(entities, comment_to_redact) redacted_comment = comment_to_redact.dup entities.each do |entity| redacted_comment[entity['begin_offset'], entity_offset_difference(entity)] = CHARACTER_TO_CLEANUP_CONTENT * entity_offset_difference(entity) end redacted_comment end def entity_offset_difference(entity) entity['end_offset'] - entity['begin_offset'] end end ================================================ FILE: aws-ruby-step-functions-with-callback/src/common/services/send_task_token_service.rb ================================================ # frozen_string_literal: true require_relative '../adapters/step_functions_adapter' class SendTaskTokenService def initialize(task_token, status, output) @task_token = task_token @status = status @output = output end def call send_task end private attr_reader :task_token, :status, :output def send_task send_task = StepFunctionsAdapter.new(task_token: task_token, output: output) if status_success? send_task.send :send_task_success else send_task.send :send_task_failure end end def status_success? status == 'success' end end ================================================ FILE: aws-ruby-step-functions-with-callback/src/handlers/check_comment/handler.rb ================================================ # frozen_string_literal: true require_relative '../../common/services/send_task_token_service' require_relative '../../common/services/detection_service' def run(event:, context:) task_token = event['task_token'] comment = event['comment'] check_content_comment(comment, task_token) end def send_task_token(task_token, status, response) SendTaskTokenService.new(task_token, status, response).call end def check_content_comment(comment, task_token) response = DetectionService.new(comment).call send_task_token(task_token, 'success', response) rescue => error send_task_token(task_token, 'failure', error) end ================================================ FILE: aws-ruby-step-functions-with-callback/src/handlers/redact_comment/handler.rb ================================================ # frozen_string_literal: true require_relative '../../common/services/redaction_service' def run(event:, context:) comment = event['comment'] detection_result = event['result'] redact_comment(comment, detection_result) end def redact_comment(comment, detection_result) RedactionService.new(comment, detection_result).call end ================================================ FILE: aws-rust-simple-http-endpoint/.gitignore ================================================ .serverless *.pyc *.pyo ================================================ FILE: aws-rust-simple-http-endpoint/Cargo.toml ================================================ [workspace] members = ["test"] ================================================ FILE: aws-rust-simple-http-endpoint/README.md ================================================ # Serverless Boilerplate - AWS - Rust Make sure `serverless` is installed. [See installation guide](https://serverless.com/framework/docs/providers/AWS/guide/installation/). You will also need to set up your AWS account credentials using environment variables or a configuration file. Please see the [this guide for more information](https://serverless.com/framework/docs/providers/AWS/guide/credentials/). ## 1. Install Project Dependencies `npm install` in this directory to download the modules from `package.json`. ## 2. Compile Rust Binary ``` $ cargo build --release ``` ## 3. Deploy Hackish way to deploy 1. Run `sls deploy` which would give an error about missing file path in the package path. 2. If you look at .serverless it should have 3 files- cloudformation-template-create-stack.json cloudformation-template-update-stack.json serverless-state.json 3. rename .serverless folder to p 4. cargo build --release then add aws-rust-simple-http-endpoint.zip which consists of target/release/test only to the p folder 5. sls deploy --package p ## 4. Invoke deployed function ```bash $ curl https://***.execute-api.us-east-1.amazonaws.com/test/test {"message":"Serverless Rust Hello"} ``` **For more information on the Serverless AWS plugin, please see the project repository: [https://serverless.com/framework/docs/providers/AWS/guide/credentials/](https://serverless.com/framework/docs/providers/AWS/guide/credentials/).** ================================================ FILE: aws-rust-simple-http-endpoint/package.json ================================================ { "name": "aws-rust-simple-http-endpoint", "version": "0.0.1", "description": "Example demonstrates how to setup a simple HTTP GET endpoint with rust", "author": "Jonee Ryan Ty", "devDependencies": { "serverless-rust": "^0.3.8" } } ================================================ FILE: aws-rust-simple-http-endpoint/serverless.yml ================================================ service: aws-rust-simple-http-endpoint frameworkVersion: '2' provider: name: aws runtime: rust # memorySize: 128 # stage: api # we attach the stage ie dev or prod in the path region: us-east-1 # make sure your region is correct package: # individually: true # creates one artifact for each function exclude: - ./** include: - ./target/release/test plugins: - serverless-rust functions: test_test: handler: test events: - httpApi: path: /test/test method: get custom: # this section allows for customization of the default # serverless-rust plugin settings rust: # flags passed to cargo # cargoFlags: '--features enable-awesome' # experimental! when set to true, artifacts are built locally outside of docker dockerless: true ================================================ FILE: aws-rust-simple-http-endpoint/test/Cargo.toml ================================================ [package] name = "test" version = "0.1.0" edition = "2018" authors = ["jonee"] [dependencies] tokio = { version = "0.2", features = ["macros"] } lambda_http = { git = "https://github.com/awslabs/aws-lambda-rust-runtime/", branch = "master"} serde_json = "1.0" ================================================ FILE: aws-rust-simple-http-endpoint/test/src/main.rs ================================================ use lambda_http::{handler, lambda, IntoResponse, Request, Context}; use serde_json::json; type Error = Box; #[tokio::main] async fn main() -> Result<(), Error> { lambda::run(handler(test)).await?; Ok(()) } async fn test(_: Request, _: Context) -> Result { // `serde_json::Values` impl `IntoResponse` by default // creating an application/json response Ok(json!({ "message": "Serverless Rust Hello" })) } /* #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn test_handles() { let request = Request::default(); let expected = json!({ "message": "Serverless Rust Hello" }) .into_response(); let response = test(request, Context::default()) .await .expect("expected Ok(_) value") .into_response(); assert_eq!(response.body(), expected.body()) } } */ ================================================ FILE: azure-node-line-bot/README.md ================================================ # Azure-line-bot-exmaple This is simple echo bot on Line messenger. ## Before you begin - Line developer account - [A channel for Line Messaging API](https://developers.line.me/en/docs/messaging-api/getting-started/) ## Get started ### install dependencies ``` $ npm install ``` ### insert your Line bot Access Token & Secret ``` const config = { channelAccessToken: "CHANNEL_ACCESS_TOKEN", channelSecret: "CHANNEL_SECRET", }; ``` ### deploy ``` serverless deploy ``` ![image](https://github.com/jiyeonseo/azure-line-bot-example/blob/master/screenshot-2.png) ## More details - [Building a bot](https://developers.line.me/en/docs/messaging-api/building-bot/) - [line-bot-sdk-nodejs](https://github.com/line/line-bot-sdk-nodejs) ================================================ FILE: azure-node-line-bot/handler.js ================================================ 'use strict'; /* eslint-disable no-param-reassign */ const line = require('@line/bot-sdk'); // create LINE SDK config from env variables const config = { channelAccessToken: 'CHANNEL_ACCESS_TOKEN', channelSecret: 'CHANNEL_SECRET', }; const client = new line.Client(config); function handleEvent(event) { if (event.type !== 'message' || event.message.type !== 'text') { // ignore non-text-message event return Promise.resolve(null); } // create a echoing text message const echo = { type: 'text', text: event.message.text }; // use reply API return client.replyMessage(event.replyToken, echo); } module.exports.hello = (context, req) => { Promise .all(req.body.events.map(handleEvent)) .then((result) => { context.res.json(result); }) .then(() => context.done()) .catch((err) => { console.error(err); context.res.status(500).end(); }); }; ================================================ FILE: azure-node-line-bot/package.json ================================================ { "name": "azure-nodejs", "version": "1.0.0", "description": "Azure Functions sample for the Serverless framework", "main": "handler.js", "keywords": [ "azure", "serverless" ], "devDependencies": { "serverless-azure-functions": "*" }, "dependencies": { "@line/bot-sdk": "^6.3.0" } } ================================================ FILE: azure-node-line-bot/serverless.yml ================================================ # Welcome to Serverless! # # This file is the main config file for your service. # It's very minimal at this point and uses default values. # You can always add more config options for more control. # We've included some commented out config examples here. # Just uncomment any of them to get that config option. # # For full config options, check the docs: # docs.serverless.com # # Happy Coding! service: azure-line-bot-example # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details # frameworkVersion: "=X.X.X" provider: name: azure location: West US plugins: - serverless-azure-functions # you can add packaging information here #package: # include: # - include-me.js # - include-me-dir/** # exclude: # - exclude-me.js # - exclude-me-dir/** functions: hello: handler: handler.hello events: - http: true x-azure-settings: authLevel : anonymous - http: true x-azure-settings: direction: out name: res # The following are a few examples of other events you can configure: # # events: # - queue: YourQueueName # x-azure-settings: # connection : StorageAppSettingName # - blob: # x-azure-settings: # name: bindingName # direction: in ================================================ FILE: azure-node-simple-http-endpoint/.gitignore ================================================ node_modules functions .serverless ================================================ FILE: azure-node-simple-http-endpoint/README.md ================================================ # Simple HTTP example In this example, we deploy an HTTP Node.js Azure Function. This sample show you how to read properties off of a query string or the request body, then set a result back to Azure. See the [Azure Functions Serverless Plugin docs](https://www.serverless.com/framework/docs/providers/azure/) for more info. _Note: you may need to change the `service` name in `serverless.yml`_ ## Setup 1. We recommend Node.js v6.5.0 2. Install the serverless framework - `npm install -g serverless` 3. Install the dependencies of this example - `npm install` ## Deploying To deploy, use the `deploy` and follow the instructions to log into your Azure account. ```bash $ serverless deploy Serverless: Packaging service... Serverless: Logging in to Azure Serverless: Paste this code (copied to your clipboard) into the launched browser, and complete the authentication process: BLAZSRMVJ ``` Once authenticated, the session will continue and deploy the app. ## Invoking Invoke the deployed function using the `invoke` command. ```bash $ serverless invoke -f hello -d "{ \"name\": \"World\" }" Serverless: Logging in to Azure "Hello World" ``` ================================================ FILE: azure-node-simple-http-endpoint/handler.js ================================================ 'use strict'; module.exports.hello = (context, req) => { context.log('JavaScript HTTP trigger function processed a request.'); const res = {}; if (req.query.name || (req.body && req.body.name)) { const name = req.query.name || req.body.name; res.body = `Hello ${name}`; } else { res.status = 400; res.body = 'Please pass a name on the query string or in the request body'; } context.done(null, res); }; ================================================ FILE: azure-node-simple-http-endpoint/package.json ================================================ { "name": "azure-node-simple-http-endpoint", "version": "0.1.0", "description": "An example of making http endpoints with the Azure Functions Serverless Framework plugin", "main": "index.js", "author": "serverless.com", "license": "MIT", "dependencies": { "serverless-azure-functions": "^0.2.0" } } ================================================ FILE: azure-node-simple-http-endpoint/serverless.yml ================================================ service: azfx-node-http provider: name: azure location: West US plugins: - serverless-azure-functions package: exclude: - node_modules/** - .gitignore - package.json - .git/** functions: hello: handler: handler.hello events: - http: true x-azure-settings: authLevel: anonymous ================================================ FILE: azure-node-telegram-bot/README.md ================================================ # Azure Node Telegram Bot This example of telegram bot using Azure Function with Serverless Framework. ## Usage ### Required - Node.js `v6.5.0` or later - Telegram account 📱 - Azure Account. check this [link](https://serverless.com/framework/docs/providers/azure/guide/credentials/) about azure credentials. ### Get started 1. Clone the repo and install dependencies ```shall # Clone the repo $ git clone git@github.com:serverless/examples.git serverless-examples $ cd serverless-examples/azure-node-telegram-bot # Install the Serverless Framework $ npm install serverless -g # Install the necessary plugins $ npm install ``` 2. Create a bot from Telegram, sending this message to [@BotFather](https://web.telegram.org/#/im?p=@BotFather) ``` $ /newbot ``` 3. Put the token received into a file called `handle.js`. ``` const token = "YOUR_API_TOKEN"; ``` 4. Deploy it! ``` $ serverless deploy ``` 5. Configure webhook ``` curl --request POST --url https://api.telegram.org/bot{token}/setWebhook --header 'content-type: application/json' --data '{"url": "{end-poinnt}"}' ``` Say `hello` to your bot 🤖 ================================================ FILE: azure-node-telegram-bot/handler.js ================================================ 'use strict'; const request = require('request'); module.exports = (context, req) => { const token = 'YOUR_API_TOKEN'; const BASE_URL = `https://api.telegram.org/bot${token}/sendMessage`; const chatId = req.body.message.chat.id; request.post(BASE_URL).form({ text: 'Hello World!', chat_id: chatId }); const res = { // status: 200, /* Defaults to 200 */ body: 'ok', }; context.done(null, res); }; ================================================ FILE: azure-node-telegram-bot/package.json ================================================ { "name": "azure-nodejs", "version": "1.0.0", "description": "Azure Functions sample for the Serverless framework", "main": "handler.js", "keywords": [ "azure", "serverless" ], "devDependencies": { "serverless-azure-functions": "*" }, "dependencies": { "lint": "^1.1.2", "request": "^2.88.0" } } ================================================ FILE: azure-node-telegram-bot/serverless.yml ================================================ # Welcome to Serverless! # # This file is the main config file for your service. # It's very minimal at this point and uses default values. # You can always add more config options for more control. # We've included some commented out config examples here. # Just uncomment any of them to get that config option. # # For full config options, check the docs: # docs.serverless.com # # Happy Coding! service: azure-telegram-bot # You can pin your service to only deploy with a specific Serverless version # Check out our docs for more details # frameworkVersion: "=X.X.X" provider: name: azure location: West US plugins: - serverless-azure-functions # you can add packaging information here #package: # include: # - include-me.js # - include-me-dir/** # exclude: # - exclude-me.js # - exclude-me-dir/** functions: hello: handler: handler.hello events: - http: true x-azure-settings: authLevel : anonymous - http: true x-azure-settings: direction: out name: res # The following are a few examples of other events you can configure: # # events: # - queue: YourQueueName # x-azure-settings: # connection : StorageAppSettingName # - blob: # x-azure-settings: # name: bindingName # direction: in ================================================ FILE: azure-node-typescript-servicebus-trigger-endpoint/.eslintignore ================================================ lib node_modules experiment.ts src/model/assetmanagementapi/types.ts webpack.config.js ================================================ FILE: azure-node-typescript-servicebus-trigger-endpoint/.eslintrc ================================================ { "root": true, "parser": "@typescript-eslint/parser", "plugins": ["@typescript-eslint", "prettier"], "extends": [ "eslint:recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", "prettier" ], "rules": { "no-console": 1, "prettier/prettier": 2, "@typescript-eslint/explicit-function-return-type": 2 }, "ignorePatterns": ["__experiments/*"] } ================================================ FILE: azure-node-typescript-servicebus-trigger-endpoint/.prettierrc ================================================ { "semi": true, "printWidth": 100, "singleQuote": true, "useTabs": false, "tabWidth": 4, "bracketSpacing": true, "trailingComma": "none", "endOfLine": "lf" } ================================================ FILE: azure-node-typescript-servicebus-trigger-endpoint/README.md ================================================ # Create and Deploy Azure Function using Service Bus Queue as a trigger event. This example demonstrates how to create and deploy an Azure Function which has Service Bus Queue as its trigger event using Serverless Framework. ## Use-cases - This app will create and deploy an Azure Function which would be triggered when a message would arrive in a Service Bus Queue. - This app also exposes an http endpoint to send a sample message to service bus queue. ## How it works The serverless.yml would define an Azure Function handler with its trigger event as Service Bus and by providing necessary details about the service bus - queue name and connection string. Whenever a message would arrive in defined service bus queue, an azure function would be invoked and sent message would be processed in its handler. To send a sample message on defined service bus queue, `serverless.yml` declares one sample http POST end point. This can be used to send a message on defined service bus queue. ## Setup #### 1. Install Project Dependencies `npm install` in this directory to download the modules from `package.json`. #### 2. To run the azure function locally using `serverlesss offline --stage dev` The `serverless offline --stage dev` command will let you try and test your azure function locally. Before running this command - 1. You need to create a service bus queue on Azure Portal and provide the connection string in `serverless.yml` as an environment variable and refer this environment variable name in "connection" hook. ```yml # in serverless.yml provider: environment: VARIABLE_SBUS_CONNECTION_STRING: 'Endpoint=sb://.......' .... .... .... events: - serviceBus: x-azure-settings: queueName: '' accessRights: manage connection: VARIABLE_SBUS_CONNECTION_STRING ``` 2. Define Service Bus details - Connection string and queuename- in `.env.dev file`. This connection string & queuename would be used in `serviceBusMessageSender.ts` file to send sample message to service bus queue. ```yml # in .env.dev file SERVICEBUS_CS='Endpoint=sb://.....' SERVICEBUS_QUEUE_NAME= ``` Once this is done, run the command `serverless offline --stage dev`. The console would show the output message which should look something like this - ```bash Application started. Press Ctrl+C to shut down. Functions: sendMessage: [POST] http://localhost:7071/api/v3/send sampleHandler: serviceBusTrigger For detailed output, run func with --verbose flag. [2020-10-30T07:54:23.803] Worker process started and initialized. ``` #### 3. To deploy the azure function using `serverless deploy --stage dev` 1. Provide the appropriate Service Bus details in `serverless.yml` & `.env.dev` file as mentioned in step #2. 2. Provide the desired azure `resourceGroup`, `subscriptionId`,`region` and `stage` values in `serverless.yml` to deploy the app on Azure. Once above steps are done, run the command `serverless deploy --env dev`. The console would show the output message which should look something like this - ```bash Serverless: Finished uploading blob Serverless: -> Function package uploaded successfully Serverless: Deployed serverless functions: Serverless: -> Function App not ready. Retry 0 of 30... Serverless: -> Function App not ready. Retry 1 of 30... Serverless: -> Function App not ready. Retry 2 of 30... Serverless: -> sendMessage: [POST] sls--dev-service-bus-trigger-example.azurewebsites.net/api/api/v3/send ``` #### 4. To test the sample end point and invoke the function - 1. Send a sample message on service bus using `../api/api/v3/send` end point: Send a POST request on `../api/api/v3/send` endpoint with following payload - ```bash { "id": 101, "name": "AnyName", "gender": "Male/Female", "age": 30 } ``` When the service bus receives the above sample message, it will invoke another azure function - `sampleHandler` and it shall print the above payload on console. The console would show the output message which should look something like this - ```bash [2020-10-30T08:20:07.998] Executed 'Functions.sendMessage' (Succeeded, Id=f08ed5e7-5bd2-4c7c-8054-e23cad3dbb82, Duration=317ms) [2020-10-30T08:20:11.001] Executing 'Functions.sampleHandler' (Reason='New ServiceBus message detected on 'myqueuename'.', Id=abbb78d1-a19c-4ea7-b8a7-3ae6e9c6e66d) [2020-10-30T08:20:11.005] Trigger Details: MessageId: 83840f43a8824791bc2c9624d68ea1c2, DeliveryCount: 2, EnqueuedTime: 10/30/2020 8:20:10 AM, LockedUntil: 10/30/2020 8:20:40 AM, SessionId: (null) [2020-10-30T08:20:11.023] [2020-10-30T13:50:11.022+05:30][INFO][src\controller\triggerFunctionController.ts]: Azure function has been trigged with message {"id":10,"name":"Kurshit","gender":"Male","age":27} in service bus [2020-10-30T08:20:11.026] Executed 'Functions.sampleHandler' (Succeeded, Id=abbb78d1-a19c-4ea7-b8a7-3ae6e9c6e66d, Duration=39ms) ``` ================================================ FILE: azure-node-typescript-servicebus-trigger-endpoint/host.json ================================================ { "version": "2.0", "extensionBundle": { "id": "Microsoft.Azure.Functions.ExtensionBundle", "version": "[1.*, 2.0.0)" }, "extensions": { "http": { "routePrefix": "" } } } ================================================ FILE: azure-node-typescript-servicebus-trigger-endpoint/local.settings.json ================================================ {"IsEncrypted":false,"Values":{"AzureWebJobsStorage":"UseDevelopmentStorage=true","FUNCTIONS_WORKER_RUNTIME":"node"}} ================================================ FILE: azure-node-typescript-servicebus-trigger-endpoint/package.json ================================================ { "name": "service-bus-trigger-example", "version": "1.0.0", "description": "Serverless application for asset model creation", "main": "handler.js", "scripts": { "prettier-check": "prettier --config .prettierrc ./src/**/*.ts --check", "prettier-format": "prettier --config .prettierrc ./src/**/*.ts --write", "lint": "eslint . --ext .ts", "lint-and-fix": "eslint . --ext .ts --fix" }, "dependencies": { "@azure/functions": "^1.0.3", "@azure/service-bus": "^1.1.10", "azure-functions-core-tools": "^2.7.1846", "serverless-azure-functions": "^2.0.9", "serverless-dotenv-plugin": "^2.1.1", "serverless-plugin-resource-tagging": "^1.0.11" }, "devDependencies": { "@types/node": "^10.17.40", "@typescript-eslint/eslint-plugin": "^3.6.0", "@typescript-eslint/parser": "^3.6.0", "eslint": "^7.4.0", "eslint-config-prettier": "^6.11.0", "eslint-plugin-prettier": "^3.1.4", "ts-loader": "^5.3.3", "install": "0.13.0", "prettier": "^2.0.5", "serverless-offline": "^6.4.0", "serverless-webpack": "^5.2.0", "ts-jest": "^26.1.1", "ts-node": "^8.10.2", "typescript": "^3.2.4", "webpack": "^4.29.0", "webpack-node-externals": "^1.7.2", "winston": "^3.3.3" }, "author": "The serverless webpack authors (https://github.com/elastic-coders/serverless-webpack)", "license": "MIT" } ================================================ FILE: azure-node-typescript-servicebus-trigger-endpoint/serverless.yml ================================================ service: name: service-bus-trigger-example plugins: - serverless-azure-functions - serverless-webpack - serverless-dotenv-plugin provider: name: azure stage: ${opt:env} runtime: nodejs10 region: '' type: premium subscriptionId: '' environment: SERVICE_BUS_CONNECTION_STRING: '' functions: sendMessage: handler: src/controller/messageSenderController.sendMessage events: - http: x-azure-settings: name: req methods: - post route: api/v3/send authLevel: anonymous sampleHandler: handler: src/controller/triggerFunctionController.sampleHandler events: - serviceBus: x-azure-settings: name: req queueName: myqueuename accessRights: manage connection: SERVICE_BUS_CONNECTION_STRING ================================================ FILE: azure-node-typescript-servicebus-trigger-endpoint/src/config/loggerConfig.ts ================================================ import * as winston from 'winston'; import * as path from 'path'; const rootLogger = winston.createLogger({ level: process.env.LOG_LEVEL, format: winston.format.combine( winston.format.timestamp({ format: 'YYYY-MM-DDTHH:mm:ss.SSSZ' }), winston.format.splat(), winston.format.printf( (info) => `[${info.timestamp}][${info.level.toLocaleUpperCase('en-US')}][${info.source}]: ${ info.message }` ) ), transports: [new winston.transports.Console({ level: process.env.LOG_LEVEL })] }); const logger = (name: string): winston.Logger => { return rootLogger.child({ source: path.relative(process.cwd(), name) }); }; export default logger; ================================================ FILE: azure-node-typescript-servicebus-trigger-endpoint/src/controller/messageSenderController.ts ================================================ import { AzureFunction, Context, HttpRequest } from '@azure/functions'; import { SampleModel } from '../model/sampleModel'; import log from '../config/loggerConfig'; import servcieBusMessageSender from '../service/serviceBusMessageSender'; const logger = log(__filename); export const sendMessage: AzureFunction = async ( context: Context, event: HttpRequest ): Promise => { try { logger.info(`Request arrived in controller with body ${event.rawBody}`); const message: SampleModel = JSON.parse(event.rawBody); servcieBusMessageSender.send(message); context.res = { statusCode: 201 }; } catch (err) { logger.error(`Error occured while sending message to service bus`) context.res = { statusCode: err.statusCode }; } }; ================================================ FILE: azure-node-typescript-servicebus-trigger-endpoint/src/controller/triggerFunctionController.ts ================================================ import { Context } from '@azure/functions'; import { SampleModel } from '../model/sampleModel'; import log from '../config/loggerConfig'; const logger = log(__filename); export const sampleHandler = async (context: Context, message: SampleModel): Promise => { logger.info( `Azure function has been trigged with message ${JSON.stringify(message)} in service bus` ); context.res = { statusCode: 200 }; }; ================================================ FILE: azure-node-typescript-servicebus-trigger-endpoint/src/model/sampleModel.ts ================================================ export class SampleModel { id: string; name: string; gender: string; age: string; } ================================================ FILE: azure-node-typescript-servicebus-trigger-endpoint/src/service/serviceBusMessageSender.ts ================================================ import { ServiceBusClient } from '@azure/service-bus'; import { SampleModel } from '../model/sampleModel'; import log from '../config/loggerConfig'; const logger = log(__filename); const connectionString = process.env.SERVICEBUS_CS; const queueName = process.env.SERVICEBUS_QUEUE_NAME; const sbClient = ServiceBusClient.createFromConnectionString(connectionString); const queueClient = sbClient.createQueueClient(queueName); const sender = queueClient.createSender(); class ServiceBusMessageSender { public async send(message: SampleModel): Promise { try { sender.send({ body: message }); logger.debug(`Message has been sent successfuly. Message is ${message}`); } catch (err) { logger.error(`Exception occurred during sending notification. Exception is ${err}`); } } } export const serviceBusMessageSender = new ServiceBusMessageSender(); export default serviceBusMessageSender; ================================================ FILE: azure-node-typescript-servicebus-trigger-endpoint/tsconfig.json ================================================ { "compilerOptions": { "lib": ["es2017"], "removeComments": true, "moduleResolution": "node", "noUnusedLocals": true, "noUnusedParameters": true, "sourceMap": true, "target": "es2017", "outDir": "dist", "resolveJsonModule": true, "experimentalDecorators": true, "allowSyntheticDefaultImports" : true }, "include": ["./**/*.ts"], "exclude": [ "node_modules", "node_modules/**/*", ".serverless/**/*", ".webpack/**/*", "_warmup/**/*", ".vscode/**/*" ] } ================================================ FILE: azure-node-typescript-servicebus-trigger-endpoint/webpack.config.js ================================================ const path = require('path'); const slsw = require('serverless-webpack'); const nodeExternals = require('webpack-node-externals'); module.exports = { context: __dirname, mode: slsw.lib.webpack.isLocal ? 'development' : 'production', entry: slsw.lib.entries, devtool: slsw.lib.webpack.isLocal ? 'cheap-module-eval-source-map' : 'source-map', resolve: { extensions: ['.mjs', '.json', '.ts'], symlinks: false, cacheWithContext: false, }, output: { libraryTarget: 'commonjs', path: path.join(__dirname, '.webpack'), filename: '[name].js', }, target: 'node', externals: [nodeExternals()], module: { rules: [ // all files with a `.ts` or `.tsx` extension will be handled by `ts-loader` { test: /\.(tsx?)$/, loader: 'ts-loader', exclude: [ [ path.resolve(__dirname, 'node_modules'), path.resolve(__dirname, '.serverless'), path.resolve(__dirname, '.webpack'), ], ], options: { transpileOnly: true, experimentalWatchApi: true, }, }, ], }, plugins: [], node: { __filename: true } }; ================================================ FILE: check-if-readme-is-up-to-date.sh ================================================ #!/usr/bin/env bash echo "Checking if README.md has been updated..." git update-index --refresh if ! git diff-index --quiet HEAD -- README.md; then echo "README.md needs to be regenerated!" echo echo "Please run:" echo " $ npm run docs" exit 1 fi exit 0 ================================================ FILE: compose-multiframework/README.md ================================================ # Serverless Framework Compose: Multiframework Deployment Deploying multiple services in a monorepository is a common pattern in larger teams. Serverless Framework Compose simplifies the deployment and orchestration of these services by offering: 1. Parallel deployment of multiple services 2. Ordered deployment of services 3. Support for deploying different types of services (e.g., Traditional, SAM, CloudFormation) together 4. Sharing outputs between services 5. Running commands across multiple services In this example, we demonstrate how to use Serverless Compose to deploy three types of services together: 1. AWS CloudFormation Service: Deploys shared resources with outputs that are referenced by the other services. 2. Serverless Framework Traditional Service 3. AWS SAM Template Service The AWS CloudFormation service is deployed first to create shared resources, followed by the parallel deployment of the Traditional and AWS SAM services. This example also illustrates how to use Serverless Variables with Serverless Compose for organizing and structuring your application, as well as managing different stages. For more information about Serverless Compose, please see the [Serverless Compose docs](https://www.serverless.com/framework/docs/guides/compose) For more information about using AWS SAM and or AWS CloudFormation templates with the Serverless Framework, please see the [AWS SAM/CFN docs](https://www.serverless.com/framework/docs/guides/sam) For more information about Serverless Variables, please see the [Serverless Variables docs](https://www.serverless.com/framework/docs/guides/variables) ================================================ FILE: compose-multiframework/cloudformation/template.yml ================================================ # This is a shared CloudFormation service that is being referenced by the traditional and SAM services. # This is a good place to put shared resources like DynamoDB tables, S3 buckets, etc. # that will be used by other services. AWSTemplateFormatVersion: "2010-09-09" Resources: SharedTable: Type: "AWS::DynamoDB::Table" Properties: # We are using the parameter passed down from Serverless Compose, and the current stage. # To construct the final table name. TableName: ${param:tableNamePrefix}-${sls:stage} AttributeDefinitions: - AttributeName: "Id" AttributeType: "S" KeySchema: - AttributeName: "Id" KeyType: "HASH" ProvisionedThroughput: ReadCapacityUnits: 5 WriteCapacityUnits: 5 # We are outputting the table name so that other services can reference it. Outputs: TableName: Value: !Ref SharedTable ================================================ FILE: compose-multiframework/sam/handler.js ================================================ exports.handler = async (event) => { return { service: "sam", tableName: process.env.TABLE_NAME, domain: process.env.DOMAIN, }; }; ================================================ FILE: compose-multiframework/sam/samconfig.toml ================================================ version = 0.1 [default] [default.global.parameters] stack_name = "sam-compose-service-example" ================================================ FILE: compose-multiframework/sam/template.yml ================================================ AWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 Globals: Function: Timeout: 3 MemorySize: 128 Resources: HelloWorldFunction: Type: AWS::Serverless::Function Properties: Handler: handler.handler Runtime: nodejs20.x Environment: Variables: # We are using the output of the shared resources CloudFormation template # that is passed down from Serverless Compose, and the staged param "domain". TABLE_NAME: ${param:tableName} DOMAIN: ${param:domain} Architectures: - x86_64 Events: Api: Type: HttpApi Properties: Path: / Method: GET Outputs: Endpoint: Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com" ================================================ FILE: compose-multiframework/serverless-compose.yml ================================================ # Serverless Framework can deploy multiple types of templates: # 1) Traditional Serverless Framework templates # 2) AWS Cloudformation Templates # 3) AWS SAM Templates. # This is useful if your organization is using these different templates and wants one tool to standardize around. # Here, we use Serverless Framework Compose (serverless.com/framework/docs/guides/compose) to deploy three different template types in a single deploy command. # Deployments happen in parallel by default. You can also share outputs from one Service to another, across template types. # In this case, Compose will auto-determine the correct order to deploy each Service. # Example stage-specific parameters you might need in your services. stages: default: params: domain: "dev-api.acmeinc.com" # This is the value of the "domain" param in the dev stage prod: params: domain: "prod-api.acmeinc.com" # This is the value of the "domain" param in the prod stage services: # Framework: AWS CloudFormation # The service name here will be used as the underlying cloudformation stack name. # Make sure it’s unique so it doesn’t collide, and accidently update another stack. # If you change it, make sure you update the references in other services. shared-resources-example: path: cloudformation params: # We are passing a simple string parameter to the CloudFormation template. # If you open the template file, you will see that we reference it with ${param:tableNamePrefix} tableNamePrefix: shared-table # Framework: Serverless Framework Traditional traditional: path: traditional params: # We are passing a reference to the shared CloudFormation stack output to the traditional service. # The shared CloudFormation stack is deployed first, and the output is passed to this service. tableName: ${shared-resources-example.TableName} # Framework: AWS SAM sam: path: sam params: # We do the same here, passing the shared CloudFormation stack output to the SAM service. # Both the traditional and SAM services will be deployed in parallel. # As they both depend on the same shared service. tableName: ${shared-resources-example.TableName} ================================================ FILE: compose-multiframework/traditional/handler.js ================================================ exports.handler = async (event) => { return { service: "traditional", tableName: process.env.TABLE_NAME, domain: process.env.DOMAIN, }; }; ================================================ FILE: compose-multiframework/traditional/serverless.yml ================================================ service: traditional provider: name: aws runtime: nodejs20.x functions: hello: handler: handler.handler events: - httpApi: path: / method: get environment: # We are using the output of the shared resources CloudFormation template # that is passed down from Serverless Compose, and the staged param "domain". TABLE_NAME: ${param:tableName} DOMAIN: ${param:domain} ================================================ FILE: examples.json ================================================ [ { "title": "Dot Net REST API with DynamoDB", "name": "aws-dotnet-rest-api-with-dynamodb", "description": "Setup a REST API w/ DynamoDB using Dot Net Core 2.1", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-dotnet-rest-api-with-dynamodb", "framework": "v1", "language": "csharp", "platform": "aws", "authorLink": "https://github.com/samueleresca", "authorName": "Samuele Resca", "authorAvatar": "https://avatars0.githubusercontent.com/u/8921095?v=4&s=140" }, { "title": "AWS FFmepg Layer", "name": "aws-ffmpeg-layer", "description": "AWS FFmepg Layer & a service using it to create GIFs", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-ffmpeg-layer", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/tdhopper", "authorName": "Timothy Hopper", "authorAvatar": "https://avatars0.githubusercontent.com/u/611122?v=4&s=140" }, { "title": "AWS Golang Auth", "name": "aws-golang-auth-examples", "description": "This example shows you how to setup auth in front of a AWS Lambda function", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-golang-auth-examples", "framework": "v1", "language": "go", "platform": "aws", "authorLink": "https://github.com/srbry", "authorName": "srbry", "authorAvatar": "https://avatars0.githubusercontent.com/u/16936753?v=4&s=140" }, { "title": "DynamoDB Stream To Elasticsearch", "name": "aws-golang-dynamo-stream-to-elasticsearch", "description": "Stream data from DynamoDB to Elasticsearch", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-golang-dynamo-stream-to-elasticsearch", "framework": "v1", "language": "go", "platform": "aws", "authorLink": "https://github.com/jalie", "authorName": "Jan Liesendahl", "authorAvatar": "https://avatars0.githubusercontent.com/u/548657?v=4&s=140" }, { "title": "Google map api", "name": "aws-golang-googlemap", "description": "Serverless example using golang to hit google map api", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-golang-googlemap", "framework": "v1", "language": "go", "platform": "aws", "authorLink": "https://github.com/pramonow", "authorName": "Pramono Winata", "authorAvatar": "https://avatars0.githubusercontent.com/u/28787057?v=4&s=140" }, { "title": "HTTP GET and POST", "name": "aws-golang-http-get-post", "description": "Boilerplate code for Golang with GET and POST example", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-golang-http-get-post", "framework": "v1", "language": "go", "platform": "aws", "authorLink": "https://github.com/pramonow", "authorName": "Pramono Winata", "authorAvatar": "https://avatars0.githubusercontent.com/u/28787057?v=4&s=140" }, { "title": "Aws golang rest api with dynamodb", "name": "aws-golang-rest-api-with-dynamodb", "description": "Boilerplate code for Golang CRUD Operations", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-golang-rest-api-with-dynamodb", "framework": "v1", "language": "go", "platform": "aws", "authorLink": "https://github.com/gsweene2", "authorName": "Garrett Sweeney", "authorAvatar": "https://avatars.githubusercontent.com/u/14845943?s=400&u=6d79e8f042cd3d30643ba4598515cae24be69ec3&v=4" }, { "title": "AWS S3 Bucket Replicator in Golang", "name": "aws-golang-s3-file-replicator", "description": "Boilerplate code for Golang with S3 object create event and replicator example", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-golang-s3-file-replicator", "framework": "v1", "language": "go", "platform": "aws", "authorLink": "https://github.com/p0n2", "authorName": "p0n2", "authorAvatar": "https://avatars3.githubusercontent.com/u/59630164" }, { "title": "TODO", "name": "aws-golang-simple-http-endpoint", "description": "This example demonstrates how to setup a simple HTTP endpoint in Go.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-golang-simple-http-endpoint", "framework": "v1", "language": "go", "platform": "aws", "authorLink": "https://github.com/sebito91", "authorName": "Sebastian Borza", "authorAvatar": "https://avatars0.githubusercontent.com/u/3159454?v=4&s=140" }, { "title": "TODO", "name": "aws-golang-stream-kinesis-to-elasticsearch", "description": "This example demonstrates how to stream kinesis information into elasticsearch in a golang runtime", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-golang-stream-kinesis-to-elasticsearch", "framework": "v1", "language": "go", "platform": "aws", "authorLink": "https://github.com/sebito91", "authorName": "Sebastian Borza", "authorAvatar": "https://avatars0.githubusercontent.com/u/3159454?v=4&s=140" }, { "title": "AWS Simple HTTP Endpoint example in Java", "name": "aws-java-simple-http-endpoint", "description": "This example demonstrates how to setup a simple HTTP GET endpoint using Java. Once you ping it, it will reply with the current time.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-java-simple-http-endpoint", "framework": "v1", "language": "java", "platform": "aws", "authorLink": "https://github.com/DoWhileGeek", "authorName": "Joeseph Rodrigues", "authorAvatar": "https://avatars3.githubusercontent.com/u/1767769?v=4&s=140" }, { "title": "TODO", "name": "aws-multiple-runtime", "description": "This example demonstrates how you can run multiple runtimes in AWS Lambda.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-multiple-runtime", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/christophgysin", "authorName": "Christoph Gysin", "authorAvatar": "https://avatars0.githubusercontent.com/u/527924?v=4&s=140" }, { "title": "AWS Serverless Alexa Skill example in NodeJS", "name": "aws-node-alexa-skill", "description": "This example demonstrates how to setup your own Alexa skill using AWS Lambdas.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-alexa-skill", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/rupakg", "authorName": "Rupak Ganguly", "authorAvatar": "https://avatars0.githubusercontent.com/u/8188?v=4&s=140" }, { "title": "API Gateway Authorizer Function for Auth0 or AWS Cognito using RS256 JSON Web Key Sets tokens.", "name": "aws-node-auth0-cognito-custom-authorizers-api", "description": "Authorize your API Gateway with either Auth0 or Cognito JWKS RS256 tokens.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-auth0-cognito-custom-authorizers-api", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/shahzeb1", "authorName": "Shahzeb K.", "authorAvatar": "https://avatars2.githubusercontent.com/u/1383831?v=4&s=140" }, { "title": "AWS API Gateway Custom Authorizer Function with Auth0 example in NodeJS", "name": "aws-node-auth0-custom-authorizers-api", "description": "This is an example of how to protect API endpoints with Auth0, JSON Web Tokens (jwt) and a custom authorizer lambda function.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-auth0-custom-authorizers-api", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/erezrokah", "authorName": "Erez Rokah", "authorAvatar": "https://avatars0.githubusercontent.com/u/26760571?v=4&s=140" }, { "title": "Dynamic Image Resizing API", "name": "aws-node-dynamic-image-resizer", "description": "This example shows you how to setup a dynamic image resizer API", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-dynamic-image-resizer", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/sebito91", "authorName": "Sebastian Borza", "authorAvatar": "https://avatars0.githubusercontent.com/u/3159454?v=4&s=140" }, { "title": "TODO", "name": "aws-node-dynamodb-backup", "description": "This examples shows your how to create a backup of your DynamoDB table to S3.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-dynamodb-backup", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/kaihendry", "authorName": "Kai Hendry", "authorAvatar": "https://avatars3.githubusercontent.com/u/765871?v=4&s=140" }, { "title": "AWS Storing Encrypted Secrets example in NodeJS", "name": "aws-node-env-variables-encrypted-in-a-file", "description": "This example demonstrates how to store secrets like API keys encrypted in your repository while providing them as environment variables to your AWS Lambda functions.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-env-variables-encrypted-in-a-file", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/rupakg", "authorName": "Rupak Ganguly", "authorAvatar": "https://avatars0.githubusercontent.com/u/8188?v=4&s=140" }, { "title": "AWS Serverless Environment Variables Usage example in NodeJS", "name": "aws-node-env-variables", "description": "This example demonstrates how to use environment variables for AWS Lambdas.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-env-variables", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/rupakg", "authorName": "Rupak Ganguly", "authorAvatar": "https://avatars0.githubusercontent.com/u/8188?v=4&s=140" }, { "title": "Node Express API on AWS", "name": "aws-node-express-api", "description": "This template demonstrates how to develop and deploy a simple Node Express API running on AWS Lambda using the Serverless Framework.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-express-api", "framework": "v4", "language": "node", "platform": "aws", "authorLink": "https://github.com/serverless", "authorName": "Serverless, inc.", "authorAvatar": "https://avatars1.githubusercontent.com/u/13742415?s=200&v=4", "priority": 2 }, { "title": "Node Express API service backed by DynamoDB on AWS", "name": "aws-node-express-dynamodb-api", "description": "This template demonstrates how to develop and deploy a simple Node Express API service backed by DynamoDB running on AWS Lambda using the Serverless Framework.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-express-dynamodb-api", "framework": "v4", "language": "node", "platform": "aws", "authorLink": "https://github.com/serverless", "authorName": "Serverless, inc.", "authorAvatar": "https://avatars1.githubusercontent.com/u/13742415?s=200&v=4", "priority": 3 }, { "title": "AWS Fetch image from URL and upload to S3 example in NodeJS", "name": "aws-node-fetch-file-and-store-in-s3", "description": "This example display how to fetch an image from remote source (URL) and then upload this image to a S3 bucket.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-fetch-file-and-store-in-s3", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/ScottBrenner", "authorName": "Scott Brenner", "authorAvatar": "https://avatars2.githubusercontent.com/u/416477?v=4&s=140" }, { "title": "Serverless Email Sign Up Form", "name": "aws-node-fullstack", "description": "This example demonstrates how to deploy a Fullstack serverless application", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-fullstack", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/trilom", "authorName": "Bryan Killian", "authorAvatar": "https://avatars0.githubusercontent.com/u/7476973?v=4&s=140" }, { "title": "AWS Function compiled with Babel example in NodeJS", "name": "aws-node-function-compiled-with-babel", "description": "This example demonstrates how to compile your JavaScript code with Babel. In order to do so the 'serverless-babel-plugin' is leveraged.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-function-compiled-with-babel", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/rupakg", "authorName": "Rupak Ganguly", "authorAvatar": "https://avatars0.githubusercontent.com/u/8188?v=4&s=140" }, { "title": "Serverless Github Check", "name": "aws-node-github-check", "description": "The idea is to validate that all Pull Requests are related to a specific trello card.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-github-check", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/Fortiz2305", "authorName": "Francisco Ortiz", "authorAvatar": "https://avatars0.githubusercontent.com/u/4025821?v=4&s=140" }, { "title": "AWS Serverless Github Webhook Listener example in NodeJS", "name": "aws-node-github-webhook-listener", "description": "This service will listen to github webhooks fired by a given repository.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-github-webhook-listener", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/adambrgmn", "authorName": "Adam Bergman", "authorAvatar": "https://avatars1.githubusercontent.com/u/13746650?v=4&s=140" }, { "title": "A Simple Serverless GraphQL API for MySQL, Postgres and Aurora", "name": "aws-node-graphql-and-rds", "description": "This is an example project that uses 3 RDS databases to illustrate the differences between using each of them", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-graphql-and-rds", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/chief-wizard", "authorName": "Chief Wizard", "authorAvatar": "https://avatars3.githubusercontent.com/u/40777040?v=4&s=140" }, { "title": "GraphQL query endpoint in NodeJS on AWS with DynamoDB", "name": "aws-node-graphql-api-with-dynamodb", "description": "A single-module GraphQL endpoint with query and mutation functionality.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-graphql-api-with-dynamodb", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/gismoranas", "authorName": "Gismo Ranas", "authorAvatar": "https://avatars0.githubusercontent.com/u/5903107?v=4&s=140" }, { "title": "Node.js AWS Lambda connecting to Heroku Postgres", "name": "aws-node-heroku-postgres", "description": "Shows how to connect AWS Lambda to Heroku Postgres. Uses an api:release Heroku webhook and the Heroku API to handle automatic Heroku Postgres credential rotation.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-heroku-postgres", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/welkie", "authorName": "Matt Welke", "authorAvatar": "https://avatars0.githubusercontent.com/u/7719209" }, { "title": "AWS Serverless IoT Event example in NodeJS", "name": "aws-node-iot-event", "description": "This example demonstrates how to setup a AWS IoT Rule to send events to a Lambda function.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-iot-event", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/rupakg", "authorName": "Rupak Ganguly", "authorAvatar": "https://avatars0.githubusercontent.com/u/8188?v=4&s=140" }, { "title": "Node.js AWS Lambda connecting to MongoDB Atlas", "name": "aws-node-mongodb-atlas", "description": "Shows how to connect AWS Lambda to MongoDB Atlas.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-mongodb-atlas", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/welkie", "authorName": "Matt Welke", "authorAvatar": "https://avatars0.githubusercontent.com/u/7719209" }, { "title": "TODO", "name": "aws-node-oauth-dropbox-api", "description": "Connect to Dropbox's API using AWS Lambda.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-oauth-dropbox-api", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/serverless", "authorName": "Jay Deshmukh", "authorAvatar": "https://avatars0.githubusercontent.com/u/38460988?v=4&s=140" }, { "title": "Running Puppeteer on AWS Lambda", "name": "aws-node-puppeteer", "description": "This example shows you how to run Puppeteer on AWS Lambda", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-puppeteer", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/emaildano", "authorName": "Daniel Olson", "authorAvatar": "https://avatars3.githubusercontent.com/u/1872327?v=4&s=140" }, { "title": "AWS Recursive Lambda function Invocation example in NodeJS", "name": "aws-node-recursive-function", "description": "This is an example of a function that will recursively call itself.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-recursive-function", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/rupakg", "authorName": "Rupak Ganguly", "authorAvatar": "https://avatars0.githubusercontent.com/u/8188?v=4&s=140" }, { "title": "AWS Analyse Image from S3 with Amazon Rekognition example in NodeJS", "name": "aws-node-rekognition-analysis-s3-image", "description": "This example shows how to analyze an image in an S3 bucket with Amazon Rekognition and return a list of labels.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-rekognition-analysis-s3-image", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/ScottBrenner", "authorName": "Scott Brenner", "authorAvatar": "https://avatars2.githubusercontent.com/u/416477?v=4&s=140" }, { "title": "TODO", "name": "aws-node-rest-api-mongodb", "description": "This example demonstrate how to use MongoDB with AWS and Serverless.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-rest-api-mongodb", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/lucianopf", "authorName": "Luciano Pellacani Franca", "authorAvatar": "https://avatars2.githubusercontent.com/u/8251208?v=4&s=140" }, { "title": "AWS Simple HTTP Endpoint example in NodeJS with Typescript", "name": "aws-node-rest-api-typescript-simple", "description": "This template demonstrates how to make a simple REST API with Node.js and Typescript running on AWS Lambda and API Gateway using the Serverless Framework v1.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-rest-api-typescript-simple", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/serverless", "authorName": "Serverless, inc.", "authorAvatar": "https://avatars1.githubusercontent.com/u/13742415?s=200&v=4" }, { "title": "Serverless Nodejs Rest API with TypeScript And MongoDB Atlas", "name": "aws-node-rest-api-typescript", "description": "This is simple REST API example for AWS Lambda By Serverless framwork with TypeScript and MongoDB Atlas.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-rest-api-typescript", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/Q-Angelo", "authorName": "May Jun", "authorAvatar": "https://avatars0.githubusercontent.com/u/17956058?s=460&u=f3acebabd097e6e93d5be5a8366b980fea5b15aa&v=4" }, { "title": "AWS Serverless REST API with DynamoDB and offline support example in NodeJS", "name": "aws-node-rest-api-with-dynamodb-and-offline", "description": "This example demonstrates how to run a service locally, using the 'serverless-offline' plugin. It provides a REST API to manage Todos stored in DynamoDB.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-rest-api-with-dynamodb-and-offline", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/adambrgmn", "authorName": "Adam Bergman", "authorAvatar": "https://avatars1.githubusercontent.com/u/13746650?v=4&s=140" }, { "title": "AWS Serverless REST API example in NodeJS", "name": "aws-node-rest-api-with-dynamodb", "description": "This example demonstrates how to setup a RESTful Web Service allowing you to create, list, get, update and delete Todos. DynamoDB is used to store the data.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-rest-api-with-dynamodb", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/ozbillwang", "authorName": "Bill Wang", "authorAvatar": "https://avatars3.githubusercontent.com/u/8954908?v=4&s=140" }, { "title": "AWS Simple HTTP Endpoint example in NodeJS", "name": "aws-node-rest-api", "description": "This template demonstrates how to make a simple REST API with Node.js running on AWS Lambda and API Gateway using the traditional Serverless Framework.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-rest-api", "framework": "v2", "language": "node", "platform": "aws", "authorLink": "https://github.com/serverless", "authorName": "Serverless, inc.", "authorAvatar": "https://avatars1.githubusercontent.com/u/13742415?s=200&v=4" }, { "title": "AWS Simple HTTP Endpoint example in NodeJS", "name": "aws-node-http-api", "description": "This template demonstrates how to make a simple HTTP API with Node.js running on AWS Lambda and API Gateway using the Serverless Framework.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-http-api", "framework": "v4", "language": "node", "platform": "aws", "authorLink": "https://github.com/serverless", "authorName": "Serverless, inc.", "authorAvatar": "https://avatars1.githubusercontent.com/u/13742415?s=200&v=4", "priority": 1 }, { "title": "AWS S3 File Replicator", "name": "aws-node-s3-file-replicator", "description": "This example creates 2 AWS S3 buckets and copies files in one bucket to the other", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-s3-file-replicator", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/ac360", "authorName": "Austen Collins", "authorAvatar": "https://avatars3.githubusercontent.com/u/2752551?v=4&s=140" }, { "title": "AWS Node Scheduled Cron example in NodeJS", "name": "aws-node-scheduled-cron", "description": "This is an example of creating a function that runs as a cron job using the serverless ''schedule'' event.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-scheduled-cron", "framework": "v4", "language": "node", "platform": "aws", "authorLink": "https://github.com/0dj0bz", "authorName": "Rob Abbott", "authorAvatar": "https://avatars3.githubusercontent.com/u/5679763?v=4&s=140", "priority": 4 }, { "title": "AWS Node Scheduled Weather example in NodeJS", "name": "aws-node-scheduled-weather", "description": "This is an example of creating a function that runs as a cron job using the serverless 'schedule' event. It retrieves weather information at 10am (UTC) and emails it to a predefined recipient.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-scheduled-weather", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/rupakg", "authorName": "Rupak Ganguly", "authorAvatar": "https://avatars0.githubusercontent.com/u/8188?v=4&s=140" }, { "title": "AWS Serving Dynamic HTML via API Gateway example in NodeJS", "name": "aws-node-serve-dynamic-html-via-http-endpoint", "description": "This example illustrates how to hookup an API Gateway endpoint to a Lambda function to render HTML on a GET request.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-serve-dynamic-html-via-http-endpoint", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/slate71", "authorName": "Lukas Andersen", "authorAvatar": "https://avatars0.githubusercontent.com/u/2078561?v=4&s=140" }, { "title": "The Serverless Gong", "name": "aws-node-serverless-gong", "description": "A serverless gong with GitHub and Slack webhooks", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-serverless-gong", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/bildungsroman", "authorName": "Anna Spysz", "authorAvatar": "https://avatars3.githubusercontent.com/u/5382821?v=4&s=140" }, { "title": "AWS SES receive emails and process body", "name": "aws-node-ses-receive-email-body", "description": "This example shows how to process receiving emails, and have S3 trigger a lambda function.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-ses-receive-email-body", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/aheissenberger", "authorName": "Andreas Heissenberger", "authorAvatar": "https://avatars2.githubusercontent.com/u/200095?v=4&s=140" }, { "title": "AWS SES receive an email, trigger a lambda function to process header.", "name": "aws-node-ses-receive-email-header", "description": "This example shows how to process receiving email header, and trigger a lambda function.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-ses-receive-email-header", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/aheissenberger", "authorName": "Andreas Heissenberger", "authorAvatar": "https://avatars0.githubusercontent.com/u/200095?v=4&s=140" }, { "title": "Shared AWS API Gateway with multiple Node Lambdas", "name": "aws-node-shared-gateway", "description": "A sample of implementing shared API gateway with multiple Node Lambdas", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-shared-gateway", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/allanchua101", "authorName": "Allan Chua", "authorAvatar": "https://avatars3.githubusercontent.com/u/26626798?s=460&v=4" }, { "title": "AWS Node Signed Uploads", "name": "aws-node-signed-uploads", "description": "The approach implemented in this service is useful when you want to use Amazon API Gateway and you want to solve the 10MB payload limit", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-signed-uploads", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/kalinchernev", "authorName": "Kalin Chernev", "authorAvatar": "https://avatars3.githubusercontent.com/u/1923476?v=4&s=140" }, { "title": "AWS Simple HTTP Endpoint example in NodeJS", "name": "aws-node-simple-http-endpoint", "description": "This example demonstrates how to setup a simple HTTP GET endpoint. Once you ping it, it will reply with the current time.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-simple-http-endpoint", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/rupakg", "authorName": "Rupak Ganguly", "authorAvatar": "https://avatars0.githubusercontent.com/u/8188?v=4&s=140" }, { "title": "Simple AWS Transcribe example in NodeJS", "name": "aws-node-simple-transcribe-s3", "description": "This example demonstrates how to setup a lambda function to transcribe your audio file (.wav format) into a text transcription. The lambda will be triggered whenever a new audio file is uploaded to S3 and the transcription (JSON format) will be saved to a S3 bucket.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-simple-transcribe-s3", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/t49tran", "authorName": "Duong Tran", "authorAvatar": "https://avatars0.githubusercontent.com/u/2223362?v=4&s=140" }, { "title": "AWS Single Page Application example in NodeJS", "name": "aws-node-single-page-app-via-cloudfront", "description": "This example demonstrates how to setup a Single Page Application.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-single-page-app-via-cloudfront", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/erezrokah", "authorName": "Erez Rokah", "authorAvatar": "https://avatars0.githubusercontent.com/u/26760571?v=4&s=140" }, { "title": "Node SQS Producer Consumer on AWS", "name": "aws-node-sqs-worker", "description": "This template demonstrates how to develop and deploy a simple SQS-based producer-consumer service running on AWS Lambda using the traditional Serverless Framework.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-sqs-worker", "framework": "v2", "language": "node", "platform": "aws", "authorLink": "https://github.com/serverless", "authorName": "Serverless, inc.", "authorAvatar": "https://avatars1.githubusercontent.com/u/13742415?s=200&v=4" }, { "title": "AWS Stripe Integration example in NodeJS", "name": "aws-node-stripe-integration", "description": "This example for Stripe integration using AWS Lambda and API Gateway.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-stripe-integration", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/adambrgmn", "authorName": "Adam Bergman", "authorAvatar": "https://avatars1.githubusercontent.com/u/13746650?v=4&s=140" }, { "title": "Simple Telegram bot", "name": "aws-node-telegram-echo-bot", "description": "This is a simple echo bot on Telegram.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-telegram-echo-bot", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/hrchu", "authorName": "Peter Chu", "authorAvatar": "https://avatars2.githubusercontent.com/u/3183314?s=460&v=4" }, { "title": "AWS Data Processing example in NodeJS", "name": "aws-node-text-analysis-via-sns-post-processing", "description": "This example demonstrates how to setup a simple data processing pipeline.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-text-analysis-via-sns-post-processing", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/adambrgmn", "authorName": "Adam Bergman", "authorAvatar": "https://avatars1.githubusercontent.com/u/13746650?v=4&s=140" }, { "title": "AWS Send SMS Message with Twilio example in NodeJS", "name": "aws-node-twilio-send-text-message", "description": "This example demonstrates how to send SMS messages with the Twilio SDK and AWS lambda.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-twilio-send-text-message", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/darrenhgc", "authorName": "Darren Holland", "authorAvatar": "https://avatars0.githubusercontent.com/u/28113106?v=4&s=140" }, { "title": "Joke Twitter Bot", "name": "aws-node-twitter-joke-bot", "description": "Twitter bot that will periodically tweet out a joke obtained from a joke API.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-twitter-joke-bot", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/Fixy250185", "authorName": "Craig Sweaton", "authorAvatar": "https://avatars0.githubusercontent.com/u/26969518?v=4&s=140" }, { "title": "AWS Apollo Lambda (NodeJS & Typescript)", "name": "aws-node-typescript-apollo-lambda", "description": "This example provides a setup for a Lambda Graphql API with apollo", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-typescript-apollo-lambda", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/jmpfrazao", "authorName": "Miguel Frazao", "authorAvatar": "https://avatars3.githubusercontent.com/u/28927258?s=460&v=4" }, { "title": "AWS Kinesis Data Streams Example (NodeJS & Typescript)", "name": "aws-node-typescript-kinesis", "description": "Produce and Consume data on a Kinesis Data Stream with Typescript.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-typescript-kinesis", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/billkidwell", "authorName": "Bill Kidwell", "authorAvatar": "https://avatars0.githubusercontent.com/u/46457910?s=460&u=7c6d271ea7527f05e6c053cab571d32ffb3dbd38&v=4" }, { "title": "AWS Nest application example (NodeJS & Typescript)", "name": "aws-node-typescript-nest", "description": "This example demonstrates how to setup a simple [Nest](https://github.com/nestjs/nest) application.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-typescript-nest", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/neilime", "authorName": "Emilien Escalle", "authorAvatar": "https://avatars3.githubusercontent.com/u/314088?s=140&v=4" }, { "title": "TODO", "name": "aws-node-typescript-rest-api-with-dynamodb", "description": "This example shows your how to create a TypeScript powered REST API with DynamoDB.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-typescript-rest-api-with-dynamodb", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/QuantumInformation", "authorName": "Nikos", "authorAvatar": "https://avatars0.githubusercontent.com/u/216566?v=4&s=140" }, { "title": "AWS SQS Standard Example (NodeJS & Typescript)", "name": "aws-node-typescript-sqs-standard", "description": "This example demonstrates how to setup a SQS with Typescript.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-typescript-sqs-standard", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/jmpfrazao", "authorName": "Miguel Frazao", "authorAvatar": "https://avatars3.githubusercontent.com/u/28927258?s=460&v=4" }, { "title": "AWS Upload a file to S3 to trigger a Lambda function example in NodeJS", "name": "aws-node-upload-to-s3-and-postprocess", "description": "This example shows how to upload a file to S3 using a HTML form, and have S3 trigger a lambda function.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-upload-to-s3-and-postprocess", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/walgarch", "authorName": "walgarch", "authorAvatar": "https://avatars1.githubusercontent.com/u/32451330?v=4&s=140" }, { "title": "Serverless side rendering with Vue.js and Nuxt.js", "name": "aws-node-vue-nuxt-ssr", "description": "This project demonstrates how to use Nuxt.js to create a server-side rendered Vue.js app on AWS Lambda and AWS API Gateway.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-vue-nuxt-ssr", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/adnanrahic", "authorName": "adnanrahic", "authorAvatar": "https://avatars1.githubusercontent.com/u/15029531?s=400&v=4" }, { "title": "Simple Websocket Authorizers", "name": "aws-node-websockets-authorizers", "description": "The example shows you how to deploy simple websocket authorizers", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node-websockets-authorizers", "framework": "v1", "language": "node", "platform": "aws", "authorLink": "https://github.com/eahefnawy", "authorName": "Eslam λ Hefnawy", "authorAvatar": "https://avatars3.githubusercontent.com/u/2312463?v=4&s=140" }, { "title": "AWS NodeJS Example", "name": "aws-node", "description": "This template demonstrates how to deploy a simple NodeJS function running on AWS Lambda using the Serverless Framework.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-node", "framework": "v4", "language": "node", "platform": "aws", "authorLink": "https://github.com/serverless", "authorName": "Serverless, inc.", "authorAvatar": "https://avatars1.githubusercontent.com/u/13742415?s=200&v=4", "priority": 5 }, { "title": "AWS Serverless Alexa Skill example in Python", "name": "aws-python-alexa-skill", "description": "This example demonstrates how to setup your own Alexa skill using AWS Lambdas.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-python-alexa-skill", "framework": "v1", "language": "python", "platform": "aws", "authorLink": "https://github.com/rupakg", "authorName": "Rupak Ganguly", "authorAvatar": "https://avatars0.githubusercontent.com/u/8188?v=4&s=140" }, { "title": "AWS API Gateway Custom Authorizer Function with Auth0 example in Python", "name": "aws-python-auth0-custom-authorizers-api", "description": "This is an example of how to protect API endpoints with Auth0, JSON Web Tokens (jwt) and a custom authorizer lambda function in Python 3.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-python-auth0-custom-authorizers-api", "framework": "v1", "language": "python", "platform": "aws", "authorLink": "https://github.com/BrianAndersen78", "authorName": "BrianAndersen78", "authorAvatar": "https://avatars3.githubusercontent.com/u/30560831?v=4&s=140" }, { "title": "Python Flask API on AWS", "name": "aws-python-flask-api", "description": "This template demonstrates how to develop and deploy a simple Python Flask API running on AWS Lambda using the Serverless Framework.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-python-flask-api", "framework": "v4", "language": "python", "platform": "aws", "authorLink": "https://github.com/serverless", "authorName": "Serverless, inc.", "authorAvatar": "https://avatars1.githubusercontent.com/u/13742415?s=200&v=4", "priority": 7 }, { "title": "Python Flask API backed by DynamoDB on AWS", "name": "aws-python-flask-dynamodb-api", "description": "This template demonstrates how to develop and deploy a simple Python Flask API service backed by DynamoDB running on AWS Lambda using the Serverless Framework.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-python-flask-dynamodb-api", "framework": "v4", "language": "python", "platform": "aws", "authorLink": "https://github.com/serverless", "authorName": "Serverless, inc.", "authorAvatar": "https://avatars1.githubusercontent.com/u/13742415?s=200&v=4", "priority": 8 }, { "title": "Simple LINE bot", "name": "aws-python-line-echo-bot", "description": "This is a simple echo bot on LINE bot.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-python-line-echo-bot", "framework": "v1", "language": "python", "platform": "aws", "authorLink": "https://github.com/NiJia", "authorName": "NiJia", "authorAvatar": "https://avatars0.githubusercontent.com/u/418548?v=4&s=140" }, { "title": "AWS Serverless REST API with DynamoDB store and presigned URLs example in Python 3.6.", "name": "aws-python-pynamodb-s3-sigurl", "description": "This example demonstrates how to setup a RESTful Web Service allowing you to create, list, get, update and delete Assets. DynamoDB is used to store the data.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-python-pynamodb-s3-sigurl", "framework": "v1", "language": "python", "platform": "aws", "authorLink": "https://github.com/bedge", "authorName": "Bruce Edge", "authorAvatar": "https://avatars1.githubusercontent.com/u/499317?v=4&s=140" }, { "title": "AWS Serverless REST API with DynamoDB store example in Python", "name": "aws-python-rest-api-with-dynamodb", "description": "This example demonstrates how to setup a RESTful Web Service allowing you to create, list, get, update and delete Todos. DynamoDB is used to store the data.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-python-rest-api-with-dynamodb", "framework": "v1", "language": "python", "platform": "aws", "authorLink": "https://github.com/godfreyhobbs", "authorName": "Godfrey Hobbs", "authorAvatar": "https://avatars1.githubusercontent.com/u/8434141?v=4&s=140" }, { "title": "AWS Serverless REST API with FaunaDB store example in Python", "name": "aws-python-rest-api-with-faunadb", "description": "This example demonstrates how to setup a RESTful Web Service allowing you to create, list, get, update and delete Todos. FaunaDB is used to store the data.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-python-rest-api-with-faunadb", "framework": "v1", "language": "python", "platform": "aws", "authorLink": "https://github.com/rupakg", "authorName": "Rupak Ganguly", "authorAvatar": "https://avatars0.githubusercontent.com/u/8188?v=4&s=140" }, { "title": "AWS Python Rest API with Pymongo", "name": "aws-python-rest-api-with-pymongo", "description": "AWS Python Rest API with Pymongo Example", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-python-rest-api-with-pymongo", "framework": "v1", "language": "python", "platform": "aws", "authorLink": "https://github.com/gsweene2", "authorName": "Garrett Sweeney", "authorAvatar": "" }, { "title": "AWS Serverless REST API with DynamoDB store example in Python", "name": "aws-python-rest-api-with-pynamodb", "description": "This example demonstrates how to setup a RESTful Web Service allowing you to create, list, get, update and delete Todos. DynamoDB is used to store the data.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-python-rest-api-with-pynamodb", "framework": "v1", "language": "python", "platform": "aws", "authorLink": "https://github.com/helveticafire", "authorName": "Ben Fitzgerald", "authorAvatar": "https://avatars0.githubusercontent.com/u/1323872?v=4&s=140" }, { "title": "AWS Simple HTTP Endpoint example in Python", "name": "aws-python-rest-api", "description": "This template demonstrates how to make a simple REST API with Python running on AWS Lambda and API Gateway using the traditional Serverless Framework.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-python-rest-api", "framework": "v2", "language": "python", "platform": "aws", "authorLink": "https://github.com/serverless", "authorName": "Serverless, inc.", "authorAvatar": "https://avatars1.githubusercontent.com/u/13742415?s=200&v=4" }, { "title": "AWS Simple HTTP Endpoint example in Python", "name": "aws-python-http-api", "description": "This template demonstrates how to make a simple HTTP API with Python running on AWS Lambda and API Gateway using the Serverless Framework.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-python-rest-api", "framework": "v4", "language": "python", "platform": "aws", "authorLink": "https://github.com/serverless", "authorName": "Serverless, inc.", "authorAvatar": "https://avatars1.githubusercontent.com/u/13742415?s=200&v=4", "priority": 6 }, { "title": "AWS Python Scheduled Cron example in Python", "name": "aws-python-scheduled-cron", "description": "This is an example of creating a function that runs as a cron job using the serverless ''schedule'' event.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-python-scheduled-cron", "framework": "v4", "language": "python", "platform": "aws", "authorLink": "https://github.com/rupakg", "authorName": "Rupak Ganguly", "authorAvatar": "https://avatars0.githubusercontent.com/u/8188?v=4&s=140", "priority": 9 }, { "title": "AWS Simple HTTP Endpoint example in Python", "name": "aws-python-simple-http-endpoint", "description": "This example demonstrates how to setup a simple HTTP GET endpoint. Once you ping it, it will reply with the current time.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-python-simple-http-endpoint", "framework": "v1", "language": "python", "platform": "aws", "authorLink": "https://github.com/rupakg", "authorName": "Rupak Ganguly", "authorAvatar": "https://avatars0.githubusercontent.com/u/8188?v=4&s=140" }, { "title": "Python SQS Producer Consumer on AWS", "name": "aws-python-sqs-worker", "description": "This template demonstrates how to develop and deploy a simple SQS-based producer-consumer service running on AWS Lambda using the traditional Serverless Framework.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-python-sqs-worker", "framework": "v2", "language": "python", "platform": "aws", "authorLink": "https://github.com/serverless", "authorName": "Serverless, inc.", "authorAvatar": "https://avatars1.githubusercontent.com/u/13742415?s=200&v=4" }, { "title": "TODO", "name": "aws-python-telegram-bot", "description": "This example demonstrates how to setup an echo Telegram Bot using the Serverless Framework.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-python-telegram-bot", "framework": "v1", "language": "python", "platform": "aws", "authorLink": "https://github.com/jonatasbaldin", "authorName": "Jonatas Baldin", "authorAvatar": "https://avatars3.githubusercontent.com/u/8570364?v=4&s=140" }, { "title": "AWS Python Example", "name": "aws-python", "description": "This template demonstrates how to deploy a Python function running on AWS Lambda using the Serverless Framework.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-python", "framework": "v4", "language": "python", "platform": "aws", "authorLink": "https://github.com/serverless", "authorName": "Serverless, inc.", "authorAvatar": "https://avatars1.githubusercontent.com/u/13742415?s=200&v=4", "priority": 10 }, { "title": "Ruby LINE bot", "name": "aws-ruby-line-bot", "description": "This example shows you how to create a LINE bot using Ruby.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-ruby-line-bot", "framework": "v1", "language": "ruby", "platform": "aws", "authorLink": "https://github.com/knugie", "authorName": "Wolfgang Teuber", "authorAvatar": "https://avatars0.githubusercontent.com/u/1446195?v=4&s=140" }, { "title": "AWS Simple HTTP Endpoint example in Ruby", "name": "aws-ruby-simple-http-endpoint", "description": "This example demonstrates how to setup a simple HTTP GET endpoint. Once you ping it, it will reply with the current time.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-ruby-simple-http-endpoint", "framework": "v1", "language": "ruby", "platform": "aws", "authorLink": "https://github.com/josephyi", "authorName": "Joseph Yi", "authorAvatar": "https://avatars0.githubusercontent.com/u/1994863?v=4&s=140" }, { "title": "Ruby Sinatra API backed by DynamoDB on AWS", "name": "aws-ruby-sinatra-dynamodb-api", "description": "This template demonstrates how to develop and deploy a simple Ruby Sinatra API service backed by DynamoDB running on AWS Lambda using the traditional Serverless Framework.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-ruby-sinatra-dynamodb-api", "framework": "v2", "language": "ruby", "platform": "aws", "authorLink": "https://github.com/serverless", "authorName": "Serverless, inc.", "authorAvatar": "https://avatars1.githubusercontent.com/u/13742415?s=200&v=4" }, { "title": "AWS Ruby scheduled cron example backed by DynamoDB", "name": "aws-ruby-cron-with-dynamodb", "description": "This is an example of creating a function that runs as a cron job using the serverless 'schedule' event. With the usage of the AWS Lambda function, it creates a record to the DynamoDB each and every 30 minutes.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-ruby-cron-with-dynamodb", "framework": "v2", "language": "ruby", "platform": "aws", "authorLink": "https://github.com/pigius", "authorName": "Daniel Aniszkiewicz", "authorAvatar": "https://avatars.githubusercontent.com/u/8863200?v=4&s=140" }, { "title": "AWS Ruby Step Functions", "name": "ruby-step-functions", "description": "AWS Ruby example that make usage of AWS Step Functions with AWS Lambda, DynamoDB and Step Functions flows.", "githubUrl": "https://github.com/serverless/examples/tree/master/serverless-ruby-step-functions", "framework": "v2", "language": "ruby", "platform": "aws", "authorLink": "https://github.com/pigius", "authorName": "Daniel Aniszkiewicz", "authorAvatar": "https://avatars.githubusercontent.com/u/8863200?v=4&s=140" }, { "title": "Serverless AWS Ruby SQS with DynamoDB example", "name": "aws-ruby-sqs-with-dynamodb", "description": "A serverless ruby example that creates DynamoDB records with the usage of SQS, API Gateway, and AWS Lambda functions.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-ruby-sqs-with-dynamodb", "framework": "v2", "language": "ruby", "platform": "aws", "authorLink": "https://github.com/pigius", "authorName": "Daniel Aniszkiewicz", "authorAvatar": "https://avatars.githubusercontent.com/u/8863200?v=4&s=140" }, { "title": "AWS Serverless Boilerplate example in Rust", "name": "aws-rust-simple-http-endpoint", "description": "This example shows a Serverless boilerplate in Rust.", "githubUrl": "https://github.com/serverless/examples/tree/master/aws-rust-simple-http-endpoint", "framework": "v1+", "language": "rust", "platform": "aws", "authorLink": "https://github.com/jonee", "authorName": "Jonee Ryan Ty", "authorAvatar": null }, { "title": "TODO", "name": "azure-node-line-bot", "description": "This example demonstrates how to setup a serverless Line Bot using Node.js.", "githubUrl": "https://github.com/serverless/examples/tree/master/azure-node-line-bot", "framework": "v1", "language": "node", "platform": "azure", "authorLink": "https://github.com/jiyeonseo", "authorName": "seojeee", "authorAvatar": "https://avatars2.githubusercontent.com/u/2231510?v=4&s=140" }, { "title": "Azure Simple HTTP Endpoint example in NodeJS", "name": "azure-node-simple-http-endpoint", "description": "In this example, we deploy an HTTP Node.js Azure Function. This example shows you how to read properties off of a query string or the request body, then set a result back to Azure.", "githubUrl": "https://github.com/serverless/examples/tree/master/azure-node-simple-http-endpoint", "framework": "v1", "language": "node", "platform": "azure", "authorLink": "https://github.com/fiveisprime", "authorName": "Matt Hernandez", "authorAvatar": "https://avatars2.githubusercontent.com/u/1186948?v=4&s=140" }, { "title": "TODO", "name": "azure-node-telegram-bot", "description": "This example demonstrates how to setup a serverless Telegram Bot on Azure.", "githubUrl": "https://github.com/serverless/examples/tree/master/azure-node-telegram-bot", "framework": "v1", "language": "node", "platform": "azure", "authorLink": "https://github.com/jiyeonseo", "authorName": "seojeee", "authorAvatar": "https://avatars2.githubusercontent.com/u/2231510?v=4&s=140" }, { "title": "Using Azure Service Queue to trigger Azure Function", "name": "azure-node-typescript-servicebus-trigger-endpoint", "description": "This example demonstrates how to trigger an Azure function when a message arrives in Service Bus Queue", "githubUrl": "https://github.com/serverless/examples/tree/master/azure-node-typescript-servicebus-trigger-endpoint", "framework": "v1", "language": "node", "platform": "azure", "authorLink": "https://github.com/Kurshit", "authorName": "Kurshit Kukreja", "authorAvatar": "https://avatars0.githubusercontent.com/u/30333780?s=400&u=53af20c512014f0b7250ed6ac003be1c5cfbddd7&v=4" }, { "title": "GCF Simple HTTP Endpoint example in golang", "name": "google-golang-simple-http-endpoint", "description": "This example demonstrates how to setup a simple golang HTTP GET endpoint on GCP Cloud Functions. When you ping the endpoint we've set up you'll see the time returned for the given request type.", "githubUrl": "https://github.com/serverless/examples/tree/master/google-golang-simple-http-endpoint", "framework": "v1", "language": "go", "platform": "gcp", "authorLink": "https://github.com/sebito91", "authorName": "Sebastian Borza", "authorAvatar": "https://avatars0.githubusercontent.com/u/3159454?v=4&s=140" }, { "title": "GCF Simple HTTP Endpoint example in NodeJS", "name": "google-node-simple-http-endpoint", "description": "This example demonstrates how to setup a simple HTTP GET endpoint.", "githubUrl": "https://github.com/serverless/examples/tree/master/google-node-simple-http-endpoint", "framework": "v1", "language": "node", "platform": "gcp", "authorLink": "https://github.com/pmuens", "authorName": "Philipp Muens", "authorAvatar": "https://avatars3.githubusercontent.com/u/1606004?v=4&s=140" }, { "title": "Typescript HTTP Endpoint", "name": "google-node-typescript-http-endpoint", "description": "This example demonstrates how to setup a simple Typescript HTTP endpoint on GCP.", "githubUrl": "https://github.com/serverless/examples/tree/master/google-node-typescript-http-endpoint", "framework": "v1", "language": "node", "platform": "gcp", "authorLink": "https://github.com/jiyeonseo", "authorName": "seojeee", "authorAvatar": "https://avatars2.githubusercontent.com/u/2231510?v=4&s=140" }, { "title": "GCF Simple HTTP Endpoint example in Python", "name": "google-python-simple-http-endpoint", "description": "This example demonstrates how to setup a simple python HTTP GET endpoint on GCP Cloud Functions. When you ping the endpoint we've set up you'll see the time returned for the given request type.", "githubUrl": "https://github.com/serverless/examples/tree/master/google-python-simple-http-endpoint", "framework": "v1", "language": "python", "platform": "gcp", "authorLink": "https://github.com/sebito91", "authorName": "Sebastian Borza", "authorAvatar": "https://avatars0.githubusercontent.com/u/3159454?v=4&s=140" }, { "title": "Kubeless Serverless Simple function example in Python", "name": "kubeless-python-simple-function", "description": "This example demonstrates a simple function example in Python.", "githubUrl": "https://github.com/serverless/examples/tree/master/kubeless-python-simple-function", "framework": "v1", "language": "python", "platform": "kubeless", "authorLink": "https://github.com/andresmgot", "authorName": "Andres", "authorAvatar": "https://avatars0.githubusercontent.com/u/4025665?v=4&s=140" }, { "title": "Kubeless Serverless Simple scheduled function example in Python", "name": "kubeless-python-simple-scheduled-function", "description": "This example demonstrates a simple sexample in Python for a scheduled function.", "githubUrl": "https://github.com/serverless/examples/tree/master/kubeless-python-simple-scheduled-function", "framework": "v1", "language": "python", "platform": "kubeless", "authorLink": "https://github.com/andresmgot", "authorName": "Andres", "authorAvatar": "https://avatars0.githubusercontent.com/u/4025665?v=4&s=140" }, { "title": "OpenWhisk Serverless Boilerplate example in Go", "name": "openwhisk-go-simple", "description": "This example shows a Serverless boilerplate in Go.", "githubUrl": "https://github.com/serverless/examples/tree/master/openwhisk-go-simple", "framework": "v1", "language": "go", "platform": "openwhisk", "authorLink": "https://github.com/jthomas", "authorName": "James Thomas", "authorAvatar": "https://avatars2.githubusercontent.com/u/2322?v=4&s=140" }, { "title": "OpenWhisk Serverless Boilerplate using Docker example in NodeJS", "name": "openwhisk-node-and-docker-chaining-functions", "description": "This example shows a Serverless boilerplate using Docker in NodeJS.", "githubUrl": "https://github.com/serverless/examples/tree/master/openwhisk-node-and-docker-chaining-functions", "framework": "v1", "language": "node", "platform": "openwhisk", "authorLink": "https://github.com/jthomas", "authorName": "James Thomas", "authorAvatar": "https://avatars2.githubusercontent.com/u/2322?v=4&s=140" }, { "title": "OpenWhisk Serverless Chaining Functions example in NodeJS", "name": "openwhisk-node-chaining-functions", "description": "This example demonstrates chaining functions in NodeJS.", "githubUrl": "https://github.com/serverless/examples/tree/master/openwhisk-node-chaining-functions", "framework": "v1", "language": "node", "platform": "openwhisk", "authorLink": "https://github.com/jthomas", "authorName": "James Thomas", "authorAvatar": "https://avatars2.githubusercontent.com/u/2322?v=4&s=140" }, { "title": "OpenWhisk Serverless Scheduled Cron job example in NodeJS", "name": "openwhisk-node-scheduled-cron", "description": "This example demonstrates scheduleding a cron job in NodeJS.", "githubUrl": "https://github.com/serverless/examples/tree/master/openwhisk-node-scheduled-cron", "framework": "v1", "language": "node", "platform": "openwhisk", "authorLink": "https://github.com/jthomas", "authorName": "James Thomas", "authorAvatar": "https://avatars2.githubusercontent.com/u/2322?v=4&s=140" }, { "title": "OpenWhisk Simple HTTP Endpoint example in NodeJS", "name": "openwhisk-node-simple-http-endpoint", "description": "This example demonstrates how to setup a simple HTTP GET endpoint.", "githubUrl": "https://github.com/serverless/examples/tree/master/openwhisk-node-simple-http-endpoint", "framework": "v1", "language": "node", "platform": "openwhisk", "authorLink": "https://github.com/jthomas", "authorName": "James Thomas", "authorAvatar": "https://avatars2.githubusercontent.com/u/2322?v=4&s=140" }, { "title": "OpenWhisk Serverless Simple example in NodeJS", "name": "openwhisk-node-simple", "description": "This example demonstrates a simple example in NodeJS.", "githubUrl": "https://github.com/serverless/examples/tree/master/openwhisk-node-simple", "framework": "v1", "language": "node", "platform": "openwhisk", "authorLink": "https://github.com/jthomas", "authorName": "James Thomas", "authorAvatar": "https://avatars2.githubusercontent.com/u/2322?v=4&s=140" }, { "title": "OpenWhisk Serverless Simple example in PHP", "name": "openwhisk-php-simple", "description": "This example demonstrates a simple example in PHP.", "githubUrl": "https://github.com/serverless/examples/tree/master/openwhisk-php-simple", "framework": "v1", "language": "php", "platform": "openwhisk", "authorLink": "https://github.com/jthomas", "authorName": "James Thomas", "authorAvatar": "https://avatars2.githubusercontent.com/u/2322?v=4&s=140" }, { "title": "OpenWhisk Serverless Scheduled Cron job example in Python", "name": "openwhisk-python-scheduled-cron", "description": "This example demonstrates scheduleding a cron job.", "githubUrl": "https://github.com/serverless/examples/tree/master/openwhisk-python-scheduled-cron", "framework": "v1", "language": "python", "platform": "openwhisk", "authorLink": "https://github.com/jthomas", "authorName": "James Thomas", "authorAvatar": "https://avatars2.githubusercontent.com/u/2322?v=4&s=140" }, { "title": "OpenWhisk Simple HTTP Endpoint example in Python", "name": "openwhisk-python-simple-http-endpoint", "description": "This example demonstrates how to setup a simple HTTP GET endpoint.", "githubUrl": "https://github.com/serverless/examples/tree/master/openwhisk-python-simple-http-endpoint", "framework": "v1", "language": "python", "platform": "openwhisk", "authorLink": "https://github.com/jthomas", "authorName": "James Thomas", "authorAvatar": "https://avatars2.githubusercontent.com/u/2322?v=4&s=140" }, { "title": "OpenWhisk Serverless Simple example in Python", "name": "openwhisk-python-simple", "description": "This example demonstrates a simple example in Python.", "githubUrl": "https://github.com/serverless/examples/tree/master/openwhisk-python-simple", "framework": "v1", "language": "python", "platform": "openwhisk", "authorLink": "https://github.com/jthomas", "authorName": "James Thomas", "authorAvatar": "https://avatars2.githubusercontent.com/u/2322?v=4&s=140" }, { "title": "OpenWhisk Serverless Simple example in Ruby", "name": "openwhisk-ruby-simple", "description": "This example demonstrates a simple example in Ruby.", "githubUrl": "https://github.com/serverless/examples/tree/master/openwhisk-ruby-simple", "framework": "v1", "language": "ruby", "platform": "openwhisk", "authorLink": "https://github.com/jthomas", "authorName": "James Thomas", "authorAvatar": "https://avatars2.githubusercontent.com/u/2322?v=4&s=140" }, { "title": "OpenWhisk Serverless Boilerplate example in Rust", "name": "openwhisk-rust-simple-http-endpoint", "description": "This example shows a Serverless boilerplate in Rust.", "githubUrl": "https://github.com/serverless/examples/tree/master/openwhisk-rust-simple-http-endpoint", "framework": "v1+", "language": "rust", "platform": "openwhisk", "authorLink": "https://github.com/jonee", "authorName": "Jonee Ryan Ty", "authorAvatar": null }, { "title": "OpenWhisk Swift example with external libraries and pre compiled binaries", "name": "openwhisk-swift-precompiled-binaries", "description": "This example shows you how to use external packages and deploy binaries", "githubUrl": "https://github.com/serverless/examples/tree/master/openwhisk-swift-precompiled-binaries", "framework": "v1", "language": "swift", "platform": "openwhisk", "authorLink": "https://github.com/jthomas", "authorName": "James Thomas", "authorAvatar": "https://avatars2.githubusercontent.com/u/2322?v=4&s=140" }, { "title": "OpenWhisk Serverless Scheduled Cron job example in Swift", "name": "openwhisk-swift-scheduled-cron", "description": "This example demonstrates scheduling a cron job.", "githubUrl": "https://github.com/serverless/examples/tree/master/openwhisk-swift-scheduled-cron", "framework": "v1", "language": "swift", "platform": "openwhisk", "authorLink": "https://github.com/jthomas", "authorName": "James Thomas", "authorAvatar": "https://avatars2.githubusercontent.com/u/2322?v=4&s=140" }, { "title": "OpenWhisk Simple HTTP Endpoint example in Swift", "name": "openwhisk-swift-simple-http-endpoint", "description": "This example demonstrates how to setup a simple HTTP GET endpoint.", "githubUrl": "https://github.com/serverless/examples/tree/master/openwhisk-swift-simple-http-endpoint", "framework": "v1", "language": "swift", "platform": "openwhisk", "authorLink": "https://github.com/jthomas", "authorName": "James Thomas", "authorAvatar": "https://avatars2.githubusercontent.com/u/2322?v=4&s=140" }, { "title": "OpenWhisk Serverless Simple example in Swift", "name": "openwhisk-swift-simple", "description": "This example demonstrates a simple example in Swift.", "githubUrl": "https://github.com/serverless/examples/tree/master/openwhisk-swift-simple", "framework": "v1", "language": "swift", "platform": "openwhisk", "authorLink": "https://github.com/jthomas", "authorName": "James Thomas", "authorAvatar": "https://avatars2.githubusercontent.com/u/2322?v=4&s=140" }, { "title": "Twilio Forward a Call", "name": "twilio-node-forward-call", "description": "This example projects helps you deploy a serverless function to the Twilio runtime. The function responds the TwiML configuration to forward phone call.", "githubUrl": "https://github.com/serverless/examples/tree/master/twilio-node-forward-call", "framework": "v1", "language": "node", "platform": "twilio", "authorLink": "https://github.com/stefanjudis", "authorName": "Stefan Judis", "authorAvatar": "https://avatars3.githubusercontent.com/u/962099?v=4&s=140" }, { "title": "Serverless Lambda S3 Demonstration", "name": "serverless-lambda-s3", "description": "This project demonstrates how the Serverless Framework can be used to deploy a NodeJS Lambda function that responds to events in an S3 bucket.", "githubUrl": "https://github.com/johncmunson/serverless-lambda-s3", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/johncmunson", "authorName": "johncmunson", "authorAvatar": "https://avatars.githubusercontent.com/u/19480078?v=4", "community": true }, { "title": "Spiderless, Web Spider on Serverless", "name": "spider-less", "description": "A web spider / scraper / website change detector built with Lambda, API Gateway, DynamoDB and SNS", "githubUrl": "https://github.com/slashbit/spider-less", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/slashbit", "authorName": "slashbit", "authorAvatar": "https://avatars.githubusercontent.com/u/5327840?v=4", "community": true }, { "title": "AWS Demo Java Spring Cloud Function Serverless", "name": "aws-java-spring-cloud-function-demo", "description": "If Java is your choice of programming language-Spring Cloud Function,Serverless Framework makes a great technology stack. It boosts developer productivity by decoupling from Vendor specific FaaS API, and deployment activities.", "githubUrl": "https://github.com/mbsambangi/aws-java-spring-cloud-function-demo", "framework": "latest", "language": "java", "platform": "aws", "authorLink": "https://github.com/mbsambangi", "authorName": "mbsambangi", "authorAvatar": "https://avatars.githubusercontent.com/u/22281980?v=4", "community": true }, { "title": "Serverless Architecture Boilerplate", "name": "serverless-architecture-boilerplate", "description": "Boilerplate to organize and deploy big projects using Serverless and CloudFormation on AWS", "githubUrl": "https://github.com/msfidelis/serverless-architecture-boilerplate", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/msfidelis", "authorName": "msfidelis", "authorAvatar": "https://avatars.githubusercontent.com/u/13524134?v=4", "community": true }, { "title": "JwtAuthorizr", "name": "jwtAuthorizr", "description": "Custom JWT Authorizer Lambda function for Amazon API Gateway with Bearer JWT", "githubUrl": "https://github.com/serverlessbuch/jwtAuthorizr", "framework": ">=1.2.0 <2.0.0", "language": "node", "platform": "aws", "authorLink": "https://github.com/serverlessbuch", "authorName": "serverlessbuch", "authorAvatar": "https://avatars.githubusercontent.com/u/24709654?v=4", "community": true }, { "title": "Slack signup serverless", "name": "slack-signup-serverless", "description": "Serverless signup to Slack and more. Lambda with Python, StepFunctions, and Web front end. Python boilerplate included.", "githubUrl": "https://github.com/dzimine/slack-signup-serverless", "framework": ">=1.2.0 <2.0.0", "language": "python", "platform": "aws", "authorLink": "https://github.com/dzimine", "authorName": "dzimine", "authorAvatar": "https://avatars.githubusercontent.com/u/1294734?v=4", "community": true }, { "title": "Serverless graphql api", "name": "serverless-graphql-api", "description": "Serverless GraphQL API using Lambda and DynamoDB", "githubUrl": "https://github.com/boazdejong/serverless-graphql-api", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/boazdejong", "authorName": "boazdejong", "authorAvatar": "https://avatars.githubusercontent.com/u/4102106?v=4", "community": true }, { "title": "Serverless screenshot", "name": "serverless-screenshot", "description": "Serverless Screenshot Service using PhantomJS", "githubUrl": "https://github.com/svdgraaf/serverless-screenshot", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/svdgraaf", "authorName": "svdgraaf", "authorAvatar": "https://avatars.githubusercontent.com/u/19777?v=4", "community": true }, { "title": "Serverless postgraphql", "name": "serverless-postgraphql", "description": "GraphQL endpoint for PostgreSQL using postgraphql", "githubUrl": "https://github.com/rentrop/serverless-postgraphql", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/rentrop", "authorName": "rentrop", "authorAvatar": "https://avatars.githubusercontent.com/u/9575579?v=4", "community": true }, { "title": "Serverless messenger boilerplate", "name": "serverless-messenger-boilerplate", "description": "Serverless messenger bot boilerplate", "githubUrl": "https://github.com/SC5/serverless-messenger-boilerplate", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/SC5", "authorName": "SC5", "authorAvatar": "https://avatars.githubusercontent.com/u/3158015?v=4", "community": true }, { "title": "Serverless npm registry", "name": "yith", "description": "Serverless private npm registry, proxy and cache.", "githubUrl": "https://github.com/craftship/yith", "framework": ">=1.20.2", "language": "node", "platform": "aws", "authorLink": "https://github.com/craftship", "authorName": "craftship", "authorAvatar": "https://avatars.githubusercontent.com/u/16784624?v=4", "community": true }, { "title": "Serverless facebook quotebot", "name": "quotebot", "description": "100% Serverless Facebook messenger chatbot which will respond with inspiring quotes", "githubUrl": "https://github.com/pmuens/quotebot", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/pmuens", "authorName": "pmuens", "authorAvatar": "https://avatars.githubusercontent.com/u/1606004?v=4", "community": true }, { "title": "Serverless slack trevorbot", "name": "trevorbot", "description": "Slack bot for info on where in the world is Trevor Gerhardt?", "githubUrl": "https://github.com/conveyal/trevorbot", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/conveyal", "authorName": "conveyal", "authorAvatar": "https://avatars.githubusercontent.com/u/3592637?v=4", "community": true }, { "title": "Pfs email serverless", "name": "pfs-email-serverless", "description": "This is a lambda function created by the serverless framework. It searches through members in our mongodb who have not been sent emails and sends them an email with their custom token to unlock the pledge free stream. It then marks those members off as already receiving the email.", "githubUrl": "https://github.com/SCPR/pfs-email-serverless", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/SCPR", "authorName": "SCPR", "authorAvatar": "https://avatars.githubusercontent.com/u/855010?v=4", "community": true }, { "title": "Plaid cashburndown service", "name": "cashburndown-service", "description": "Service for calculating cash burndown with plaid. Frontend code can be found here: https://github.com/cplee/cashburndown-site", "githubUrl": "https://github.com/cplee/cashburndown-service", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/cplee", "authorName": "cplee", "authorAvatar": "https://avatars.githubusercontent.com/u/2239057?v=4", "community": true }, { "title": "Cordis serverless", "name": "cordis-serverless", "description": "A serverless API for EU Cordis data", "githubUrl": "https://github.com/marzeelabs/cordis-serverless", "framework": "1.4.0", "language": "node", "platform": "aws", "authorLink": "https://github.com/marzeelabs", "authorName": "marzeelabs", "authorAvatar": "https://avatars.githubusercontent.com/u/2045852?v=4", "community": true }, { "title": "Serverless newsletter signup", "name": "serverless-newsletter-signup", "description": "Saves user details into DynamoDB table. Required values are email, first_name and last_name.", "githubUrl": "https://github.com/dschep/serverless-newsletter-signup", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/dschep", "authorName": "dschep", "authorAvatar": "https://avatars.githubusercontent.com/u/667763?v=4", "community": true }, { "title": "Serverless slack cron", "name": "serverless-slack-cron", "description": "Lambda function which sends messages to Slack channel in regular intervals via cron trigger.", "githubUrl": "https://github.com/ivanderbu2/serverless-slack-cron", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/ivanderbu2", "authorName": "ivanderbu2", "authorAvatar": "https://avatars.githubusercontent.com/u/2388543?v=4", "community": true }, { "title": "Sls access counter", "name": "sls-access-counter", "description": "Site visitor counter", "githubUrl": "https://github.com/takahashim/sls-access-counter", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/takahashim", "authorName": "takahashim", "authorAvatar": "https://avatars.githubusercontent.com/u/10401?v=4", "community": true }, { "title": "Sls form mail", "name": "sls-form-mail", "description": "Send SNS email from form data", "githubUrl": "https://github.com/takahashim/sls-form-mail", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/takahashim", "authorName": "takahashim", "authorAvatar": "https://avatars.githubusercontent.com/u/10401?v=4", "community": true }, { "title": "Serverless python sample", "name": "serverless-python-sample", "description": "A simple serverless python sample with REST API endpoints and dependencies", "githubUrl": "https://github.com/bennybauer/serverless-python-sample", "framework": "latest", "language": "python", "platform": "aws", "authorLink": "https://github.com/bennybauer", "authorName": "bennybauer", "authorAvatar": "https://avatars.githubusercontent.com/u/3007059?v=4", "community": true }, { "title": "Serverless slack emojibot", "name": "emojibot", "description": "Serverless slack bot for emoji", "githubUrl": "https://github.com/markhobson/emojibot", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/markhobson", "authorName": "markhobson", "authorAvatar": "https://avatars.githubusercontent.com/u/178443?v=4", "community": true }, { "title": "Serverless cloudwatch rds custom metrics", "name": "serverless-cloudwatch-rds-custom-metrics", "description": "A NodeJS-based MySQL RDS Data Collection script to push Custom Metrics to Cloudwatch with Serverless", "githubUrl": "https://github.com/AndrewFarley/serverless-cloudwatch-rds-custom-metrics", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/AndrewFarley", "authorName": "AndrewFarley", "authorAvatar": "https://avatars.githubusercontent.com/u/470163?v=4", "community": true }, { "title": "Sc5 serverless boilerplate", "name": "sc5-serverless-boilerplate", "description": "A boilerplate that contains setup for test-driven development", "githubUrl": "https://github.com/SC5/sc5-serverless-boilerplate", "framework": ">=1.2.0 <2.0.0", "language": "node", "platform": "aws", "authorLink": "https://github.com/SC5", "authorName": "SC5", "authorAvatar": "https://avatars.githubusercontent.com/u/14107257?v=4", "community": true }, { "title": "Serverless blog to podcast", "name": "serverless-blog-to-podcast", "description": "Service that reads RSS feed and converts the entries to a podcast feed and audio files using Amazon Polly", "githubUrl": "https://github.com/SC5/serverless-blog-to-podcast", "framework": ">=1.2.0 <2.0.0", "language": "node", "platform": "aws", "authorLink": "https://github.com/SC5", "authorName": "SC5", "authorAvatar": "https://avatars.githubusercontent.com/u/3158015?v=4", "community": true }, { "title": "Offset trump", "name": "offset-trump", "description": "Single page app using Serverless (C# runtime) and S3 site hosting. Pledge to do a good thing for the next four years to offset the potential negative effects of the US Presidency", "githubUrl": "https://github.com/FLGMwt/offset-trump", "framework": "latest", "language": "csharp", "platform": "aws", "authorLink": "https://github.com/FLGMwt", "authorName": "FLGMwt", "authorAvatar": "https://avatars.githubusercontent.com/u/4138357?v=4", "community": true }, { "title": "Serverless url shortener", "name": "serverless-url-shortener", "description": "A simple url-shortener, using Serverless framework", "githubUrl": "https://github.com/aletheia/serverless-url-shortener", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/aletheia", "authorName": "aletheia", "authorAvatar": "https://avatars.githubusercontent.com/u/653823?v=4", "community": true }, { "title": "Serverless html pdf", "name": "serverless-html-pdf", "description": "Service that convert HTML to PDF using PhantomJS's rasterize example.", "githubUrl": "https://github.com/calvintychan/serverless-html-pdf", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/calvintychan", "authorName": "calvintychan", "authorAvatar": "https://avatars.githubusercontent.com/u/11818?v=4", "community": true }, { "title": "Serverless examples cached rds ws", "name": "serverless-examples-cached-rds-ws", "description": "A serverless framework example project that uses API Gateway, ElastiCache, and RDS PostgreSQL.", "githubUrl": "https://github.com/mugglmenzel/serverless-examples-cached-rds-ws", "framework": "latest", "language": "java", "platform": "aws", "authorLink": "https://github.com/mugglmenzel", "authorName": "mugglmenzel", "authorAvatar": "https://avatars.githubusercontent.com/u/1062212?v=4", "community": true }, { "title": "Bittman", "name": "bittman", "description": "A serverless project that follows a stock trading algorithm and uses scheduled functions to save data to DynamoDB and send emails through Mailgun.", "githubUrl": "https://github.com/rhlsthrm/bittman", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/rhlsthrm", "authorName": "rhlsthrm", "authorAvatar": "https://avatars.githubusercontent.com/u/11512787?v=4", "community": true }, { "title": "Adoptable pet bot", "name": "adoptable-pet-bot", "description": "Tweets adoptable pets using Serverless (Node.js) and AWS Lambda", "githubUrl": "https://github.com/lynnaloo/adoptable-pet-bot", "framework": ">=1.8.0", "language": "node", "platform": "aws", "authorLink": "https://github.com/lynnaloo", "authorName": "lynnaloo", "authorAvatar": "https://avatars.githubusercontent.com/u/1610195?v=4", "community": true }, { "title": "Owntracks serverless", "name": "owntracks-serverless", "description": "A serverless implementation of the OwnTracks HTTP backend", "githubUrl": "https://github.com/dschep/owntracks-serverless", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/dschep", "authorName": "dschep", "authorAvatar": "https://avatars.githubusercontent.com/u/667763?v=4", "community": true }, { "title": "Serverless modern koa", "name": "serverless-modern-koa", "description": "Serverless modern koa starter kit", "githubUrl": "https://github.com/barczaG/serverless-modern-koa", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/barczaG", "authorName": "barczaG", "authorAvatar": "https://avatars.githubusercontent.com/u/1392454?v=4", "community": true }, { "title": "Serverless ReactJS Universal Rendering Boilerplate", "name": "react-universal-in-serverless", "description": "ReactJS web app Starter kit does universal (isomorphic) rendering with Serverless", "githubUrl": "https://github.com/TylorShin/react-universal-in-serverless", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/TylorShin", "authorName": "TylorShin", "authorAvatar": "https://avatars.githubusercontent.com/u/9796103?v=4", "community": true }, { "title": "Open Bot", "name": "open-bot", "description": "An unoptionated Github bot driven by a configuration file in the repository", "githubUrl": "https://github.com/open-bot/open-bot", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/open-bot", "authorName": "open-bot", "authorAvatar": "https://avatars.githubusercontent.com/u/26090214?v=4", "community": true }, { "title": "Aws ses serverless example", "name": "aws-ses-serverless-example", "description": "AWS SES example in NodeJS using lambda", "githubUrl": "https://github.com/lakshmantgld/aws-ses-serverless-example", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/lakshmantgld", "authorName": "lakshmantgld", "authorAvatar": "https://avatars.githubusercontent.com/u/6481030?v=4", "community": true }, { "title": "Aws node signed uploads", "name": "aws-node-signed-uploads", "description": "Upload files larger than 10MB with AWS Lambda and API Gateway. Can be developed and tested locally.", "githubUrl": "https://github.com/kalinchernev/aws-node-signed-uploads", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/kalinchernev", "authorName": "kalinchernev", "authorAvatar": "https://avatars.githubusercontent.com/u/1923476?v=4", "community": true }, { "title": "SQS Worker with AWS Lambda and CloudWatch Alarms", "name": "sqs-worker-serverless", "description": "Process messages stored in SQS with an [auto-scaled AWS Lambda worker](https://sbstjn.com/serverless-sqs-worker-with-aws-lambda.html) function.", "githubUrl": "https://github.com/sbstjn/sqs-worker-serverless", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/sbstjn", "authorName": "sbstjn", "authorAvatar": "https://avatars.githubusercontent.com/u/248965?v=4", "community": true }, { "title": "Serverless image manager", "name": "lambda-image-manager", "description": "image upload / download with resizing. Used API gateway's binary support & serverless", "githubUrl": "https://github.com/TylorShin/lambda-image-manager", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/TylorShin", "authorName": "TylorShin", "authorAvatar": "https://avatars.githubusercontent.com/u/9796103?v=4", "community": true }, { "title": "Amazon Kinesis Streams fan out via Kinesis Analytics", "name": "kinesis-streams-fan-out-kinesis-analytics", "description": "Use Amazon Kinesis Analytics to fan-out your Kinesis Streams and avoid read throttling.", "githubUrl": "https://github.com/alexcasalboni/kinesis-streams-fan-out-kinesis-analytics", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/alexcasalboni", "authorName": "alexcasalboni", "authorAvatar": "https://avatars.githubusercontent.com/u/2457588?v=4", "community": true }, { "title": "HoneyLambda", "name": "honeyLambda", "description": "a simple, serverless application designed to create and monitor URL {honey}tokens, on top of AWS Lambda and Amazon API Gateway", "githubUrl": "https://github.com/0x4D31/honeyLambda", "framework": "latest", "language": "python", "platform": "aws", "authorLink": "https://github.com/0x4D31", "authorName": "0x4D31", "authorAvatar": "https://avatars.githubusercontent.com/u/11577776?v=4", "community": true }, { "title": "Faultline", "name": "faultline", "description": "Error tracking tool on AWS managed services.", "githubUrl": "https://github.com/faultline/faultline", "framework": ">=1.24.1 <2.0.0", "language": "node", "platform": "aws", "authorLink": "https://github.com/faultline", "authorName": "faultline", "authorAvatar": "https://avatars.githubusercontent.com/u/27847548?v=4", "community": true }, { "title": "Stack Overflow Monitor", "name": "stackoverflowmonitor", "description": "Monitor Stack Overflow questions and post them in a Slack channel", "githubUrl": "https://github.com/picsoung/stackoverflowmonitor", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/picsoung", "authorName": "picsoung", "authorAvatar": "https://avatars.githubusercontent.com/u/172072?v=4", "community": true }, { "title": "Serverless Analytics", "name": "serverless-analytics", "description": "Write your own Google Analytics clone and track website visitors serverless with API Gateway, Kinesis, Lambda, and DynamoDB.", "githubUrl": "https://github.com/sbstjn/serverless-analytics", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/sbstjn", "authorName": "sbstjn", "authorAvatar": "https://avatars.githubusercontent.com/u/248965?v=4", "community": true }, { "title": "Serverless + medium text to speech", "name": "serverless-medium-text-to-speech", "description": "Serverless-based, text-to-speech service for Medium articles", "githubUrl": "https://github.com/RafalWilinski/serverless-medium-text-to-speech", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/RafalWilinski", "authorName": "RafalWilinski", "authorAvatar": "https://avatars.githubusercontent.com/u/3391616?v=4", "community": true }, { "title": "Serverless + java DynamoDB imlementation example", "name": "java-lambda-dynamodb", "description": " example for java programmers that want to work with AWS-Lambda and DynamoDB", "githubUrl": "https://github.com/igorbakman/java-lambda-dynamodb", "framework": "latest", "language": "java", "platform": "aws", "authorLink": "https://github.com/igorbakman", "authorName": "igorbakman", "authorAvatar": "https://avatars.githubusercontent.com/u/4776228?v=4", "community": true }, { "title": "AWS Cognito Custom User Pool Example", "name": "aws-node-custom-user-pool", "description": "Example CloudFormation custom resource backed by a lambda using Cognito User Pools", "githubUrl": "https://github.com/bsdkurt/aws-node-custom-user-pool", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/bsdkurt", "authorName": "bsdkurt", "authorAvatar": "https://avatars.githubusercontent.com/u/4834782?v=4", "community": true }, { "title": "AWS Lambda, Amazon API Gateway, S3, DynamoDB and Cognito Example", "name": "serverless-framework-aws-lambda-amazon-api-gateway-s3-dynamodb-and-cognito", "description": "Step by step guide how to deploy simple web application on top of AWS Lambda, Amazon API Gateway, S3, DynamoDB and Cognito.", "githubUrl": "https://github.com/andreivmaksimov/serverless-framework-aws-lambda-amazon-api-gateway-s3-dynamodb-and-cognito", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/andreivmaksimov", "authorName": "andreivmaksimov", "authorAvatar": "https://avatars.githubusercontent.com/u/1902417?v=4", "community": true }, { "title": "Run your Kubernetes Workloads on Amazon EC2 Spot Instances with Amazon EKS and Lambda Part 1", "name": "aws-eks-spot-instances-serverless-framework-demo", "description": "From this tutorial you'll learn how to add AWS EKS Cluster with Spot Instances to your cloud environment managed by Serverless framework", "githubUrl": "https://github.com/andreivmaksimov/aws-eks-spot-instances-serverless-framework-demo", "framework": "latest", "language": "python", "platform": "aws", "authorLink": "https://github.com/andreivmaksimov", "authorName": "andreivmaksimov", "authorAvatar": "https://avatars.githubusercontent.com/u/1902417?v=4", "community": true }, { "title": "Serverless + lambda protobuf responses", "name": "lambda-protobuf-demo", "description": "Demo using API Gateway and Lambda with Protocol Buffer", "githubUrl": "https://github.com/theburningmonk/lambda-protobuf-demo", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/theburningmonk", "authorName": "theburningmonk", "authorAvatar": "https://avatars.githubusercontent.com/u/546969?v=4", "community": true }, { "title": "Serverless Telegram Bot", "name": "serverless-telegram-bot", "description": "This example demonstrates how to setup an echo Telegram Bot using the Serverless Framework ⚡🤖", "githubUrl": "https://github.com/jonatasbaldin/serverless-telegram-bot", "framework": "latest", "language": "python", "platform": "aws", "authorLink": "https://github.com/jonatasbaldin", "authorName": "jonatasbaldin", "authorAvatar": "https://avatars.githubusercontent.com/u/8570364?v=4", "community": true }, { "title": "Serverless + lambda + vpc + nat + redis", "name": "aws-lambda-vpc-nat-examples", "description": "Demo using API Gateway and Lambda with VPC and NAT to access Internet and AWS Resource", "githubUrl": "https://github.com/ittus/aws-lambda-vpc-nat-examples", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/ittus", "authorName": "ittus", "authorAvatar": "https://avatars.githubusercontent.com/u/5120965?v=4", "community": true }, { "title": "Serverless Gitlab CI", "name": "serverless-gitlab-ci", "description": "Simple Gitlab CI template for automatic testing and deployments", "githubUrl": "https://github.com/bvincent1/serverless-gitlab-ci", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/bvincent1", "authorName": "bvincent1", "authorAvatar": "https://avatars.githubusercontent.com/u/6344504?v=4", "community": true }, { "title": "Serverless ffmpeg", "name": "serverless-ffmpeg", "description": "Bucket event driven FFMPEG using serverless. Input bucket => Serverless ffmpeg => Output bucket.", "githubUrl": "https://github.com/kvaggelakos/serverless-ffmpeg", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/kvaggelakos", "authorName": "kvaggelakos", "authorAvatar": "https://avatars.githubusercontent.com/u/1001352?v=4", "community": true }, { "title": "Realtime WW2 Alexa Skill", "name": "realtime-ww2-alexa", "description": "An alexa skill project that's using Alexa SDK. Can also be used for a working example of serverless-webpack (with use of async/await via babel).", "githubUrl": "https://github.com/ceilfors/realtime-ww2-alexa", "framework": ">=1.22.0", "language": "node", "platform": "aws", "authorLink": "https://github.com/ceilfors", "authorName": "ceilfors", "authorAvatar": "https://avatars.githubusercontent.com/u/1336981?v=4", "community": true }, { "title": "Serverless Kakao Bot", "name": "serverless-kakao-bot", "description": "Easy development for Kakaotalk Bot with Serverless", "githubUrl": "https://github.com/JisuPark/serverless-kakao-bot", "framework": "=1.26.0", "language": "node", "platform": "aws", "authorLink": "https://github.com/JisuPark", "authorName": "JisuPark", "authorAvatar": "https://avatars.githubusercontent.com/u/4257693?v=4", "community": true }, { "title": "Personal Access Tokens Cron Check", "name": "cfpat-audit", "description": "Audit for leaked PAT in your Contentful organization. How to use serverless as cronjobs to keep your Personal Access Tokens secure", "githubUrl": "https://github.com/madtrick/cfpat-audit", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/madtrick", "authorName": "madtrick", "authorAvatar": "https://avatars.githubusercontent.com/u/48772?v=4", "community": true }, { "title": "Daily Instance Backups with AMI Rotation", "name": "AWSAutomatedDailyInstanceAMISnapshots", "description": "A simple Python application which scans through your entire AWS account for tagged instances, makes daily AMIs of them, and rotates their backups automatically", "githubUrl": "https://github.com/AndrewFarley/AWSAutomatedDailyInstanceAMISnapshots", "framework": ">=1.26.0 <2.0.0", "language": "python", "platform": "aws", "authorLink": "https://github.com/AndrewFarley", "authorName": "AndrewFarley", "authorAvatar": "https://avatars.githubusercontent.com/u/470163?v=4", "community": true }, { "title": "Serverless Instagram Crawler", "name": "serverless-instagram-crawler", "description": "Instagram hashtag Crawler with Lambda & DynamoDB.", "githubUrl": "https://github.com/kimcoder/serverless-instagram-crawler", "framework": ">=1.1.0 <2.0.0", "language": "node", "platform": "aws", "authorLink": "https://github.com/kimcoder", "authorName": "kimcoder", "authorAvatar": "https://avatars.githubusercontent.com/u/2926726?v=4", "community": true }, { "title": "Serverless Next.js Example", "name": "serverless-nextjs", "description": "Next.js example project for development & deploy.", "githubUrl": "https://github.com/kimcoder/serverless-nextjs", "framework": "latest", "language": "", "platform": "", "authorLink": "https://github.com/kimcoder", "authorName": "kimcoder", "authorAvatar": "https://avatars.githubusercontent.com/u/2926726?v=4", "community": true }, { "title": "Serving binary files", "name": "serverless-binary-files-xlsx", "description": "Small example showing how to serve binary files using Serverless on AWS with the serverless-apigw-binary plugin, using generated Excel files as an example", "githubUrl": "https://github.com/thomastoye/serverless-binary-files-xlsx", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/thomastoye", "authorName": "thomastoye", "authorAvatar": "https://avatars.githubusercontent.com/u/3396772?v=4", "community": true }, { "title": "Lambda PubSub via SNS Example", "name": "serverless-lambda-sns-example", "description": "Example illustrating the flow: Lambda (publisher) => SNS => Lambda (consumer)", "githubUrl": "https://github.com/didil/serverless-lambda-sns-example", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/didil", "authorName": "didil", "authorAvatar": "https://avatars.githubusercontent.com/u/1284255?v=4", "community": true }, { "title": "Serverless CloudWatch Proxy", "name": "cloudwatch-proxy", "description": "Logging adapter that consumes log streams from AWS CloudWatch, streams them to other log destinations. Also capable of identying alerts and sending notifications via Slack/Email", "githubUrl": "https://github.com/abbasdgr8/cloudwatch-proxy", "framework": "=1.30.0", "language": "python", "platform": "aws", "authorLink": "https://github.com/abbasdgr8", "authorName": "abbasdgr8", "authorAvatar": "https://avatars.githubusercontent.com/u/1814309?v=4", "community": true }, { "title": "Serverless side rendering with Vue.js and Nuxt.js", "name": "serverless-side-rendering-vue-nuxt", "description": "Sample project for using Nuxt.js to create a server-side rendered Vue.js app on AWS Lambda and AWS API Gateway. Can easily integrate with your own API or 3rd party APIs such as headless CMS, e-commerce or serverless architecture.", "githubUrl": "https://github.com/adnanrahic/serverless-side-rendering-vue-nuxt", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/adnanrahic", "authorName": "adnanrahic", "authorAvatar": "https://avatars.githubusercontent.com/u/15029531?v=4", "community": true }, { "title": "Aws mfa enforce", "name": "aws-mfa-enforce", "description": "Serverless function to automate enforcement of Multi-Factor Authentication (MFA) to all AWS IAM users with access to AWS Management Console.", "githubUrl": "https://github.com/Chan9390/aws-mfa-enforce", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/Chan9390", "authorName": "Chan9390", "authorAvatar": "https://avatars.githubusercontent.com/u/12944530?v=4", "community": true }, { "title": "Vanity stargazer", "name": "vanity-stargazer", "description": "Github vanity-stargazer is a serverless application to handle posting Github new star gazers to Slack", "githubUrl": "https://github.com/silvermullet/vanity-stargazer", "framework": "latest", "language": "python", "platform": "aws", "authorLink": "https://github.com/silvermullet", "authorName": "silvermullet", "authorAvatar": "https://avatars.githubusercontent.com/u/29214768?v=4", "community": true }, { "title": "Fotopia Serverless", "name": "fotopia-serverless", "description": "A photo archive web app including API, storage and face detection using serverless framework", "githubUrl": "https://github.com/mbudm/fotopia-serverless", "framework": ">=1.1.0 <2.0.0", "language": "node", "platform": "aws", "authorLink": "https://github.com/mbudm", "authorName": "mbudm", "authorAvatar": "https://avatars.githubusercontent.com/u/2467577?v=4", "community": true }, { "title": "Commenting API", "name": "serverless_typescript_graphQl_commentingService", "description": "A commenting api using Serverless Typescript GraphQl and Redis", "githubUrl": "https://github.com/AyoubEd/serverless_typescript_graphQl_commentingService", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/AyoubEd", "authorName": "AyoubEd", "authorAvatar": "https://avatars.githubusercontent.com/u/20077848?v=4", "community": true }, { "title": "Serverless node api dynamodb neo4j", "name": "serverless-node-api-dynamodb-neo4j", "description": "Architecture example to stream DynamoDB data to a read-model using Neo4j", "githubUrl": "https://github.com/noetix/serverless-node-api-dynamodb-neo4j", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/noetix", "authorName": "noetix", "authorAvatar": "https://avatars.githubusercontent.com/u/1192279?v=4", "community": true }, { "title": "Serverless python rds cron", "name": "serverless-python-rds-cron", "description": "A serverless python example that periodically removes entries from AWS RDS", "githubUrl": "https://github.com/caulagi/serverless-python-rds-cron", "framework": ">=1.2.0 <2.0.0", "language": "python", "platform": "aws", "authorLink": "https://github.com/caulagi", "authorName": "caulagi", "authorAvatar": "https://avatars.githubusercontent.com/u/222507?v=4", "community": true }, { "title": "Nietzsche", "name": "Nietzsche", "description": "A serverless application that fetches quotes from Goodreads and saves it to DynamoDB with example use cases using `Lambda`, `SNS`, `SQS`, `Step Functions`, `DynamoDB`, `API Gateway`, `CloudWatch`", "githubUrl": "https://github.com/rpidanny/Nietzsche", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/rpidanny", "authorName": "rpidanny", "authorAvatar": "https://avatars.githubusercontent.com/u/6696862?v=4", "community": true }, { "title": "Serverless DotNet BoilerPlate", "name": "serverlessDotNetSample", "description": "A serverless starter solution for .NET Core, ready for local debugging in VS Code, HTTP Endpoint, etc.", "githubUrl": "https://github.com/pharindoko/serverlessDotNetSample", "framework": "latest", "language": "csharp", "platform": "aws", "authorLink": "https://github.com/pharindoko", "authorName": "pharindoko", "authorAvatar": "https://avatars.githubusercontent.com/u/5619511?v=4", "community": true }, { "title": "Serverless Load Balancer", "name": "serverless-load-balancer", "description": "A sample that shows how to combine a load balancer with (vpc/subnet configuration) with a lambda.", "githubUrl": "https://github.com/pharindoko/serverless-load-balancer", "framework": ">=1.4.6", "language": "node", "platform": "aws", "authorLink": "https://github.com/pharindoko", "authorName": "pharindoko", "authorAvatar": "https://avatars.githubusercontent.com/u/5619511?v=4", "community": true }, { "title": "Serverless api typescript template", "name": "serverless-api-typescript-template", "description": "A starter template for a Serverless API using Typescript and Jest", "githubUrl": "https://github.com/JoshuaToth/serverless-api-typescript-template", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/JoshuaToth", "authorName": "JoshuaToth", "authorAvatar": "https://avatars.githubusercontent.com/u/10456811?v=4", "community": true }, { "title": "Serverless SNS SQS offline Example ", "name": "serverless-sns-sqs-offline-example", "description": "Minimal example of running serverless-offline with SQS and SNS in local environment.", "githubUrl": "https://github.com/kenyipp/serverless-sns-sqs-offline-example", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/kenyipp", "authorName": "kenyipp", "authorAvatar": "https://avatars.githubusercontent.com/u/24988208?v=4", "community": true }, { "title": "Serverless RDS Log Sync S3", "name": "serverless-aws-rds-logs-s3", "description": "Annotated exmaple of a periodic scheduled task to sync changed RDS log files to an S3 bucket.", "githubUrl": "https://github.com/drocco007/serverless-aws-rds-logs-s3", "framework": "latest", "language": "python", "platform": "aws", "authorLink": "https://github.com/drocco007", "authorName": "drocco007", "authorAvatar": "https://avatars.githubusercontent.com/u/1424858?v=4", "community": true }, { "title": "HTTP Headers Checks", "name": "http-headers-check", "description": "Serverless Application to check integrity of the headers of a given HTTP server", "githubUrl": "https://github.com/authdog/http-headers-check", "framework": ">=1.30.3 <3.2.0", "language": "node", "platform": "aws", "authorLink": "https://github.com/authdog", "authorName": "authdog", "authorAvatar": "https://avatars.githubusercontent.com/u/34599569?v=4", "community": true }, { "title": "Serverless Image Labeller", "name": "serverless-image-labeller", "description": "Serverless image labelling using Rekognition, s3, DynamoDB.", "githubUrl": "https://github.com/nileshprasad137/serverless-image-labeller", "framework": "latest", "language": "python", "platform": "aws", "authorLink": "https://github.com/nileshprasad137", "authorName": "nileshprasad137", "authorAvatar": "https://avatars.githubusercontent.com/u/16336390?v=4", "community": true }, { "title": "Serverless AppSync offline TypeScript with CircleCI", "name": "serverless-appsync-offline-typescript-template", "description": "A Serverless Framework template that allows you to launch an AppSync emulator locally and proceed with development. Lambda Function build by TypeScript/Webpack.", "githubUrl": "https://github.com/daisuke-awaji/serverless-appsync-offline-typescript-template", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/daisuke-awaji", "authorName": "daisuke-awaji", "authorAvatar": "https://avatars.githubusercontent.com/u/20736455?v=4", "community": true }, { "title": "Serverless Screenshot to S3", "name": "aws-node-screenshot-to-s3", "description": "An example serverless stack which takes a screenshot using aws-chrome-lambda and puts it in s3. NodeJS.", "githubUrl": "https://github.com/slaytr/aws-node-screenshot-to-s3", "framework": "2", "language": "node", "platform": "aws", "authorLink": "https://github.com/slaytr", "authorName": "slaytr", "authorAvatar": "https://avatars.githubusercontent.com/u/14005795?v=4", "community": true }, { "title": "Express Application With Lambda", "name": "serverless-lambda-express-example", "description": "This example demonstrates how to build an express application for AWS Lambda based on serverless framework.", "githubUrl": "https://github.com/HoseungJang/serverless-lambda-express-example", "framework": "latest", "language": "node", "platform": "aws", "authorLink": "https://github.com/HoseungJang", "authorName": "HoseungJang", "authorAvatar": "https://avatars.githubusercontent.com/u/39669819?v=4", "community": true }, { "title": "DropBucket - Serverless file sharing", "name": "drop-bucket", "description": "A serverless file sharing app powered by Cognito/S3/Lambda/API Gateway. Includes a React single-page app UI and virus scanning.", "githubUrl": "https://github.com/marksteele/drop-bucket", "framework": "", "language": "", "platform": "", "authorLink": "https://github.com/marksteele", "authorName": "Mark Steele", "authorAvatar": "https://avatars.githubusercontent.com/u/1577178?v=4", "community": true }, { "title": "serverless-pokego", "name": "pokego-serverless", "description": "Serverless-powered API to fetch nearby Pokemon Go data", "githubUrl": "https://github.com/jch254/pokego-serverless", "framework": "", "language": "", "platform": "", "authorLink": "https://github.com/jch254", "authorName": "Jordan Hornblow", "authorAvatar": "https://avatars.githubusercontent.com/u/3821107?v=4", "community": true }, { "title": "serverless-garden-aid", "name": "web-bff", "description": "IoT Garden Aid Backend", "githubUrl": "https://github.com/garden-aid/web-bff", "framework": "", "language": "", "platform": "", "authorLink": "https://github.com/garden-aid", "authorName": "Garden Aid", "authorAvatar": "https://avatars.githubusercontent.com/u/20978945?v=4", "community": true }, { "title": "serverless-react-boilerplate", "name": "serverless-react-boilerplate", "description": "A serverless react boilerplate for offline development", "githubUrl": "https://github.com/99xt/serverless-react-boilerplate", "framework": "", "language": "", "platform": "", "authorLink": "", "authorName": "", "authorAvatar": "", "community": true }, { "title": "serverless-delivery-framework", "name": "serverless-delivery-framework", "description": "This is a boilerplate for version release pipeline with serverless framework", "githubUrl": "https://github.com/99xt/serverless-delivery-framework", "framework": "", "language": "", "platform": "", "authorLink": "", "authorName": "", "authorAvatar": "", "community": true }, { "title": "serverless-mailgun-slack", "name": "serverless-mailgun-slack", "description": "A Serverless function for posting to a Slack Webhook in response to a Mailgun route", "githubUrl": "https://github.com/Marcus-L/serverless-mailgun-slack", "framework": "", "language": "", "platform": "", "authorLink": "https://github.com/Marcus-L", "authorName": "Marcus Lum", "authorAvatar": "https://avatars.githubusercontent.com/u/1369184?v=4", "community": true }, { "title": "serverless-AWS-Rekognition-finpics", "name": "finpics", "description": "Use AWS Rekognition to provide a faces search of finpics.com", "githubUrl": "https://github.com/rgfindl/finpics", "framework": "", "language": "", "platform": "", "authorLink": "https://github.com/rgfindl", "authorName": "Randy Findley", "authorAvatar": "https://avatars.githubusercontent.com/u/1237052?v=4", "community": true }, { "title": "jrestless-examples", "name": "jrestless-examples", "description": "JRestless (Java / JAX-RS) examples for API Gateway Functions (plain JAX-RS), Spring, binary data requests/responses, custom authorizers and Cognito User Pool authorizers), SNS Functions) (asynchronous communication between functions) and Service Functions) (synchronous HTTP-like communication between functions - transparent through Feign)", "githubUrl": "https://github.com/bbilger/jrestless-examples", "framework": "", "language": "", "platform": "", "authorLink": "https://github.com/bbilger", "authorName": "Björn Bilger", "authorAvatar": "https://avatars.githubusercontent.com/u/6827090?v=4", "community": true }, { "title": "AWS API Gateway Serverless project written in Go", "name": "serverless-golang", "description": "A serverless project that contains an API Gateway endpoint powered by a Lambda function written in golang and built using [eawsy/aws-lambda-go-shim](https://github.com/eawsy/aws-lambda-go-shim).", "githubUrl": "https://github.com/yunspace/serverless-golang", "framework": "", "language": "", "platform": "", "authorLink": "", "authorName": "", "authorAvatar": "", "community": true }, { "title": "video-preview-and-analysis-service", "name": "video-preview-and-analysis-service", "description": "An event-driven service that generates labels using Amazon Rekognition and creates preview GIF animation from a video file.", "githubUrl": "https://github.com/laardee/video-preview-and-analysis-service", "framework": "", "language": "", "platform": "", "authorLink": "https://github.com/laardee", "authorName": "Eetu Tuomala", "authorAvatar": "https://avatars.githubusercontent.com/u/4726921?v=4", "community": true }, { "title": "Serverless ES6/7 CRUD API", "name": "serverless-stack-demo-api", "description": "Serverless Stack examples of backend CRUD APIs (DynamoDB + Lambda + API Gateway + Cognito User Pool authorizer) for React.js single-page app", "githubUrl": "https://github.com/AnomalyInnovations/serverless-stack-demo-api", "framework": "", "language": "", "platform": "", "authorLink": "https://github.com/AnomalyInnovations", "authorName": "Anomaly Innovations", "authorAvatar": "https://avatars.githubusercontent.com/u/22177954?v=4", "community": true }, { "title": "AWS Lambda Power Tuning (powered by Step Functions)", "name": "aws-lambda-power-tuning", "description": "Build a Step Functions state machine to optimize your AWS Lambda Function memory/power configuration.", "githubUrl": "https://github.com/alexcasalboni/aws-lambda-power-tuning", "framework": "", "language": "", "platform": "", "authorLink": "https://github.com/alexcasalboni", "authorName": "Alex Casalboni", "authorAvatar": "https://avatars.githubusercontent.com/u/2457588?v=4", "community": true }, { "title": "React & Stripe Serverless Ecommerce", "name": "serverless-shop", "description": "Serverless E-Commerce App with AWS Lambda, Stripe and React", "githubUrl": "https://github.com/patrick-michelberger/serverless-shop", "framework": "", "language": "", "platform": "", "authorLink": "https://github.com/patrick-michelberger", "authorName": "Patrick Michelberger", "authorAvatar": "https://avatars.githubusercontent.com/u/1750000?v=4", "community": true }, { "title": "Run your Kubernetes Workloads on Amazon EC2 Spot Instances with Amazon EKS and Lambda - Part 2", "name": "aws-eks-spot-instances-serverless-framework-demo", "description": "From this article you'll learn how to configure AWS Lambda functions to allow them manage your EKS Kubernetes cluster and run triggered jobs", "githubUrl": "https://github.com/andreivmaksimov/aws-eks-spot-instances-serverless-framework-demo/tree/part2", "framework": "", "language": "", "platform": "", "authorLink": "https://github.com/andreivmaksimov", "authorName": "Andrei Maksimov", "authorAvatar": "https://avatars.githubusercontent.com/u/1902417?v=4", "community": true }, { "title": "Serverless Dashboard For Atom Editor", "name": "serverless-dashboard-for-atom", "description": "Atom editor package which allows you to deploy and visualize your serverless services with Serverless Framework on your editor.", "githubUrl": "https://github.com/horike37/serverless-dashboard-for-atom", "framework": "", "language": "", "platform": "", "authorLink": "https://github.com/horike37", "authorName": "Takahiro Horike", "authorAvatar": "https://avatars.githubusercontent.com/u/1301012?v=4", "community": true }, { "title": "Serverless SSH Command", "name": "serverless-openwhisk-ssh", "description": "Example of executing ssh command with OpenWhisk", "githubUrl": "https://github.com/upgle/serverless-openwhisk-ssh", "framework": "", "language": "", "platform": "", "authorLink": "https://github.com/upgle", "authorName": "Seonghyun Oh", "authorAvatar": "https://avatars.githubusercontent.com/u/5635513?v=4", "community": true }, { "title": "JSON-Serverless", "name": "json-serverless", "description": "A simple & cheap serverless REST API using [json-server](https://github.com/typicode/json-server) in combination with AWS Lambda / S3 and the serverless framework", "githubUrl": "https://github.com/pharindoko/json-serverless", "framework": "", "language": "", "platform": "", "authorLink": "https://github.com/pharindoko", "authorName": "Florian Fuß", "authorAvatar": "https://avatars.githubusercontent.com/u/5619511?v=4", "community": true }, { "title": "[Unly] Boilerplates Generator", "name": "boilerplates-generator", "description": "A boilerplates generator, meant to help to quick-start Serverless (AWS Lambda/API GW) and OSS projects, using good defaults _(sentry for automated error handling, staging/prod environments, built-in support for env vars, jest support, babel/webpack)_, yet flexible to fit your needs.", "githubUrl": "https://github.com/UnlyEd/boilerplates-generator", "framework": "", "language": "", "platform": "", "authorLink": "https://github.com/UnlyEd", "authorName": "Unly", "authorAvatar": "https://avatars.githubusercontent.com/u/44462742?v=4", "community": true }, { "title": "Demo project for serverless-migrate-plugin", "name": "serverless-migrate-plugin", "description": "An example about how to use migrations in your serverless project with serverless-migrate-plugin", "githubUrl": "https://github.com/EliuX/serverless-migrate-plugin/tree/master/example", "framework": "", "language": "", "platform": "", "authorLink": "https://github.com/EliuX", "authorName": "Eliecer Hernandez Garbey", "authorAvatar": "https://avatars.githubusercontent.com/u/6514740?v=4", "community": true }, { "title": "GoLive", "name": "golive", "description": "Boilerplate to live stream using AWS MediaLive and MediaStore", "githubUrl": "https://github.com/adimoraret/golive/", "framework": "", "language": "", "platform": "", "authorLink": "https://github.com/adimoraret", "authorName": "Adrian Moraret", "authorAvatar": "https://avatars.githubusercontent.com/u/18240385?v=4", "community": true }, { "title": "Idempotent Serverless Functions", "name": "idempotent-serverless-functions", "description": "This repository demonstrates how to ensure the idempotence of serverless functions running on AWS Lambda.", "githubUrl": "https://github.com/Nsupyq/idempotent-serverless-functions", "framework": "", "language": "", "platform": "", "authorLink": "https://github.com/Nsupyq", "authorName": "Nsupyq", "authorAvatar": "https://avatars.githubusercontent.com/u/93358967?v=4", "community": true }, { "title": "File uploads using S3 presigned URLs", "name": "aws-node-serverless-upload-presigned-url", "description": "A Serverless photo upload service with API Gateway, S3 presigned URLs and Lambda.", "githubUrl": "https://github.com/marchetti2/aws-node-serverless-upload-presigned-url", "framework": "", "language": "", "platform": "", "authorLink": "https://github.com/marchetti2", "authorName": "Mário Luiz", "authorAvatar": "https://avatars.githubusercontent.com/u/35637518?v=4", "community": true }, { "title": "Monorepo Typescript microservices", "name": "serverless-monorepo-microservices-template", "description": "An opinionated Serverless template with several Typescript microservices in a monorepo", "githubUrl": "https://github.com/fargito/serverless-monorepo-microservices-template", "framework": "", "language": "", "platform": "", "authorLink": "https://github.com/fargito", "authorName": "François Farge", "authorAvatar": "https://avatars.githubusercontent.com/u/29537204?v=4", "community": true }, { "title": "Serverless Python Twitch EventSub to Discord Webhook on AWS", "name": "aws-python-twitch-eventsub-to-discord-webhook", "description": "This template takes go-live events from Twitch EventSub, and publishes the events through a Discord webhook", "githubUrl": "https://github.com/dylmye/aws-python-twitch-eventsub-to-discord-webhook", "framework": "", "language": "", "platform": "", "authorLink": "https://github.com/dylmye", "authorName": "Dylan Myers", "authorAvatar": "https://avatars.githubusercontent.com/u/7024578?v=4", "community": true }, { "title": "Serverless Compose of Serverless, Cloudformation, and SAM", "name": "compose-multiframework", "description": "This template shows how to compose multiple services using different frameworks, in a single project", "githubUrl": "https://github.com/serverless/examples/tree/master/compose-multiframework", "framework": "", "language": "node", "platform": "aws", "authorLink": "https://github.com/eahefnawy", "authorName": "Eslam λ Hefnawy", "authorAvatar": "https://avatars.githubusercontent.com/u/2312463?v=4" } ] ================================================ FILE: generate-readme.js ================================================ const fs = require("fs"); const path = require("path"); const url = require("url"); const markdownMagic = require("markdown-magic"); // eslint-disable-line const toTitleCase = (str) => str.replace( /\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase() ); const formatPluginName = (string) => toTitleCase(string.replace(/-/g, " ")); const username = (repo) => { if (!repo) { return null; } const o = url.parse(repo); var urlPath = o.path; // eslint-disable-line if (urlPath.length && urlPath.charAt(0) === "/") { urlPath = urlPath.slice(1); } urlPath = urlPath.split("/")[0]; return urlPath; }; const getRuntime = (dirname) => { if (!dirname) { return "unknown"; } if (dirname.match(/node/)) { return "nodeJS"; } else if (dirname.match(/python/)) { return "python"; } else if (dirname.match(/swift/)) { return "swift"; } else if (dirname.match(/php/)) { return "php"; } else if (dirname.match(/ruby/)) { return "ruby"; } else if (dirname.match(/golang/)) { return "golang"; } else if (dirname.match(/dotnet/)) { return "dotnet"; } return "unknown"; }; const config = { transforms: { /* In README.md the below comment block adds the list to the readme plugin list will be generated here */ SERVERLESS_EXAMPLE_TABLE() { const examplesPath = path.join(__dirname, "examples.json"); const examples = JSON.parse(fs.readFileSync(examplesPath, "utf8")); // Make table header let md = "| Example | Runtime |\n"; md += "|:--------------------------- |:-----|\n"; examples.forEach((example) => { const dirname = example.dirname || ""; const exampleUrl = `https://github.com/serverless/examples/tree/master/${dirname}`; const runtime = getRuntime(dirname); const description = example.description ? `
${example.description}` : ""; // add table rows md += `| [${ example.title || formatPluginName(example.name) }](${exampleUrl}) ${description} | ${runtime} |\n`; }); return md; }, }, }; const markdownPath = path.join(__dirname, "README.md"); markdownMagic(markdownPath, config, () => { console.log("Docs updated!"); // eslint-disable-line }); ================================================ FILE: google-golang-simple-http-endpoint/.gcloudignore ================================================ # This file specifies files that are *not* uploaded to Google Cloud Platform # using gcloud. It follows the same syntax as .gitignore, with the addition of # "#!include" directives (which insert the entries of the given .gitignore-style # file at that point). # # For more information, run: # $ gcloud topic gcloudignore # .gcloudignore # If you would like to upload your .git directory, .gitignore file or files # from your .gitignore file, remove the corresponding line # below: .git .gitignore node_modules .serverless ================================================ FILE: google-golang-simple-http-endpoint/README.md ================================================ # Simple HTTP Endpoint Example This example demonstrates how to setup a simple golang HTTP GET endpoint. When you fetch the endpoint we've set up here you'll see the time returned for the given request type. ## Use Cases - Wrapping an existing internal or external endpoint/service ## Development The GCF golang runtime has a few requirements when defining your solution, namely: - Your function must be exported (e.g `Hello`) - You must define a non-main package, which can have an arbitrary name: `package p` - Your function code may not contain `package main` or a `func main()` Since this is an alpha runtime provided by GCF the docs are not yet available for consumption. We'll update this README once those instructions are made public. ## Deploy In order to deploy the you endpoint simply run ```bash serverless deploy -v ``` The expected result should be similar to: ```bash Serverless: Uploading artifacts... Serverless: Artifacts successfully uploaded... Serverless: Updating deployment... Serverless: Checking deployment update progress... .............. Serverless: Done... Service Information service: golang-simple-http-endpoint project: stage: dev region: Deployed functions currentTime https://-.cloudfunctions.net/endpoint ``` ## Usage You can now invoke the Cloud Function directly and even see the resulting log via ```bash serverless invoke --function hello ``` The expected result should be similar to: ```bash Serverless: 6xthowrso4u2 {"message":"Go Serverless v1! Your function executed hello successfully!"} ``` And to check out the logs directly from sls, you can run the following: ```bash serverless logs --function hello ... Serverless: Displaying the 10 most recent log(s): 2018-11-21T17:44:58.450200631Z: Function execution took 7 ms, finished with status code: 200 2018-11-21T17:44:58.443618562Z: Function execution started 2018-11-21T17:43:40.651063187Z: Function execution took 8 ms, finished with status code: 200 2018-11-21T17:43:40.644123421Z: Function execution started 2018-11-21T17:43:35.688702419Z: Function execution took 25 ms, finished with status code: 200 2018-11-21T17:43:35.664212161Z: Function execution started 2018-11-21T17:40:46.166468516Z: Function execution took 6 ms, finished with status code: 200 2018-11-21T17:40:46.161422666Z: Function execution started 2018-11-21T17:33:27.004019351Z: Function execution took 10 ms, finished with status code: 200 2018-11-21T17:33:26.994600775Z: Function execution started ``` Finally you can send an HTTP request directly to the endpoint using a tool like curl: ```bash curl https://-.cloudfunctions.net/Hello ``` **NOTE:** notice that the request terminates with your golang exported function name, not the serverless function name you've defined. ================================================ FILE: google-golang-simple-http-endpoint/hello.go ================================================ package hello import ( "bytes" "encoding/json" "net/http" ) // Hello is a simple HTTP handler that addresses HTTP requests to the /hello endpoint func Hello(w http.ResponseWriter, r *http.Request) { var buf bytes.Buffer body, err := json.Marshal(map[string]interface{}{ "message": "Go Serverless v1! Your function executed hello successfully!", }) if err != nil { w.WriteHeader(http.StatusNotFound) w.Write([]byte(err.Error())) return } json.HTMLEscape(&buf, body) w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json") w.Header().Set("MyCompany-Func-Reply", "hello-handler") w.Write(buf.Bytes()) } ================================================ FILE: google-golang-simple-http-endpoint/package.json ================================================ { "name": "google-golang-simple-http-endpoint", "version": "0.0.1", "description": "Example demonstrates how to setup a simple HTTP GET endpoint with golang", "author": "Sebastian Borza ", "license": "MIT", "main": "handler.py", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { "serverless-google-cloudfunctions": "^2.1.0" } } ================================================ FILE: google-golang-simple-http-endpoint/serverless.yml ================================================ service: golang-simple-http-endpoint frameworkVersion: ">=1.33.0 <2.0.0" package: exclude: - node_modules/** - .gitignore - .git/** plugins: - serverless-google-cloudfunctions # The GCF credentials can be a little tricky to set up. Luckily we've documented this for you here: # https://serverless.com/framework/docs/providers/google/guide/credentials/ # # NOTE: the golang runtime is currently in alpha state, you must have access from google to use the alpha toolchain provider: name: google runtime: go111 # currently both vendored and go.mod repos are supported project: sborza-91 # replace with your project name here credentials: ~/.gcloud/slsframework.json # path must be absolute, change to whichever keyfile you need functions: hello: handler: Hello events: - http: path ================================================ FILE: google-node-simple-http-endpoint/README.md ================================================ # Pre-request Follow below link to setup google account for deploying your first serveless application. [Google account setup](https://www.serverless.com/framework/docs/providers/google/guide/credentials/) # Simple HTTP example # Setup 1. Install Serverless with `npm install -g serverless` 2. Install the dependencies `npm install` # Setting the credentials and project Update the `credentials` and your `project` property in the `serverless.yml` file. # Deployment ``` serverless deploy ``` You should see your functions URL endpoint after the deployment # Invoking ``` curl ``` # Caveats # Below are the errors will occure during serveless deploy 1. Enable Cloud Deployment Manager V2 API - make sure Cloud Deployment V2 API is enabled other wise you will get below error during serverless deployment ``` Error: Cloud Deployment Manager V2 API has not been used in project before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/deploymentmanager.googleapis.com/overview?project= then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry. ``` 2. Enable Cloud Functions API - make sure Cloud Function API is enabled. Other wise you will get below error. ``` {"ResourceType":"cloudfunctions.v1beta2.function","ResourceErrorCode":"403","ResourceErrorMessage":{"code":403,"message":"Cloud Functions API has not been used in project before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/cloudfunctions.googleapis.com/overview?project= then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.","status":"PERMISSION_DENIED" ``` 3. sls deploy --region us-central1 - provide region during sls or serverless deployment , other wise below rest endpoints will be created for your handler , example outof servlerless deployment console. ``` Service Information service: node-simple-http-endpoint project: stage: dev region: undefined Deployed functions helloWorld https://undefined-.cloudfunctions.net/http ``` ================================================ FILE: google-node-simple-http-endpoint/index.js ================================================ 'use strict'; exports.http = (request, response) => { response.status(200).send('Hello World!'); }; ================================================ FILE: google-node-simple-http-endpoint/package.json ================================================ { "name": "google-node-simple-http-endpoint", "version": "0.1.0", "description": "An example of making http endpoints with the Google Cloud Functions Serverless Framework plugin.", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "serverless.com", "license": "MIT", "dependencies": { "serverless-google-cloudfunctions": "^2.3.3" } } ================================================ FILE: google-node-simple-http-endpoint/serverless.yml ================================================ service: node-simple-http-endpoint # NOTE: Don't put the word "google" in here provider: name: google runtime: 'nodejs8' project: my-project-1234 #google account project name credentials: ~/.gcloud/keyfile.json # path must be absolute plugins: - serverless-google-cloudfunctions package: exclude: - node_modules/** - .gitignore - .git/** functions: helloWorld: handler: http events: - http: path ================================================ FILE: google-node-typescript-http-endpoint/README.md ================================================ # gcp-node-typescript-simple Simple HTTP example for GCP functions by Serverless framework with Typescript ## Credentials - [Google Cloud Platform credential](https://serverless.com/framework/docs/providers/google/guide/credentials/) ## Get Started ### install dependencies ``` $ npm i ``` ### deploy ``` $ serverless deploy ``` ================================================ FILE: google-node-typescript-http-endpoint/package.json ================================================ { "name": "gcp-node-typescript-simple", "version": "0.1.0", "description": "Simple HTTP example for GCP functions by Serverless framework with Typescript", "main": "/src/App.ts", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "serverless.com", "license": "MIT", "devDependencies": { "awesome-typescript-loader": "^5.2.1", "serverless-google-cloudfunctions": "^1.2.0", "typescript": "^3.1.6", "@types/node": "^7.0.5" }, "dependencies": {} } ================================================ FILE: google-node-typescript-http-endpoint/serverless.yml ================================================ service: gcp-node-typescript-simple # NOTE: Don't put the word "google" in here provider: name: google runtime: nodejs project: gcp-node-typescript-simple # the path to the credentials file needs to be absolute credentials: ~/.gcloud/keyfile.json plugins: - serverless-google-cloudfunctions # needs more granular excluding in production as only the serverless provider npm # package should be excluded (and not the whole node_modules directory) package: include: - ./src/App.ts exclude: - node_modules/** - .gitignore - .git/** functions: first: handler: http events: - http: path # NOTE: the following uses an "event" event (pubSub event in this case). # Please create the corresponding resources in the Google Cloud # before deploying this service through Serverless #second: # handler: event # events: # - event: # eventType: providers/cloud.pubsub/eventTypes/topic.publish # resource: projects/*/topics/my-topic # you can define resources, templates etc. the same way you would in a # Google Cloud deployment configuration #resources: # resources: # - type: storage.v1.bucket # name: my-serverless-service-bucket # imports: # - path: my_template.jinja ================================================ FILE: google-node-typescript-http-endpoint/src/App.ts ================================================ 'use strict'; exports.http = (request, response) => { response.status(200).send("Hello World! Let's start Typescript!!"); }; exports.event = (event, callback) => { callback(); }; ================================================ FILE: google-node-typescript-http-endpoint/tsconfig.json ================================================ { "compilerOptions": { "target": "es6", "module": "commonjs", }, "include": [ "src/**/*.ts" ], "exclude": [ "node_modules" ], "types": [ "node", ] } ================================================ FILE: google-python-simple-http-endpoint/README.md ================================================ # Simple HTTP Endpoint Example This example demonstrates how to setup a simple python HTTP GET endpoint. When you fetch the endpoint we've set up here you'll see the time returned for the given request type. ## Use Cases - Wrapping an existing internal or external endpoint/service ## Development The GCF python runtime has a few requirements when defining your solution, in particular both a `requirements.txt` and your initial handler living in `main.py`. More details are listed here: https://cloud.google.com/functions/docs/concepts/python-runtime ## Deploy In order to deploy the you endpoint simply run ```bash serverless deploy -v ``` The expected result should be similar to: ```bash Serverless: Uploading artifacts... Serverless: Artifacts successfully uploaded... Serverless: Updating deployment... Serverless: Checking deployment update progress... .............. Serverless: Done... Service Information service: python-simple-http-endpoint project: stage: dev region: Deployed functions currentTime https://-.cloudfunctions.net/endpoint ``` ## Usage You can now invoke the Cloud Function directly and even see the resulting log via ```bash serverless invoke --function currentTime ``` The expected result should be similar to: ```bash Serverless: tudkstajertt { "statusCode": 200, "body": { "message": "Received a POST request at 17:44:58.448696" } } ``` And to check out the logs directly from sls, you can run the following: ```bash serverless logs --function currentTime ... Serverless: Displaying the 10 most recent log(s): 2018-11-21T17:44:58.450200631Z: Function execution took 7 ms, finished with status code: 200 2018-11-21T17:44:58.443618562Z: Function execution started 2018-11-21T17:43:40.651063187Z: Function execution took 8 ms, finished with status code: 200 2018-11-21T17:43:40.644123421Z: Function execution started 2018-11-21T17:43:35.688702419Z: Function execution took 25 ms, finished with status code: 200 2018-11-21T17:43:35.664212161Z: Function execution started 2018-11-21T17:40:46.166468516Z: Function execution took 6 ms, finished with status code: 200 2018-11-21T17:40:46.161422666Z: Function execution started 2018-11-21T17:33:27.004019351Z: Function execution took 10 ms, finished with status code: 200 2018-11-21T17:33:26.994600775Z: Function execution started ``` Finally you can send an HTTP request directly to the endpoint using a tool like curl ```bash curl https://-.cloudfunctions.net/endpoint ``` ================================================ FILE: google-python-simple-http-endpoint/main.py ================================================ """GCP HTTP Cloud Function Example.""" # -*- coding: utf-8 -*- import json import datetime def endpoint(request): """GCP HTTP Cloud Function Example. Args: request (flask.Request) Returns: The response text, or any set of values that can be turned into a Response object using `make_response` . """ current_time = datetime.datetime.now().time() body = { "message": "Received a {} request at {}".format(request.method, str(current_time)) } response = { "statusCode": 200, "body": body } return json.dumps(response, indent=4) ================================================ FILE: google-python-simple-http-endpoint/package.json ================================================ { "name": "google-python-simple-http-endpoint", "version": "0.0.1", "description": "Example demonstrates how to setup a simple HTTP GET endpoint with python", "author": "Sebastian Borza ", "license": "MIT", "main": "handler.py", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { "serverless-google-cloudfunctions": "^2.1.0" } } ================================================ FILE: google-python-simple-http-endpoint/requirements.txt ================================================ ================================================ FILE: google-python-simple-http-endpoint/serverless.yml ================================================ service: python-simple-http-endpoint frameworkVersion: ">=1.2.0 <2.0.0" package: exclude: - node_modules/** - .gitignore - .git/** plugins: - serverless-google-cloudfunctions provider: name: google runtime: python37 project: credentials: ~/.gcloud/keyfile.json # path must be absolute functions: currentTime: handler: endpoint events: - http: path ================================================ FILE: google-ruby-simple-http-endpoint/Gemfile ================================================ source "https://rubygems.org" ffargs = if ::File.directory? "vendor/functions_framework" { path: "vendor/functions_framework" } else {} end gem "functions_framework", **ffargs ================================================ FILE: google-ruby-simple-http-endpoint/README.md ================================================ # Simple HTTP Endpoint Example This example demonstrates how to setup a simple Ruby HTTP GET endpoint. ## Use Cases - Wrapping an existing internal or external endpoint/service ## Development The GCF Ruby environment has the following requirements: - The code to be invoked must be defined in the `app.rb` file using `FunctionsFramework` - Any dependencies must be specified in the `Gemfile` https://cloud.google.com/functions/docs/concepts/ruby-runtime https://github.com/GoogleCloudPlatform/functions-framework-ruby ## Deploy Run the following to deploy: ```bash serverless deploy ``` ================================================ FILE: google-ruby-simple-http-endpoint/app.rb ================================================ # app.rb require "functions_framework" FunctionsFramework.http("hello") do |request| "Hello, world!\n" end ================================================ FILE: google-ruby-simple-http-endpoint/package.json ================================================ { "name": "google-ruby-simple-http-endpoint", "version": "0.0.1", "description": "Example demonstrates how to setup a simple HTTP GET endpoint with Ruby", "author": "Allie Coleman ", "license": "MIT", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "devDependencies": { "serverless-google-cloudfunctions": "^4.5.0" } } ================================================ FILE: google-ruby-simple-http-endpoint/serverless.yml ================================================ service: ruby-simple-http-endpoint frameworkVersion: ">=2.70.0" package: exclude: - node_modules/** - .gitignore - .git/** plugins: - serverless-google-cloudfunctions provider: name: google runtime: ruby27 project: l credentials: ~/.config/gcloud/creds.json # path must be absolute functions: simpleGet: handler: hello events: - http: path ================================================ FILE: kubeless-python-simple-function/README.md ================================================ # Serverless Boilerplate - Kubeless - Python Make sure `kubeless` and `serverless` are installed. See the respective installation guides: * [Kubeless](https://github.com/kubeless/kubeless/blob/master/README.md#usage) * [Serverless](https://github.com/serverless/serverless#quick-start) Please see the [this guide for more information](https://github.com/serverless/serverless-kubeless/blob/master). ## 1. Install Service Dependencies Run `npm install` in this directory to download the modules from `package.json`. ## 2. Deploy Run `serverless deploy` in order to deploy the function defined in `serverless.yml` ```bash $ serverless deploy Serverless: Packaging service... Serverless: Deploying function: hello... Serverless: Function hello succesfully deployed ``` ## 3. Invoke deployed function Run `serverless invoke --function hello --log --data "Bob"` In your terminal window you should see the response from Kubernetes. ```bash $ sls invoke --function hello --data 'Bob' --log Serverless: Calling function: hello... -------------------------------------------------------------------- Hello Bob! ``` **For more information on the Serverless Kubeless plugin, please see the project repository: [https://github.com/serverless/serverless-kubeless](https://github.com/serverless/serverless-kubeless).** ================================================ FILE: kubeless-python-simple-function/handler.py ================================================ def hello(event, context): return "Hello " + event['data'] + "!" ================================================ FILE: kubeless-python-simple-function/package.json ================================================ { "name": "kubeless-python-simple-function", "version": "0.0.1", "description": "This example demonstrates how to setup a simple Python function with Kubeless", "dependencies": { "bluebird": "^3.5.0", "kubernetes-client": "^3.10.1", "serverless-kubeless": "^0.4.0" }, "main": "handler.py", "autor": "Bitnami", "license": "ASL" } ================================================ FILE: kubeless-python-simple-function/serverless.yml ================================================ service: python-hello provider: name: kubeless runtime: python2.7 plugins: - serverless-kubeless functions: hello: handler: handler.hello ================================================ FILE: kubeless-python-simple-scheduled-function/README.md ================================================ # Serverless Boilerplate - Kubeless - Python Make sure `kubeless` and `serverless` are installed. See the respective installation guides: * [Kubeless](https://github.com/kubeless/kubeless/blob/master/README.md#usage) * [Serverless](https://github.com/serverless/serverless#quick-start) Please see the [this guide for more information](https://github.com/serverless/serverless-kubeless). ## 1. Install Service Dependencies Run `npm install` in this directory to download the modules from `package.json`. ## 2. Setting the schedule You can check that we are setting the function schedule in the `serverless.yml` file. This schedule should follow the Cron notation. In this example we are setting it to `* * * * *` for the function to be executed every minute. ## 3. Deploy Run `serverless deploy` in order to deploy the function defined in `serverless.yml` ```bash $ serverless deploy Serverless: Packaging service... Serverless: Deploying function clock... Serverless: Function clock successfully deployed ``` ## 3. Check the function logs Run `serverless logs --function clock` In your terminal window you should see the consecutive executions of the scheduled function: ```bash $ sls logs -f clock Bottle v0.12.13 server starting up (using CherryPyServer())... Listening on http://0.0.0.0:8080/ Hit Ctrl-C to quit. 172.17.0.1 - - [26/Sep/2017:10:25:27 +0000] "GET /healthz HTTP/1.1" 200 2 "" "Go-http-client/1.1" 0/153 172.17.0.1 - - [26/Sep/2017:10:25:41 +0000] "GET /healthz HTTP/1.1" 200 2 "" "Go-http-client/1.1" 0/96 10:26 172.17.0.10 - - [26/Sep/2017:10:26:04 +0000] "GET / HTTP/1.1" 200 5 "" "Wget" 0/1647 172.17.0.1 - - [26/Sep/2017:10:26:11 +0000] "GET /healthz HTTP/1.1" 200 2 "" "Go-http-client/1.1" 0/95 172.17.0.1 - - [26/Sep/2017:10:26:41 +0000] "GET /healthz HTTP/1.1" 200 2 "" "Go-http-client/1.1" 0/100 10:27 ``` **For more information on the Serverless Kubeless plugin, please see the project repository: [https://github.com/serverless/serverless-kubeless](https://github.com/serverless/serverless-kubeless).** ================================================ FILE: kubeless-python-simple-scheduled-function/handler.py ================================================ from datetime import datetime def printClock(event, context): now = datetime.now() clock = "%02d:%02d" % (now.hour,now.minute) print clock return clock ================================================ FILE: kubeless-python-simple-scheduled-function/package.json ================================================ { "name": "kubeless-python-simple-scheduled-function", "version": "0.0.1", "description": "This example demonstrates how to setup a simple Python function with Kubeless", "dependencies": { "bluebird": "^3.5.0", "kubernetes-client": "^3.10.1", "serverless-kubeless": "^0.4.0" }, "main": "handler.py", "autor": "Bitnami", "license": "ASL" } ================================================ FILE: kubeless-python-simple-scheduled-function/serverless.yml ================================================ service: python-clock provider: name: kubeless runtime: python2.7 plugins: - serverless-kubeless functions: clock: handler: handler.printClock events: - schedule: '* * * * *' ================================================ FILE: openwhisk-go-simple/.gitignore ================================================ .serverless *.pyc *.pyo ================================================ FILE: openwhisk-go-simple/README.md ================================================ # Serverless Boilerplate - OpenWhisk - Go Make sure `serverless` is installed. [See installation guide](https://serverless.com/framework/docs/providers/openwhisk/guide/installation/). You will also need to set up your OpenWhisk account credentials using environment variables or a configuration file. Please see the [this guide for more information](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/). ## 1. Install Project Dependencies `npm install` in this directory to download the modules from `package.json`. ## 2. Compile Go Binary ``` $ env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -ldflags '-s -w -extldflags "-static"' handler.go ``` ## 3. Deploy `serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command ``` Serverless: Packaging service... Serverless: Compiling Functions... Serverless: Compiling API Gateway definitions... Serverless: Compiling Rules... Serverless: Compiling Triggers & Feeds... Serverless: Deploying Functions... Serverless: Deployment successful! Service Information platform: openwhisk.ng.bluemix.net namespace: _ service: go-service actions: go-service-dev-greeting ``` ## 4. Invoke deployed function `serverless invoke --function greeting` or `serverless invoke -f greeting` `-f` is shorthand for `--function` In your terminal window you should see the response from Apache OpenWhisk ```bash $ serverless invoke -f greeting { "msg": "Hello stranger!" } $ serverless invoke -f greeting -d '{"name": "James"}' { "msg": "Hello James!" } ``` **For more information on the Serverless OpenWhisk plugin, please see the project repository: [https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/).** ================================================ FILE: openwhisk-go-simple/handler.go ================================================ package main import "encoding/json" import "fmt" import "os" func main() { // native actions receive one argument, the JSON object as a string arg := os.Args[1] // unmarshal the string to a JSON object var obj map[string]interface{} json.Unmarshal([]byte(arg), &obj) name, ok := obj["name"].(string) if !ok { name = "Stranger" } msg := map[string]string{"msg": ("Hello, " + name + "!")} res, _ := json.Marshal(msg) fmt.Println(string(res)) } ================================================ FILE: openwhisk-go-simple/package.json ================================================ { "name": "openwhisk-go-simple", "version": "0.1.0", "description": "Example demonstrates how to setup a simple Go function with OpenWhisk.", "dependencies": { "serverless-openwhisk": ">=0.13.0" } } ================================================ FILE: openwhisk-go-simple/serverless.yml ================================================ service: go-service provider: name: openwhisk runtime: binary functions: greeting: handler: handler plugins: - serverless-openwhisk ================================================ FILE: openwhisk-node-and-docker-chaining-functions/.gitignore ================================================ node_modules .serverless ================================================ FILE: openwhisk-node-and-docker-chaining-functions/README.md ================================================ # Serverless Boilerplate - OpenWhisk - Node.js & Docker Make sure `serverless` is installed. [See installation guide](https://serverless.com/framework/docs/providers/openwhisk/guide/installation/). You will also need to set up your OpenWhisk account credentials using environment variables or a configuration file. Please see the [this guide for more information](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/). ## 1. Install Service Dependencies & Provider Plugin `npm install` in this directory to download the modules from `package.json`. ## 2. Deploy `serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command ``` $ serverless deploy Serverless: Packaging service... Serverless: Compiling Functions... Serverless: Compiling API Gateway definitions... Serverless: Compiling Rules... Serverless: Compiling Triggers & Feeds... Serverless: Deploying Functions... Serverless: Deploying Sequences... Serverless: Deployment successful! Service Information platform: openwhisk.ng.bluemix.net namespace: _ service: testing actions: testing-dev-location_sunrise_sunset testing-dev-sunrise_sunset testing-dev-location_from_address testing-dev-jq ``` ## 3. Invoke sequence function `serverless invoke -f location_sunrise_sunset -d '{"address": "london"}'` `-f` is also shorthand for `--function` In your terminal window you should see the response from Apache OpenWhisk ```bash { "results": { "astronomical_twilight_end": "8:26:54 PM", "day_length": "12:48:55", "civil_twilight_begin": "5:06:55 AM", "solar_noon": "12:05:03 PM", "sunrise": "5:40:36 AM", "civil_twilight_end": "7:03:11 PM", "sunset": "6:29:31 PM", "nautical_twilight_end": "7:43:44 PM", "astronomical_twilight_begin": "3:43:12 AM", "nautical_twilight_begin": "4:26:22 AM" }, "status": "OK" } ``` **For more information on the Serverless OpenWhisk plugin, please see the project repository: [https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/).** ================================================ FILE: openwhisk-node-and-docker-chaining-functions/handler.js ================================================ 'use strict'; /* eslint-disable import/no-extraneous-dependencies */ const request = require('request'); function http(url, qs) { const options = { url, qs, json: true, }; return new Promise((resolve, reject) => { request(options, (err, resp) => { if (err) { console.log(err); return reject({ err }); } if (resp.body.status !== 'OK') { console.log(resp.body.status); return reject({ err: resp.body.status }); } return resolve(resp.body); }); }); } function locationFromAddress(params) { if (!params.address) return Promise.reject('Missing mandatory property: address'); return http('https://maps.googleapis.com/maps/api/geocode/json', { address: params.address }); } function sunriseSunset(params) { if (!params.lat) return Promise.reject('Missing mandatory property: lat'); if (!params.lng) return Promise.reject('Missing mandatory property: lng'); return http('http://api.sunrise-sunset.org/json', { lat: params.lat, lng: params.lng }); } module.exports.locationFromAddress = locationFromAddress; module.exports.sunriseSunset = sunriseSunset; ================================================ FILE: openwhisk-node-and-docker-chaining-functions/package.json ================================================ { "name": "openwhisk-node-and-docker-chaining-functions", "version": "0.1.0", "description": "Example of chaining function calls using sequences and docker images.", "dependencies": { "serverless-openwhisk": ">=0.13.0" } } ================================================ FILE: openwhisk-node-and-docker-chaining-functions/serverless.yml ================================================ # Welcome to Serverless! # # This file is the main config file for your service. # It's very minimal at this point and uses default values. # You can always add more config options for more control. # We've included some commented out config examples here. # Just uncomment any of them to get that config option. # # For full config options, check the docs: # docs.serverless.com # # Happy Coding! service: testing provider: name: openwhisk functions: location_from_address: handler: handler.locationFromAddress sunrise_sunset: handler: handler.sunriseSunset jq: handler: jamesthomas/openwhisk-jq runtime: docker parameters: jq: '.results[0].geometry.location' location_sunrise_sunset: sequence: - location_from_address - jq - sunrise_sunset # remember to run npm install to download the provider plugin. plugins: - serverless-openwhisk ================================================ FILE: openwhisk-node-chaining-functions/.gitignore ================================================ node_modules .serverless ================================================ FILE: openwhisk-node-chaining-functions/README.md ================================================ # Serverless Boilerplate - OpenWhisk - Node.js Make sure `serverless` is installed. [See installation guide](https://serverless.com/framework/docs/providers/openwhisk/guide/installation/). You will also need to set up your OpenWhisk account credentials using environment variables or a configuration file. Please see the [this guide for more information](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/). ## 1. Install Provider Plugin & Service Dependencies `npm install` in this directory to download the modules from `package.json`. ## 2. Deploy `serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command ## 3. Invoke sequence function `serverless invoke --function chained_seq --data '{"message": "a b c d e"}'` `-f` is also shorthand for `--function` In your terminal window you should see the response from Apache OpenWhisk ```bash { "message": "e d c b a" } ``` ## 4. Invoke chained function `serverless invoke --function manual_seq --data '{"message": "a b c d e"}'` `-f` is also shorthand for `--function` In your terminal window you should see the response from Apache OpenWhisk ```bash { "message": "e d c b a" } ``` **For more information on the Serverless OpenWhisk plugin, please see the project repository: [https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/).** ================================================ FILE: openwhisk-node-chaining-functions/handler.js ================================================ 'use strict'; function chain(parameters) { // eslint-disable-next-line global-require, import/no-extraneous-dependencies const ow = require('openwhisk')(); const invoke = (actionName, params) => ow.actions.invoke({ actionName, params, blocking: true }); return invoke('my_service-dev-split', parameters) .then(res => invoke('my_service-dev-reverse', res.response.result)) .then(res => invoke('my_service-dev-join', res.response.result)) .then(res => res.response.result); } module.exports.chain = chain; ================================================ FILE: openwhisk-node-chaining-functions/package.json ================================================ { "name": "openwhisk-node-chaining-functions", "version": "0.1.0", "description": "Example of chaining function calls using sequences and the sdk.", "dependencies": { "serverless-openwhisk": ">=0.13.0" } } ================================================ FILE: openwhisk-node-chaining-functions/serverless.yml ================================================ # Welcome to Serverless! # # This file is the main config file for your service. # It's very minimal at this point and uses default values. # You can always add more config options for more control. # We've included some commented out config examples here. # Just uncomment any of them to get that config option. # # For full config options, check the docs: # docs.serverless.com # # Happy Coding! service: my_service # NOTE: update this with your service name provider: name: openwhisk functions: split: handler: utils.split reverse: handler: utils.reverse join: handler: utils.join chained_seq: sequence: - split - reverse - join manual_seq: handler: handler.chain # remember to run npm install to download the provider plugin. plugins: - serverless-openwhisk ================================================ FILE: openwhisk-node-chaining-functions/utils.js ================================================ 'use strict'; function split(params) { return { message: params.message.split(' ') }; } function join(params) { return { message: params.message.join(' ') }; } function reverse(params) { return { message: params.message.reverse() }; } module.exports.split = split; module.exports.join = join; module.exports.reverse = reverse; ================================================ FILE: openwhisk-node-scheduled-cron/.gitignore ================================================ node_modules .serverless ================================================ FILE: openwhisk-node-scheduled-cron/README.md ================================================ # Serverless Boilerplate - OpenWhisk - Node.js Make sure `serverless` is installed. [See installation guide](https://serverless.com/framework/docs/providers/openwhisk/guide/installation/). You will also need to set up your OpenWhisk account credentials using environment variables or a configuration file. Please see the [this guide for more information](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/). ## 1. Install Provider Plugin & Service Dependencies `npm install` in this directory to download the modules from `package.json`. ## 2. Deploy `serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command **For more information on the Serverless OpenWhisk plugin, please see the project repository: [https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/).** ================================================ FILE: openwhisk-node-scheduled-cron/handler.js ================================================ 'use strict'; function cron() { const time = new Date(); const name = '__OW_ACTION_NAME'; console.log(`Your cron function "${process.env[name]}" ran at ${time}`); } module.exports.cron = cron; ================================================ FILE: openwhisk-node-scheduled-cron/package.json ================================================ { "name": "openwhisk-node-scheduled-cron", "version": "0.1.0", "description": "Example of creating a function that runs as a cron job using the serverless schedule event.", "dependencies": { "serverless-openwhisk": ">=0.13.0" } } ================================================ FILE: openwhisk-node-scheduled-cron/serverless.yml ================================================ # Welcome to Serverless! # # This file is the main config file for your service. # It's very minimal at this point and uses default values. # You can always add more config options for more control. # We've included some commented out config examples here. # Just uncomment any of them to get that config option. # # For full config options, check the docs: # docs.serverless.com # # Happy Coding! service: my_service # NOTE: update this with your service name provider: name: openwhisk functions: cron: handler: handler.cron events: - schedule: cron(* * * * *) # remember to run npm install to download the provider plugin. plugins: - serverless-openwhisk ================================================ FILE: openwhisk-node-simple/.gitignore ================================================ node_modules .serverless ================================================ FILE: openwhisk-node-simple/README.md ================================================ # Serverless Boilerplate - OpenWhisk - Node.js Make sure `serverless` is installed. [See installation guide](https://serverless.com/framework/docs/providers/openwhisk/guide/installation/). You will also need to set up your OpenWhisk account credentials using environment variables or a configuration file. Please see the [this guide for more information](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/). ## 1. Install Provider Plugin & Service Dependencies `npm install` in this directory to download the modules from `package.json`. ## 2. Deploy `serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command ## 3. Invoke deployed function `serverless invoke --function hello_world` or `serverless invoke -f hello_world` `-f` is shorthand for `--function` In your terminal window you should see the response from Apache OpenWhisk ```bash { "payload": "Hello, World!" } ``` Congrats you have just deployed and run your Hello World function! **For more information on the Serverless OpenWhisk plugin, please see the project repository: [https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/).** ================================================ FILE: openwhisk-node-simple/delay.js ================================================ function delay() { return new Promise(resolve => setTimeout(() => resolve({ done: true }), 2000), ); } exports.handler = delay; ================================================ FILE: openwhisk-node-simple/hello_world.js ================================================ 'use strict'; function main(params) { const name = params.name || 'World'; return { payload: `Hello, ${name}!` }; } exports.handler = main; ================================================ FILE: openwhisk-node-simple/left_pad.js ================================================ const leftPad = require('left-pad'); function padlines(args) { const lines = args.lines || []; return { padded: lines.map(l => leftPad(l, 30, '.')) }; } exports.handler = padlines; ================================================ FILE: openwhisk-node-simple/package.json ================================================ { "name": "openwhisk-node-simple", "version": "0.1.0", "description": "Simple example demonstrating OpenWhisk provider support.", "dependencies": { "left-pad": "^1.1.3", "serverless-openwhisk": ">=0.13.0" } } ================================================ FILE: openwhisk-node-simple/serverless.yml ================================================ # Welcome to Serverless! # # This file is the main config file for your service. # It's very minimal at this point and uses default values. # You can always add more config options for more control. # We've included some commented out config examples here. # Just uncomment any of them to get that config option. # # For full config options, check the docs: # docs.serverless.com # # Happy Coding! service: my_service # NOTE: update this with your service name provider: name: openwhisk functions: hello_world: handler: hello_world.handler events: - trigger: event_name left_pad: handler: left_pad.handler delay: handler: delay.handler # remember to run npm install to download the provider plugin. plugins: - serverless-openwhisk ================================================ FILE: openwhisk-node-simple-http-endpoint/.gitignore ================================================ node_modules .serverless ================================================ FILE: openwhisk-node-simple-http-endpoint/README.md ================================================ # Serverless Boilerplate - OpenWhisk - Node.js Make sure `serverless` is installed. [See installation guide](https://serverless.com/framework/docs/providers/openwhisk/guide/installation/). You will also need to set up your OpenWhisk account credentials using environment variables or a configuration file. Please see the [this guide for more information](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/). ## 1. Install Provider Plugin & Service Dependencies `npm install` in this directory to download the modules from `package.json`. ## 2. Deploy `serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command Make a note of the API endpoint that is logged to the console during deployment. ``` Serverless: Configured API endpoint: https://xxx-yyy-zzz-gws.api-gw.mybluemix.net/my_service ``` ## 3. Invoke deployed function `serverless invoke --function time` or `serverless invoke -f time` `-f` is shorthand for `--function` In your terminal window you should see the response from Apache OpenWhisk ```bash { "payload": "The time in Europe/London is: 16:01:14." } ``` ## 4. Test HTTP endpoint Use a HTTP client to access the endpoint for your function. The endpoint will be the API gateway root path, logged during deployment, and your configured function path. ``` $ http get https://xxx-yyy-zzz-gws.api-gw.mybluemix.net/my_service/time { "payload": "The time in Europe/London is: 16:01:07." } $ http get https://xxx-yyy-zzz-gws.api-gw.mybluemix.net/my_service/time?timezone=Europe/Berlin { "payload": "The time in Europe/Berlin is: 17:01:11." } ``` **For more information on the Serverless OpenWhisk plugin, please see the project repository: [https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/).** ================================================ FILE: openwhisk-node-simple-http-endpoint/handler.js ================================================ 'use strict'; const moment = require('moment-timezone'); function time(params) { const timezone = params.timezone || 'Europe/London'; const timestr = moment().tz(timezone).format('HH:MM:ss'); return { payload: `The time in ${timezone} is: ${timestr}.` }; } module.exports.time = time; ================================================ FILE: openwhisk-node-simple-http-endpoint/package.json ================================================ { "name": "openwhisk-node-simple-http", "version": "0.1.0", "description": "Example demonstrates how to setup a simple HTTP GET endpoint with OpenWhisk.", "dependencies": { "moment-timezone": "^0.5.11", "serverless-openwhisk": ">=0.13.0" } } ================================================ FILE: openwhisk-node-simple-http-endpoint/serverless.yml ================================================ # Welcome to Serverless! # # This file is the main config file for your service. # It's very minimal at this point and uses default values. # You can always add more config options for more control. # We've included some commented out config examples here. # Just uncomment any of them to get that config option. # # For full config options, check the docs: # docs.serverless.com # # Happy Coding! service: my_service # NOTE: update this with your service name provider: name: openwhisk functions: time: handler: handler.time events: - http: GET time # remember to run npm install to download the provider plugin. plugins: - serverless-openwhisk ================================================ FILE: openwhisk-php-simple/.gitignore ================================================ .serverless *.pyc *.pyo ================================================ FILE: openwhisk-php-simple/README.md ================================================ # Serverless Boilerplate - OpenWhisk - PHP Make sure `serverless` is installed. [See installation guide](https://serverless.com/framework/docs/providers/openwhisk/guide/installation/). You will also need to set up your OpenWhisk account credentials using environment variables or a configuration file. Please see the [this guide for more information](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/). ## 1. Install Project Dependencies `npm install` in this directory to download the modules from `package.json`. ## 2. Deploy `serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command ``` Serverless: Packaging service... Serverless: Compiling Functions... Serverless: Compiling API Gateway definitions... Serverless: Compiling Rules... Serverless: Compiling Triggers & Feeds... Serverless: Deploying Functions... Serverless: Deployment successful! Service Information platform: openwhisk.ng.bluemix.net namespace: _ service: php-service actions: php-service-dev-greeting ``` ## 3. Invoke deployed function `serverless invoke --function greeting` or `serverless invoke -f greeting` `-f` is shorthand for `--function` In your terminal window you should see the response from Apache OpenWhisk ```bash $ serverless invoke -f greeting { "greeting": "Hello stranger!" } $ serverless invoke -f greeting -d '{"name": "James"}' { "greeting": "Hello James!" } ``` **For more information on the Serverless OpenWhisk plugin, please see the project repository: [https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/).** ================================================ FILE: openwhisk-php-simple/handler.php ================================================ $greeting]; } ================================================ FILE: openwhisk-php-simple/package.json ================================================ { "name": "openwhisk-php-simple", "version": "0.1.0", "description": "Example demonstrates how to setup a simple PHP function with OpenWhisk.", "dependencies": { "serverless-openwhisk": ">=0.13.0" } } ================================================ FILE: openwhisk-php-simple/serverless.yml ================================================ service: php-service provider: name: openwhisk runtime: php functions: greeting: handler: handler.greeting plugins: - serverless-openwhisk ================================================ FILE: openwhisk-python-scheduled-cron/.gitignore ================================================ node_modules .serverless ================================================ FILE: openwhisk-python-scheduled-cron/README.md ================================================ # Serverless Boilerplate - OpenWhisk - Python Make sure `serverless` is installed. [See installation guide](https://serverless.com/framework/docs/providers/openwhisk/guide/installation/). You will also need to set up your OpenWhisk account credentials using environment variables or a configuration file. Please see the [this guide for more information](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/). ## 1. Install Project Dependencies `npm install` in this directory to download the modules from `package.json`. ## 2. Deploy `serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command ``` $ serverless deploy Serverless: Packaging service... Serverless: Compiling Functions... Serverless: Compiling API Gateway definitions... Serverless: Compiling Rules... Serverless: Compiling Triggers & Feeds... Serverless: Deploying Functions... Serverless: Deploying Triggers... Serverless: Binding Feeds To Triggers... Serverless: Deploying Rules... Serverless: Deployment successful! Service Information platform: openwhisk.ng.bluemix.net namespace: _ service: python_service actions: python_service-dev-cron triggers: python_service_cron_schedule_trigger rules: python_service_cron_schedule_rule ``` ## 3. Monitor function logs After sixty seconds the function should be executed and you can review the logging output using `serverless logs --function cron` or `serverless logs -f cron` ```bash $ serverless logs -f cron activation (78ebd109b3bd4da5bb20c13bd2982319): 2028 16:51:01.539 Your cron function /james.thomas@uk.ibm.com_dev/python_service-dev-cron ran at 15:51:01.538616 ``` **For more information on the Serverless OpenWhisk plugin, please see the project repository: [https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/).** ================================================ FILE: openwhisk-python-scheduled-cron/handler.py ================================================ import datetime import os def run(params): current_time = datetime.datetime.now().time() name = os.environ['__OW_ACTION_NAME'] print("Your cron function " + name + " ran at " + str(current_time)) return {} ================================================ FILE: openwhisk-python-scheduled-cron/package.json ================================================ { "name": "openwhisk-python-scheduled-cron", "version": "0.1.0", "description": "Example of creating a Python function that runs as a cron job using the serverless schedule event.", "dependencies": { "serverless-openwhisk": ">=0.13.0" } } ================================================ FILE: openwhisk-python-scheduled-cron/serverless.yml ================================================ service: python_service provider: name: openwhisk runtime: python:3 functions: cron: handler: handler.run events: - schedule: cron(* * * * *) # remember to run npm install to download the provider plugin. plugins: - serverless-openwhisk ================================================ FILE: openwhisk-python-simple/.gitignore ================================================ .serverless *.pyc *.pyo ================================================ FILE: openwhisk-python-simple/README.md ================================================ # Serverless Boilerplate - OpenWhisk - Python Make sure `serverless` is installed. [See installation guide](https://serverless.com/framework/docs/providers/openwhisk/guide/installation/). You will also need to set up your OpenWhisk account credentials using environment variables or a configuration file. Please see the [this guide for more information](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/). ## 1. Install Project Dependencies `npm install` in this directory to download the modules from `package.json`. ## 2. Deploy `serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command ``` Serverless: Packaging service... Serverless: Compiling Functions... Serverless: Compiling API Gateway definitions... Serverless: Compiling Rules... Serverless: Compiling Triggers & Feeds... Serverless: Deploying Functions... Serverless: Deployment successful! Service Information platform: openwhisk.ng.bluemix.net namespace: _ service: python-service actions: python-service-dev-greeting ``` ## 3. Invoke deployed function `serverless invoke --function greeting` or `serverless invoke -f greeting` `-f` is shorthand for `--function` In your terminal window you should see the response from Apache OpenWhisk ```bash $ serverless invoke -f greeting { "greeting": "Hello stranger!" } $ serverless invoke -f greeting -d '{"name": "James"}' { "greeting": "Hello James!" } ``` **For more information on the Serverless OpenWhisk plugin, please see the project repository: [https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/).** ================================================ FILE: openwhisk-python-simple/handler.py ================================================ def endpoint(params): name = params.get("name", "stranger") greeting = "Hello " + name + "!" print(greeting) return {"greeting": greeting} ================================================ FILE: openwhisk-python-simple/package.json ================================================ { "name": "openwhisk-python-simple", "version": "0.1.0", "description": "Example demonstrates how to setup a simple Python function with OpenWhisk.", "dependencies": { "serverless-openwhisk": ">=0.13.0" } } ================================================ FILE: openwhisk-python-simple/serverless.yml ================================================ service: python-service provider: name: openwhisk runtime: python:3 functions: greeting: handler: handler.endpoint plugins: - serverless-openwhisk ================================================ FILE: openwhisk-python-simple-http-endpoint/.gitignore ================================================ .serverless *.pyc *.pyo ================================================ FILE: openwhisk-python-simple-http-endpoint/README.md ================================================ # Serverless Boilerplate - OpenWhisk - Python Make sure `serverless` is installed. [See installation guide](https://serverless.com/framework/docs/providers/openwhisk/guide/installation/). You will also need to set up your OpenWhisk account credentials using environment variables or a configuration file. Please see the [this guide for more information](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/). ## 1. Install Project Dependencies `npm install` in this directory to download the modules from `package.json`. ## 2. Deploy `serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command Make a note of the API endpoint that is logged to the console during deployment. ``` endpoints: GET https://xxx.api-gw.mybluemix.net/python-service/time --> python-service-dev-currentTime ``` ## 3. Invoke deployed function `serverless invoke --function currentTime` or `serverless invoke -f currentTime` `-f` is shorthand for `--function` In your terminal window you should see the response from Apache OpenWhisk ```bash { "message": "Hello stranger, the current time is 15:59:30.983379" } ``` ## 4. Test HTTP endpoint Use a HTTP client to access the endpoint for your function. The endpoint will be the API gateway root path, logged during deployment, and your configured function path. ``` $ http get https://xxx.api-gw.mybluemix.net/python-service/time HTTP/1.1 200 OK ... { "message": "Hello stranger, the current time is 16:00:11.837331" } $ http get https://xxx.api-gw.mybluemix.net/python-service/time?name=James HTTP/1.1 200 OK ... { "message": "Hello James, the current time is 16:00:15.749699" } ``` **For more information on the Serverless OpenWhisk plugin, please see the project repository: [https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/).** ================================================ FILE: openwhisk-python-simple-http-endpoint/handler.py ================================================ import json import datetime def endpoint(params): current_time = datetime.datetime.now().time() name = params.get("name", "stranger") body = { "message": "Hello " + name + ", the current time is " + str(current_time) } return body ================================================ FILE: openwhisk-python-simple-http-endpoint/package.json ================================================ { "name": "openwhisk-python-simple-http-endpoint", "version": "0.1.0", "description": "Example demonstrates how to setup a simple HTTP GET endpoint with OpenWhisk.", "dependencies": { "serverless-openwhisk": ">=0.13.0" } } ================================================ FILE: openwhisk-python-simple-http-endpoint/serverless.yml ================================================ service: python-service provider: name: openwhisk runtime: python:3 functions: currentTime: handler: handler.endpoint events: - http: path: time method: get plugins: - serverless-openwhisk ================================================ FILE: openwhisk-ruby-simple/.gitignore ================================================ .serverless *.pyc *.pyo ================================================ FILE: openwhisk-ruby-simple/README.md ================================================ # Serverless Boilerplate - OpenWhisk - Ruby Make sure `serverless` is installed. [See installation guide](https://serverless.com/framework/docs/providers/openwhisk/guide/installation/). You will also need to set up your OpenWhisk account credentials using environment variables or a configuration file. Please see the [this guide for more information](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/). ## 1. Install Project Dependencies `npm install` in this directory to download the modules from `package.json`. ## 2. Deploy `serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command ``` Serverless: Packaging service... Serverless: Compiling Functions... Serverless: Compiling API Gateway definitions... Serverless: Compiling Rules... Serverless: Compiling Triggers & Feeds... Serverless: Deploying Functions... Serverless: Deployment successful! Service Information platform: openwhisk.ng.bluemix.net namespace: _ service: ruby-service actions: ruby-service-dev-greeting ``` ## 3. Invoke deployed function `serverless invoke --function greeting` or `serverless invoke -f greeting` `-f` is shorthand for `--function` In your terminal window you should see the response from Apache OpenWhisk ```bash $ serverless invoke -f greeting { "greeting": "Hello stranger!" } $ serverless invoke -f greeting -d '{"name": "James"}' { "greeting": "Hello James!" } ``` **For more information on the Serverless OpenWhisk plugin, please see the project repository: [https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/).** ================================================ FILE: openwhisk-ruby-simple/handler.rb ================================================ def main(args) name = args["name"] || "stranger" greeting = "Hello #{name}!" puts greeting { "greeting" => greeting } end ================================================ FILE: openwhisk-ruby-simple/package.json ================================================ { "name": "openwhisk-ruby-simple", "version": "0.1.0", "description": "Example demonstrates how to setup a simple Ruby function with OpenWhisk.", "dependencies": { "serverless-openwhisk": ">=0.13.0" } } ================================================ FILE: openwhisk-ruby-simple/serverless.yml ================================================ service: ruby-service provider: name: openwhisk runtime: ruby functions: greeting: handler: handler.main plugins: - serverless-openwhisk ================================================ FILE: openwhisk-rust-simple-http-endpoint/.gitignore ================================================ .serverless *.pyc *.pyo ================================================ FILE: openwhisk-rust-simple-http-endpoint/Cargo.toml ================================================ [workspace] members = ["test"] ================================================ FILE: openwhisk-rust-simple-http-endpoint/README.md ================================================ # Serverless Boilerplate - OpenWhisk - Rust (This example is largely based on the openwhisk-go-simple by James Thomas but adapted for Rust) Make sure `serverless` is installed. [See installation guide](https://serverless.com/framework/docs/providers/openwhisk/guide/installation/). You will also need to set up your OpenWhisk account credentials using environment variables or a configuration file. Please see the [this guide for more information](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/). ## 1. Install Project Dependencies `npm install` in this directory to download the modules from `package.json`. ## 2. Compile Rust Binary (Statically) ``` $ cargo build --release --target x86_64-unknown-linux-musl ``` ## 3. Deploy `serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command ``` Serverless: Packaging service... Serverless: Excluding development dependencies... Serverless: Compiling Functions... Serverless: Compiling Packages... Serverless: Compiling API Gateway definitions... Serverless: Compiling Rules... Serverless: Compiling Triggers & Feeds... Serverless: Compiling Service Bindings... Serverless: Deploying Functions... Serverless: Deploying API Gateway definitions... Serverless: Deployment successful! Service Information platform: us-south.functions.cloud.ibm.com namespace: _ service: rust-service actions: rust-service-dev-test_test ``` ## 4. Invoke deployed function `serverless invoke --function test_test` or `serverless invoke -f test_test` `-f` is shorthand for `--function` In your terminal window you should see the response from Apache OpenWhisk ```bash $ serverless invoke -f test_test { "message": "Serverless Rust Hello" } ``` **For more information on the Serverless OpenWhisk plugin, please see the project repository: [https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/).** ================================================ FILE: openwhisk-rust-simple-http-endpoint/package.json ================================================ { "name": "openwhisk-rust-simple-http-endpoint", "version": "0.1.0", "description": "Example demonstrates how to setup a simple Rust function with OpenWhisk.", "dependencies": { "serverless-openwhisk": ">=0.13.0" } } ================================================ FILE: openwhisk-rust-simple-http-endpoint/serverless.yml ================================================ service: rust-service provider: name: openwhisk runtime: binary # memorySize: 128 # stage: api # we attach the stage ie dev or prod in the path package: # individually: true # creates one artifact for each function exclude: - ./** include: - ./target/x86_64-unknown-linux-musl/release/test # remember to run npm install to download the provider plugin. plugins: - serverless-openwhisk functions: test_test: handler: target/x86_64-unknown-linux-musl/release/test events: - http: path: test/test method: get ================================================ FILE: openwhisk-rust-simple-http-endpoint/test/Cargo.toml ================================================ [package] name = "test" version = "0.1.0" edition = "2018" authors = ["jonee"] [dependencies] tokio = { version = "0.2", features = ["macros"] } #lambda_http = { git = "https://github.com/awslabs/aws-lambda-rust-runtime/", branch = "master"} serde_json = "1.0" rustc-serialize = "0.3" ================================================ FILE: openwhisk-rust-simple-http-endpoint/test/src/main.rs ================================================ use serde_json::json; fn main() { // creating an application/json response println!("{}", json!({ "message": "Serverless Rust Hello" })) } ================================================ FILE: openwhisk-swift-precompiled-binaries/Package.swift ================================================ import PackageDescription let package = Package( name: "Action", dependencies: [ .Package(url: "https://github.com/jthomas/OpenWhiskAction.git", majorVersion: 0) ] ) ================================================ FILE: openwhisk-swift-precompiled-binaries/README.md ================================================ # Serverless OpenWhisk Swift Template Hello! 😎 This is a template Swift service with pre-compiled binaries for the OpenWhisk platform. Before you can deploy your service, please follow the instructions below… ### Have you set up your account credentials? Before you can deploy your service to OpenWhisk, you need to have an account registered with the platform. - *Want to run the platform locally?* Please read the project's [*Quick Start*](https://github.com/openwhisk/openwhisk#quick-start) guide for deploying it locally. - *Want to use a hosted provider?* Please sign up for an account with [IBM Bluemix](https://console.ng.bluemix.net/) and then follow the instructions for getting access to [OpenWhisk on Bluemix](https://console.ng.bluemix.net/openwhisk/). Account credentials for OpenWhisk can be provided through a configuration file or environment variables. This plugin requires the API endpoint, namespace and authentication credentials. **Do you want to use a configuration file for storing these values?** Please [follow the instructions](https://console.ng.bluemix.net/openwhisk/cli) for setting up the OpenWhisk command-line utility. This tool stores account credentials in the `.wskprops` file in the user's home directory. The plugin automatically extracts credentials from this file at runtime. No further configuration is needed. **Do you want to use environment variables for credentials?** Use the following environment variables to be pass in account credentials. These values override anything extracted from the configuration file. - *OW_APIHOST* - Platform endpoint, e.g. `openwhisk.ng.bluemix.net` - *OW_AUTH* - Authentication key, e.g. `xxxxxx:yyyyy ### Have you installed and setup the provider plugin? Using the framework with the OpenWhisk platform needs you to install the provider plugin and link this to your service. #### Install the provider plugin ``` $ npm install ``` **_…and that's it!_** ### Project Set-Up This template project contains a Swift package that generates multiple binaries during the build process. These binaries are used to create separate OpenWhisk actions. ``` $ tree . . ├── Package.swift ├── README.md ├── Sources │   ├── hello │   │   └── main.swift │   └── welcome │   └── main.swift ├── package.json └── serverless.yml ``` Swift action sources files use an [external package library](https://packagecatalog.com/package/jthomas/OpenWhiskAction) to handle wrapping functions within a shim for execution on the platform. ``` import OpenWhiskAction func hello(args: [String:Any]) -> [String:Any] { if let name = args["name"] as? String { return [ "greeting" : "Hello \(name)!" ] } else { return [ "greeting" : "Hello stranger!" ] } } OpenWhiskAction(main: hello) ``` Binaries must be compiled for the correct platform architecture. This example uses the following Docker command to run the build in the OpenWhisk Swift environment. ``` docker run --rm -it -v $(pwd):/swift-package openwhisk/action-swift-v3.1.1 bash -e -c 'cd /swift-package && swift build -v -c release' ``` Plugins for the framework handle running the build scripts using npm prior to deployment. ``` custom: scripts: hooks: 'package:initialize': npm run-script compile plugins: - serverless-openwhisk - serverless-plugin-scripts ``` ### Deploy Service Use the `serverless` command to deploy your service. ```shell serverless deploy ``` If this command is successful, you can invoke both serverless functions defined in your configuration. ``` $ serverless invoke -f hello { "greeting": "Hello stranger!" } $ serverless invoke -f welcome { "greeting": "Welcome stranger!" } ``` ### Issues / Feedback / Feature Requests? If you have any issues, comments or want to see new features, please file an issue in the project repository: https://github.com/serverless/serverless-openwhisk ================================================ FILE: openwhisk-swift-precompiled-binaries/Sources/hello/main.swift ================================================ import OpenWhiskAction func hello(args: [String:Any]) -> [String:Any] { if let name = args["name"] as? String { return [ "greeting" : "Hello \(name)!" ] } else { return [ "greeting" : "Hello stranger!" ] } } OpenWhiskAction(main: hello) ================================================ FILE: openwhisk-swift-precompiled-binaries/Sources/welcome/main.swift ================================================ import OpenWhiskAction func hello(args: [String:Any]) -> [String:Any] { if let name = args["name"] as? String { return [ "greeting" : "Welcome \(name)!" ] } else { return [ "greeting" : "Welcome stranger!" ] } } OpenWhiskAction(main: hello) ================================================ FILE: openwhisk-swift-precompiled-binaries/package.json ================================================ { "name": "openwhisk-swift-package-with-precompiled-binaries", "version": "1.0.0", "description": "Swift packages and pre-compiled binaries on OpenWhisk.", "main": "handler.js", "scripts": { "compile": "docker run --rm -it -v $(pwd):/swift-package openwhisk/action-swift-v3.1.1 bash -e -c 'cd /swift-package && swift build -v -c release'" }, "keywords": [ "serverless", "openwhisk" ], "dependencies": { "serverless-plugin-scripts": "^1.0.2", "serverless-openwhisk": ">=0.13.0" } } ================================================ FILE: openwhisk-swift-precompiled-binaries/serverless.yml ================================================ service: swift-packages provider: name: openwhisk runtime: swift functions: hello: handler: .build/release/hello welcome: handler: .build/release/welcome custom: scripts: hooks: 'package:initialize': npm run-script compile plugins: - serverless-openwhisk - serverless-plugin-scripts ================================================ FILE: openwhisk-swift-scheduled-cron/.gitignore ================================================ node_modules .serverless ================================================ FILE: openwhisk-swift-scheduled-cron/README.md ================================================ # Serverless Boilerplate - OpenWhisk - Swift Make sure `serverless` is installed. [See installation guide](https://serverless.com/framework/docs/providers/openwhisk/guide/installation/). You will also need to set up your OpenWhisk account credentials using environment variables or a configuration file. Please see the [this guide for more information](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/). ## 1. Install Project Dependencies `npm install` in this directory to download the modules from `package.json`. ## 2. Deploy `serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command ``` $ serverless deploy Serverless: Packaging service... Serverless: Compiling Functions... Serverless: Compiling API Gateway definitions... Serverless: Compiling Rules... Serverless: Compiling Triggers & Feeds... Serverless: Deploying Functions... Serverless: Deploying Triggers... Serverless: Binding Feeds To Triggers... Serverless: Deploying Rules... Serverless: Deployment successful! Service Information platform: openwhisk.ng.bluemix.net namespace: _ service: swift_service actions: swift_service-dev-cron triggers: swift_service_cron_schedule_trigger rules: swift_service_cron_schedule_rule ``` ## 3. Monitor function logs After sixty seconds the function should be executed and you can review the logging output using `serverless logs --function cron` or `serverless logs -f cron` ```bash $ serverless logs -f cron activation (96d31322bab24cf1940e7b05b428ee34): 2028 17:47:05.084 Compiling 2028 17:47:06.943 swiftc status is 0 2028 17:47:06.943 Linking 2028 17:47:07.073 Swift function (/james.thomas@uk.ibm.com_dev/swift_service-dev-cron) was called @ 2028 16:47:07 ``` **For more information on the Serverless OpenWhisk plugin, please see the project repository: [https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/).** ================================================ FILE: openwhisk-swift-scheduled-cron/handler.swift ================================================ func main(args: [String:Any]) -> [String:Any] { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" let now = formatter.string(from: Date()) if let value = ProcessInfo.processInfo.environment["__OW_ACTION_NAME"] { print("Swift function (\(value)) was called @ \(now)") return [ "cron": "Swift function (\(value)) was called @ \(now)" ] } print("Swift function was called @ \(now)") return [ "cron": "Swift function was called @ \(now)" ] } ================================================ FILE: openwhisk-swift-scheduled-cron/package.json ================================================ { "name": "openwhisk-swift-scheduled-cron", "version": "0.1.0", "description": "Example of creating a Swift function that runs as a cron job using the serverless schedule event.", "dependencies": { "serverless-openwhisk": ">=0.13.0" } } ================================================ FILE: openwhisk-swift-scheduled-cron/serverless.yml ================================================ service: swift_service provider: name: openwhisk runtime: swift functions: cron: handler: handler.main events: - schedule: cron(* * * * *) # remember to run npm install to download the provider plugin. plugins: - serverless-openwhisk ================================================ FILE: openwhisk-swift-simple/.gitignore ================================================ .serverless *.pyc *.pyo ================================================ FILE: openwhisk-swift-simple/README.md ================================================ # Serverless Boilerplate - OpenWhisk - Swift Make sure `serverless` is installed. [See installation guide](https://serverless.com/framework/docs/providers/openwhisk/guide/installation/). You will also need to set up your OpenWhisk account credentials using environment variables or a configuration file. Please see the [this guide for more information](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/). ## 1. Install Project Dependencies `npm install` in this directory to download the modules from `package.json`. ## 2. Deploy `serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command ``` Serverless: Packaging service... Serverless: Compiling Functions... Serverless: Compiling API Gateway definitions... Serverless: Compiling Rules... Serverless: Compiling Triggers & Feeds... Serverless: Deploying Functions... Serverless: Deployment successful! Service Information platform: openwhisk.ng.bluemix.net namespace: _ service: swift-service actions: swift-service-dev-ping ``` ## 3. Invoke deployed function `serverless invoke --function ping` or `serverless invoke -f ping` `-f` is shorthand for `--function` In your terminal window you should see the response from Apache OpenWhisk ```bash $ serverless invoke -f ping { } $ serverless invoke -f ping -d '{"name": "James"}' { "greeting": "Hello James! The time is 2028 16:24:23" } ``` **For more information on the Serverless OpenWhisk plugin, please see the project repository: [https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/).** ================================================ FILE: openwhisk-swift-simple/package.json ================================================ { "name": "openwhisk-swift-simple", "version": "0.1.0", "description": "Example demonstrates how to setup a simple Swift function with OpenWhisk.", "dependencies": { "serverless-openwhisk": ">=0.13.0" } } ================================================ FILE: openwhisk-swift-simple/ping.swift ================================================ func main(args: [String:Any]) -> [String:Any] { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" let now = formatter.string(from: Date()) if let name = args["name"] as? String { return [ "greeting" : "Hello \(name)! The time is \(now)" ] } else { return [ "greeting" : "Hello stranger! The time is \(now)" ] } } ================================================ FILE: openwhisk-swift-simple/serverless.yml ================================================ service: swift-service provider: name: openwhisk runtime: swift functions: ping: handler: ping.main plugins: - serverless-openwhisk ================================================ FILE: openwhisk-swift-simple-http-endpoint/.gitignore ================================================ .serverless *.pyc *.pyo ================================================ FILE: openwhisk-swift-simple-http-endpoint/README.md ================================================ # Serverless Boilerplate - OpenWhisk - Swift Make sure `serverless` is installed. [See installation guide](https://serverless.com/framework/docs/providers/openwhisk/guide/installation/). You will also need to set up your OpenWhisk account credentials using environment variables or a configuration file. Please see the [this guide for more information](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/). ## 1. Install Project Dependencies `npm install` in this directory to download the modules from `package.json`. ## 2. Deploy `serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command Make a note of the API endpoint that is logged to the console during deployment. ``` endpoints: GET https://xxx.api-gw.mybluemix.net/swift-service/time --> swift-service-dev-time ``` ## 3. Invoke deployed function `serverless invoke --function time` or `serverless invoke -f time` `-f` is shorthand for `--function` In your terminal window you should see the response from Apache OpenWhisk ```bash { "message": "Hello stranger, the current time is 15:59:30.983379" } ``` ## 4. Test HTTP endpoint Use a HTTP client to access the endpoint for your function. The endpoint will be the API gateway root path, logged during deployment, and your configured function path. ``` $ http get https://xxx.api-gw.mybluemix.net/swift-service/time HTTP/1.1 200 OK ... { "message": "Hello stranger, the current time is 16:00:11.837331" } $ http get https://xxx.api-gw.mybluemix.net/swift-service/time?name=James HTTP/1.1 200 OK ... { "message": "Hello James, the current time is 16:00:15.749699" } ``` **For more information on the Serverless OpenWhisk plugin, please see the project repository: [https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/](https://serverless.com/framework/docs/providers/openwhisk/guide/credentials/).** ================================================ FILE: openwhisk-swift-simple-http-endpoint/package.json ================================================ { "name": "openwhisk-swift-simple-http-endpoint", "version": "0.1.0", "description": "Example demonstrates how to setup a simple HTTP endpoint using Swift function with OpenWhisk.", "dependencies": { "serverless-openwhisk": ">=0.13.0" } } ================================================ FILE: openwhisk-swift-simple-http-endpoint/ping.swift ================================================ func main(args: [String:Any]) -> [String:Any] { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" let now = formatter.string(from: Date()) if let name = args["name"] as? String { return [ "greeting" : "Hello \(name)! The time is \(now)" ] } else { return [ "greeting" : "Hello stranger! The time is \(now)" ] } } ================================================ FILE: openwhisk-swift-simple-http-endpoint/serverless.yml ================================================ service: swift-service provider: name: openwhisk runtime: swift functions: time: handler: time.main events: - http: path: time method: get plugins: - serverless-openwhisk ================================================ FILE: package.json ================================================ { "name": "serverless-examples", "version": "0.0.0", "description": "A compilation of several examples and boilerplates", "scripts": { "lint": "eslint .", "docs": "node generate-readme.js", "validate": "node validate.js" }, "license": "MIT", "dependencies": { "eslint": "^5.15.3", "eslint-config-airbnb-base": "^10.0.1", "eslint-plugin-import": "^2.16.0", "gray-matter": "^4.0.2", "markdown-magic": "^0.1.25" }, "devDependencies": {} } ================================================ FILE: twilio-node-forward-call/.gitignore ================================================ node_modules .serverless ================================================ FILE: twilio-node-forward-call/README.md ================================================ # Serverless Boilerplate - Twilio - Node.js - Forward a call [Twilio](https://www.twilio.com) is a commincations API provide that allows developers to build applications using phone calls, SMS, emails and more. To configure communications a lot of services work with a configuration language called [TwiML](https://www.twilio.com/docs/glossary/what-is-twilio-markup-language-twiml). This example projects helps you deploy a serverless function to the Twilio runtime. The function responds the TwiML configuration to [forward phone call](https://www.twilio.com/docs/voice/tutorials/call-forwarding). Make sure `serverless` is installed globally. [See installation guide](https://serverless.com/framework/docs/providers/openwhisk/guide/installation/). You will also need to set up your Twilio account credentials using environment variables. You find these in your [Twilio Console](https://twilio.com/console/). Needed environment variables are: - `TWILIO_ACCOUNT_SID` - `TWILIO_AUTH_TOKEN` - `MY_PHONE_NUMBER` ## 1. Install Twilio Node.js Provider Plugin & Service Dependencies `npm install` in this directory to download the modules from `package.json`. ## 2. Deploy `serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command ## 3. Invoke deployed function `serverless invoke --function forward-call` or `serverless invoke -f forward-call` `-f` is shorthand for `--function` In your terminal window you should see the response from Apache OpenWhisk ```xml +491... ``` Congrats you have just deployed and run your Forward Call function! **For more information on the Twilio Runtime Serverless plugin, please see the project repository: [github.com/twilio-labs/serverless-framework-integration](https://github.com/twilio-labs/serverless-framework-integration).** ================================================ FILE: twilio-node-forward-call/forward-call.js ================================================ exports.handler = function(context, event, callback) { let twiml = new Twilio.twiml.VoiceResponse() twiml.dial(context.MY_PHONE_NUMBER); callback(null, twiml); }; ================================================ FILE: twilio-node-forward-call/package.json ================================================ { "name": "twilio.node-forward-call", "version": "1.0.-", "description": "Example demonstrating Twilio Runtime support with an endpoint that returns TwiML to forward a phone call", "dependencies": {}, "devDependencies": { "@twilio-labs/serverless-twilio-runtime": "^1.0.5" } } ================================================ FILE: twilio-node-forward-call/serverless.yml ================================================ service: your-service # update this with your service name provider: name: twilio # Twilio access credentials (mandatory) config: accountSid: ${env:TWILIO_ACCOUNT_SID} authToken: ${env:TWILIO_AUTH_TOKEN} # Twilio runtime supports several domains # your functions and assets will be available under # -> defaulting to 'dev' environment: ${env:TWILIO_RUNTIME_ENV, 'dev'} # Environment variables passed to your functions # available in the Twilio runtim via `context` parameter environmentVars: MY_PHONE_NUMBER: ${env:MY_PHONE_NUMBER} # Twilio runtime has to be added as a plugin plugins: - '@twilio-labs/serverless-twilio-runtime' functions: # Function name forward-call: # Path to the JS handler function in the project (without file extension '.js') handler: forward-call # URL path of the function after deployment path: /forward-call # visibility of the function (can be "public" or "protected") access: public ================================================ FILE: validate.js ================================================ const path = require('path'); const yml = require('gray-matter'); const fs = require('markdown-magic').fsExtra; const globby = require('markdown-magic').globby; const rootDirectory = path.join(__dirname); /* utils */ function hasSameProps(obj1, obj2) { return Object.keys(obj1).every(prop => obj2.hasOwnProperty(prop)); } function isEmptyObject(obj) { return Object.keys(obj).length === 0 && obj.constructor === Object; } const exampleData = { title: 'AWS Simple HTTP Endpoint example in NodeJS', description: 'This example demonstrates how to setup a simple HTTP GET endpoint. Once you ping it, it will reply with the current time.', framework: 'v1', platform: 'AWS', language: 'nodeJS', authorLink: 'https://github.com/lucianopf', authorName: 'Luciano Pellacani Franca', authorAvatar: 'https://avatars2.githubusercontent.com/u/8251208?v=4&s=140', }; // test author directory globby(['*/*', '!node_modules'], { cwd: rootDirectory, }).then((paths) => { paths.forEach((file) => { const fp = path.join(rootDirectory, file); if (fp.toLowerCase().endsWith('readme.md')) { const example = fs.readFileSync(fp, 'utf8').replace('', '---'); const exampleFileFrontmatter = yml(example).data; let msg; if (isEmptyObject(exampleFileFrontmatter)) { msg = `no frontmatter found! Please update ${file} \n\nHere's an example frontmatter for reference: https://raw.githubusercontent.com/serverless/examples/master/aws-node-simple-http-endpoint/README.md `; throw new Error(msg); } if (!hasSameProps(exampleData, exampleFileFrontmatter)) { msg = `incomplete frontmatter in ${file} \n\nFollowing properties are required: ${Object.keys(exampleData)} \n\nHere's an example frontmatter for reference: https://raw.githubusercontent.com/serverless/examples/master/aws-node-simple-http-endpoint/README.md `; throw new Error(msg); } } }); return true; }).then((done) => { console.log('all example readme.md files contain frontmatter'); }).catch((e) => { console.log(e); process.exit(1); });