├── .eslintrc.json ├── .gitignore ├── ISSUE_TEMPLATE.md ├── README.md ├── alexa-skill ├── README.md ├── bot.js ├── package.json └── setup.md ├── api-gw-geolocation ├── README.md ├── api.js └── package.json ├── async-await-and-babel ├── .gitignore ├── README.md ├── package.json └── src │ └── api.js ├── aws-mobile-analytics ├── README.md ├── api.js ├── ask.js ├── console.js ├── eventlogger.js ├── package.json └── policies │ └── analytics.json ├── babel ├── .gitignore ├── README.md ├── package.json └── src │ └── api.js ├── binary-content ├── README.md ├── child-process-promise.js ├── fs-promise.js ├── img.png ├── main.js └── package.json ├── bot-with-buttons ├── .eslintrc.json ├── README.md ├── bot.js ├── package.json └── src │ ├── ws-promise-wrap.js │ └── ws.js ├── cors-settings ├── .gitignore ├── README.md ├── api.js ├── index.html ├── package.json ├── web.js └── webpack.config.js ├── custom-authorizers ├── README.md ├── authorizer.js ├── demo.gif ├── index.js └── package.json ├── deploy-proxy-api ├── README.md ├── main.js └── package.json ├── detecting-context ├── README.md ├── api.js └── package.json ├── dynamodb-example ├── README.md ├── example.json ├── index.js ├── package.json └── policies │ └── access-dynamodb.json ├── env-variables ├── README.md ├── dev.json ├── lambda.js └── package.json ├── express-app-lambda ├── README.md ├── app.js ├── index.html └── package.json ├── fastify-app-lambda ├── README.md ├── app.js ├── lambda.js └── package.json ├── github-repo-labels ├── README.md ├── api.js ├── idraw │ ├── large.idraw │ └── small.idraw ├── package.json └── svg │ ├── large.svg │ └── small.svg ├── graphql-example ├── .eslintrc.json ├── .gitignore ├── GraphiQL_app.png ├── README.md ├── package.json ├── policies │ └── access-dynamodb.json ├── src │ ├── dataStore.js │ ├── index.js │ └── schema.js └── test │ ├── add-user.sh │ ├── all-users.sh │ ├── del-user.sh │ ├── get-a-user.sh │ └── run.sh ├── hello-world ├── README.md ├── main.js └── package.json ├── intercepting-requests ├── README.md ├── package.json └── web.js ├── iot-topic-filter ├── README.md ├── lambda.js ├── package.json └── post.js ├── pandoc-s3-converter ├── README.md ├── child-process-promise.js ├── convert.js ├── main.js ├── package.json └── s3-util.js ├── recursive-invoke ├── README.md ├── counter.json ├── index.js ├── package.json └── policy.json ├── s3-file-processing ├── README.md ├── convert.js ├── main.js └── package.json ├── sam-packaging ├── .gitignore ├── README.md ├── output.yaml ├── package.json ├── src │ ├── html-response.js │ └── index.js └── template.yaml ├── simple-bot ├── LICENSE ├── README.md ├── api.js ├── package.json └── slack-thanks.md ├── slack-delayed-response ├── README.md ├── bot.js ├── package.json └── slack-delayed-response.gif ├── stripe-checkout-payment ├── README.md ├── index.html ├── index.js └── package.json ├── svg-to-pdf-s3-converter ├── README.md ├── child-process-promise.js ├── convert.js ├── main.js ├── package.json └── s3-util.js ├── twilio-shippo-webhook ├── README.md ├── app.js └── package.json ├── using-npm-modules ├── README.md ├── main.js └── package.json ├── web-api-custom-cors ├── README.md ├── package.json └── web.js ├── web-api-custom-headers ├── README.md ├── package.json └── web.js ├── web-api-custom-status-code ├── README.md ├── package.json └── web.js ├── web-api-generic-handlers ├── README.md ├── package.json └── web.js ├── web-api-lambda-context ├── README.md ├── package.json └── web.js ├── web-api-postdeploy-configuration ├── README.md ├── package.json └── web.js ├── web-api-postdeploy-private-gateway ├── README.md ├── package.json └── web.js ├── web-api-postdeploy ├── README.md ├── package.json └── web.js ├── web-api ├── README.md ├── package.json └── web.js ├── web-serving-html ├── README.md ├── package.json └── web.js ├── web-serving-universal-react ├── README.md ├── build │ ├── client │ │ ├── asset-manifest.json │ │ ├── favicon.ico │ │ ├── manifest.json │ │ ├── service-worker.js │ │ └── static │ │ │ ├── css │ │ │ ├── main.cacbacc7.css │ │ │ └── main.cacbacc7.css.map │ │ │ ├── js │ │ │ ├── main.fb8951f8.js │ │ │ └── main.fb8951f8.js.map │ │ │ └── media │ │ │ └── logo.5d5d9eef.svg │ └── server │ │ ├── bundle.js │ │ ├── bundle.js.map │ │ └── static │ │ └── media │ │ └── logo.5d5d9eef.svg ├── config │ ├── env.js │ ├── jest │ │ ├── cssTransform.js │ │ └── fileTransform.js │ ├── paths.js │ ├── polyfills.js │ ├── production.js │ ├── webpack.config.base.js │ ├── webpack.config.client.dev.js │ ├── webpack.config.client.prod.js │ ├── webpack.config.server.js │ └── webpackDevServer.config.js ├── dev-env.json ├── index.js ├── localServer.js ├── package-lock.json ├── package.json ├── prod-env.json ├── public │ ├── favicon.ico │ └── manifest.json ├── scripts │ ├── build-client.js │ ├── build-server.js │ ├── start.js │ └── test.js └── src │ ├── client │ ├── index.css │ ├── index.js │ └── registerServiceWorker.js │ ├── server │ ├── app.js │ ├── index.js │ └── render.js │ └── shared │ ├── App.js │ ├── App.test.js │ └── logo.svg └── website-email-alert ├── README.md ├── main.js ├── package.json ├── policies └── send-email.json └── test.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "crockford", 3 | "env": { 4 | "node": true, 5 | "es6": true 6 | }, 7 | "parserOptions": { 8 | "ecmaVersion": 6 9 | }, 10 | "rules": { 11 | "semi": ["error", "always"], 12 | "strict": ["error", "function"], 13 | "no-unused-vars": "error", 14 | "indent": ["error", "tab" ], 15 | "no-const-assign": "error", 16 | "one-var": "error", 17 | "prefer-const": "error", 18 | "no-var": "warn", 19 | "no-plusplus": ["error", { "allowForLoopAfterthoughts": true }], 20 | "quotes": ["error", "single", {"avoidEscape": true, "allowTemplateLiterals": true}] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/package-lock.json 2 | **/claudia*.json 3 | **/node_modules 4 | **/npm-debug.log 5 | .DS_Store 6 | **/.DS_Store 7 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Use the [Claudia.js Gitter Chat](https://gitter.im/claudiajs/claudia) instead to ask a general question or request assistance/support. 2 | 3 | Please use GitHub issues only to report bugs with example projects. To report a bug or a problem, please fill in the sections below. The more you provide, the better we'll be able to help. 4 | 5 | --- 6 | 7 | * Expected behaviour: 8 | 9 | * What actually happens: 10 | 11 | * Link to a minimal, executable project that demonstrates the problem: 12 | 13 | * Steps to install the project: 14 | 15 | * Steps to reproduce the problem: 16 | 17 | -------------------------------------------------------------------------------- /alexa-skill/README.md: -------------------------------------------------------------------------------- 1 | # Amazon Alexa Skill with Claudia Bot Builder 2 | 3 | This is an example how to build a simple Alexa skill using [Claudia Bot Builder](https://github.com/claudiajs/claudia-bot-builder). Bot Builder will handle the parsing and response packaging for the most common scenarios, and you can use the full Alexa API for more advanced processing. To configure your bot with this new interface, use `--configure-alexa-skill` while doing `claudia update`. 4 | 5 | This [simple skill](bot.js) just spells out English language phrases, and shows how to use simple text requests/responses and more complex Alexa API features. [See it in action](https://vimeo.com/192685945) 6 | 7 | [![Spelling Bee Alexa Skill Demo](https://claudiajs.com/assets/spelling-bee-video-splash.png)](https://vimeo.com/192685945) 8 | 9 | ## Installing from source code 10 | 11 | To try it out, follow these steps: 12 | 13 | 1. Create a new Alexa Skills app at from the [Amazon Developer Console](https://developer.amazon.com/edw/home.html) 14 | 2. On the **Interaction Model** screen of the app setup, create the [intents and utterances](setup.md) 15 | 3. On the **Configuration** screen of the app setup, select HTTPS integration 16 | 4. run `npm run create` in this project to deploy the chat bot, and copy the endpoint URL it prints out, then paste to the HTTPS endpoint input box in the app setup screen in the Amazon Developer Console 17 | 5. add a name to your chatbot when NPM deployment asks, then complete the deployment process 18 | 6. Complete the app setup in the Amazon Developer Console 19 | 7. You can enable the skill for your Amazon account in the **Test** tab, and try it out. For example, say 'Alexa, ask Spelling Bee how do you spell Magic' 20 | 21 | ## Privacy 22 | 23 | This bot keeps no user information. 24 | 25 | ## License 26 | 27 | The MIT License (MIT) 28 | 29 | Copyright (c) 2016 Gojko Adzic, Alexander Simovic, Slobodan Stojanovic 30 | 31 | 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: 32 | 33 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 34 | 35 | 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. 36 | -------------------------------------------------------------------------------- /alexa-skill/bot.js: -------------------------------------------------------------------------------- 1 | /*global require, module, console */ 2 | const botBuilder = require('claudia-bot-builder'), 3 | getIntentName = function (alexaPayload) { 4 | 'use strict'; 5 | return alexaPayload && 6 | alexaPayload.request && 7 | alexaPayload.request.type === 'IntentRequest' && 8 | alexaPayload.request.intent && 9 | alexaPayload.request.intent.name; 10 | }; 11 | const api = botBuilder( 12 | function (message, originalRequest) { 13 | 'use strict'; 14 | console.log(originalRequest.body); 15 | // message.text has all intent placeholders joined together, for quick access 16 | if (message.text) { 17 | // just return a text message to have it automatically packaged 18 | // as a PlainText Alexa response, continuing the session 19 | return 'Spelling bee says ' + message.text + ' is spelled ' + message.text.toUpperCase().split('').join('. ') + '.'; 20 | // you can use all the Alexa request properties from originalRequest.body 21 | } else if (getIntentName(originalRequest.body) === 'ExitApp'){ 22 | // return a JavaScript object to set advanced response params 23 | // this prevents any packaging from bot builder and is just 24 | // returned to Alexa as you specify 25 | return { 26 | response: { 27 | outputSpeech: { 28 | type: 'PlainText', 29 | text: 'Bye from Spelling Bee!' 30 | }, 31 | shouldEndSession: true 32 | } 33 | }; 34 | } else { 35 | return {}; 36 | } 37 | }, 38 | { platforms: ['alexa'] } 39 | ); 40 | 41 | module.exports = api; 42 | -------------------------------------------------------------------------------- /alexa-skill/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alexa-spell", 3 | "version": "1.0.0", 4 | "description": "A sample Alexa Skill bot built using Claudia.js", 5 | "scripts": { 6 | "create": "claudia create --configure-alexa-skill --region us-east-1 --api-module bot --cache-api-config apiConfig", 7 | "update": "claudia update --cache-api-config apiConfig" 8 | }, 9 | "keywords": [], 10 | "author": "Slobodan Stojanovic (http://slobodan.me/)", 11 | "license": "MIT", 12 | "dependencies": { 13 | "claudia-bot-builder": "^4" 14 | }, 15 | "devDependencies": { 16 | "claudia": "^4" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /alexa-skill/setup.md: -------------------------------------------------------------------------------- 1 | Here are the parameters you should set up with your Alexa Skill: 2 | 3 | # Intent Schema 4 | 5 | ``` 6 | { 7 | "intents": [{ 8 | "intent": "GetSpelling", 9 | "slots": [ 10 | { 11 | "name": "Words", 12 | "type": "LIST_OF_WORDS" 13 | }] 14 | }, { 15 | "intent": "ExitApp" 16 | }] 17 | } 18 | ``` 19 | 20 | # Slot types 21 | 22 | * Type: `LIST_OF_WORDS` 23 | 24 | Examples: 25 | 26 | ``` 27 | good morning 28 | line 29 | tea 30 | promises 31 | my phone number is 12345 32 | ``` 33 | 34 | # Sample Utterances 35 | 36 | GetSpelling give me the spelling for {Words} 37 | GetSpelling how do you spell {Words} 38 | GetSpelling what is the spelling for {Words} 39 | GetSpelling spell {Words} 40 | GetSpelling to spell {Words} 41 | ExitApp stop 42 | -------------------------------------------------------------------------------- /api-gw-geolocation/README.md: -------------------------------------------------------------------------------- 1 | # Geolocation using API Gateway 2 | 3 | API Gateway requests go through CloudFront, and they will contain a few useful analytic headers, including the geo-location country 4 | where the request originated. This is a simple example that shows how to read out the country header and use it in your API. 5 | 6 | ## Get started 7 | 8 | * run `npm init` to install the dependencies 9 | * run `npm run create` to deploy this project to AWS 10 | * grab the URL printed as the result, and GET it using CURL or paste into a browser 11 | 12 | -------------------------------------------------------------------------------- /api-gw-geolocation/api.js: -------------------------------------------------------------------------------- 1 | /*global module, require */ 2 | var API = require('claudia-api-builder'), 3 | api = new API(); 4 | 5 | api.get('/', function (request) { 6 | 'use strict'; 7 | return { 8 | IP: request.context.sourceIp, 9 | Country: request.headers['CloudFront-Viewer-Country'] 10 | }; 11 | }); 12 | 13 | module.exports = api; 14 | -------------------------------------------------------------------------------- /api-gw-geolocation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-gw-geolocation", 3 | "version": "1.0.0", 4 | "description": "A simple demonstration of built-in geolocation service with API Gateway", 5 | "main": "index.js", 6 | "scripts": { 7 | "create": "claudia create --region us-east-1 --api-module api", 8 | "update": "claudia update" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "MIT", 13 | "dependencies": { 14 | "claudia-api-builder": "^4" 15 | }, 16 | "devDependencies": { 17 | "claudia": "^4" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /async-await-and-babel/.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | -------------------------------------------------------------------------------- /async-await-and-babel/README.md: -------------------------------------------------------------------------------- 1 | # Using Babel with Claudia.js 2 | 3 | The AWS Node.js execution runtime still uses Node 4.3.2, and many modern ES6 features are not available there (notably, imports and async/await). You can use Babel to transpile more modern ES6 code into ES2015. 4 | 5 | This example shows how to create a simple project that is using async/await and import, transpile it to promises and deploy it using Claudia. 6 | 7 | The transpilation output folder (in this case `bin`) is excluded from version control in [`.gitignore`](.gitignore), but explicitly listed in the `files` section of `package.json`, so it will be deployed by Claudia. 8 | 9 | ## Usage 10 | 11 | 1. Run `npm install` to install the dependencies 12 | 2. Run `npm run transpile` to see the transpiled code in `/bin` directory 13 | 3. Run `npm run create` to transpile the code and deploy it to lambda 14 | 4. To update the code run `npm run update`, it will transpile and update the code 15 | 16 | -------------------------------------------------------------------------------- /async-await-and-babel/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async-await-and-babel", 3 | "version": "1.0.0", 4 | "description": "An example of how to usei async/await and babel with Claudia to transpile to Node 4.3.2 available on AWS Lambda", 5 | "files": [ 6 | "bin" 7 | ], 8 | "scripts": { 9 | "transpile": "babel --presets es2015 --plugins async-to-promises src --out-dir bin", 10 | "create": "npm run transpile && claudia create --region us-east-1 --api-module bin/api", 11 | "update": "npm run transpile && claudia update" 12 | }, 13 | "license": "MIT", 14 | "dependencies": { 15 | "claudia-api-builder": "^4", 16 | "node-fetch": "^1.6.3" 17 | }, 18 | "devDependencies": { 19 | "babel-cli": "^6.18.0", 20 | "babel-plugin-async-to-promises": "^1.0.5", 21 | "babel-preset-es2015": "^6.18.0", 22 | "claudia": "^4" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /async-await-and-babel/src/api.js: -------------------------------------------------------------------------------- 1 | import API from 'claudia-api-builder' 2 | import fetch from 'node-fetch' 3 | 4 | const api = new API() 5 | 6 | async function handler() { 7 | const response = await fetch('http://www.colourlovers.com/api/colors/random?format=json') 8 | const colorArray = await response.json() 9 | return colorArray[0] 10 | } 11 | 12 | api.get('/', handler) 13 | 14 | module.exports = api 15 | -------------------------------------------------------------------------------- /aws-mobile-analytics/README.md: -------------------------------------------------------------------------------- 1 | # Using AWS Mobile Analytics for server-side events 2 | 3 | This example demonstrates how to use [Amazon Mobile Analytics](https://console.aws.amazon.com/mobileanalytics) to collect internal telemetry for Lambda execution. Lambda 4 | and API Gateway already support great external telemetry using CloudWatch, but if you want to trigger events and have a nice dashboard about what happens inside a Lambda function, relying on CloudWatch alone is not good. 5 | 6 | Amazon Mobile Analytics is a relatively nice event-logging solution that can aggregate sessions, report on various event parameters and aggregate metrics. 7 | 8 | ## Prerequisites 9 | 10 | * create a mobile analytics APP ID in the [AWS Mobile Analytics Console](https://console.aws.amazon.com/mobileanalytics) 11 | * deploy and configure the app using `npm run start` and provide the APP Id when asked 12 | 13 | ## Using the app 14 | 15 | After the initial deployment, Claudia will log the API URL. 16 | 17 | Open that in a browser and it will log an anonymous event. 18 | 19 | Add `?name=Tom` to the URL, and it will log an event for `Tom`. 20 | 21 | About one hour later, check your Amazon Mobile Analytics console, you should see those events in the 'Custom Events' tab. 22 | 23 | ## How it works 24 | 25 | The utility logging function is in [`eventlogger.js`](eventlogger.js). It connects to AMA and pre-populates most of the fields with the Lambda execution information. You can modify this to create session events, or payment events – check out the event structure reference below. 26 | 27 | The main module is [api.js](api.js), which just logs a simple event and demonstrates how to pass in additional event parameters and metrics. In this case, it logs the query string parameter name, and the length of the name as the metric. It uses a stage variable to store the application ID, and asks the users to configure it in a post-deploy step. (That's why `start` and `configure` [package scripts](package.json) use `--configure-analytics`). 28 | 29 | When using this example in your code, remember to copy the policy file from the [policies](policies) directory, and provide to Claudia when creating the function, so that your Lambda function gets authorised to log events. (That's why the `start` [package script](package.json) has `--policies`). 30 | 31 | ## Experimenting with the app 32 | 33 | * to upload a new version of the code to Lambda, use `npm run deploy` 34 | * to reconfigure the logging application ID, use `npm run configure` 35 | 36 | ## References 37 | 38 | * [PutEvents REST API](http://docs.aws.amazon.com/mobileanalytics/latest/ug/PutEvents.html) 39 | * [Mobile Analytics Client](https://github.com/aws/aws-sdk-mobile-analytics-js/blob/master/lib/MobileAnalyticsClient.js) 40 | * [putEvents](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/MobileAnalytics.html#putEvents-property) 41 | -------------------------------------------------------------------------------- /aws-mobile-analytics/api.js: -------------------------------------------------------------------------------- 1 | /*global module, require */ 2 | var ApiBuilder = require('claudia-api-builder'), 3 | ask = require('./ask'), 4 | EventLogger = require('./eventlogger'), 5 | api = new ApiBuilder(); 6 | 7 | 8 | module.exports = api; 9 | 10 | api.get('/', function (request) { 11 | 'use strict'; 12 | var logger = new EventLogger(request, request.env.analyticsAppId), 13 | name = request.queryString.name || ''; 14 | 15 | return logger.logEvent('executed', { name: name }, { length: name.length }).then(function () { 16 | if (name) { 17 | return 'Logged for ' + name; 18 | } else { 19 | return 'Logged empty request. Specify ?name= to log with a name'; 20 | } 21 | }); 22 | }); 23 | 24 | api.addPostDeployStep('analyticsConfig', function (options, lambdaDetails, utils) { 25 | 'use strict'; 26 | if (options['configure-analytics']) { 27 | return ask('Mobile Analytics ID', utils.Promise) 28 | .then(function (appId) { 29 | var deployment = { 30 | restApiId: lambdaDetails.apiId, 31 | stageName: lambdaDetails.alias, 32 | variables: { 33 | analyticsAppId: appId 34 | } 35 | }; 36 | return utils.apiGatewayPromise.createDeploymentPromise(deployment).then(function () { 37 | return appId; 38 | }); 39 | }); 40 | } 41 | }); 42 | -------------------------------------------------------------------------------- /aws-mobile-analytics/ask.js: -------------------------------------------------------------------------------- 1 | /*global require, module, process */ 2 | var readline = require('readline'); 3 | 4 | module.exports = function ask(question, PromiseImpl) { 5 | 'use strict'; 6 | return new PromiseImpl(function (resolve, reject) { 7 | var rl = readline.createInterface({ 8 | input: process.stdin, 9 | output: process.stdout 10 | }); 11 | rl.question(question + '? ', function (answer) { 12 | rl.close(); 13 | if (answer) { 14 | resolve(answer); 15 | } else { 16 | reject(question + ' must be provided'); 17 | } 18 | }); 19 | }); 20 | }; 21 | 22 | -------------------------------------------------------------------------------- /aws-mobile-analytics/console.js: -------------------------------------------------------------------------------- 1 | /* global require, console */ 2 | var AWS = require('aws-sdk'), 3 | mobileanalytics, 4 | clientContext = { 5 | client : { 6 | client_id: '1d2c3bfa-9c6e-751b-2722-28ee097c87ac', 7 | app_title: 'JS CLient' 8 | }, 9 | env: { 10 | platform: 'linux' 11 | }, 12 | services: { 13 | mobile_analytics: { 14 | app_id: '7c2fdcd226ae4852b01face1c179102f', 15 | sdk_name: 'aws-sdk-mobile-analytics-js', 16 | sdk_version: '0.9.1:2.4.3' 17 | } 18 | }, 19 | custom: { } 20 | }, 21 | session = { 22 | id: '1bc5391b-5d62-b818-511c-45913fd79ec4', 23 | startTimestamp: '2016-06-30T17:10:53.939Z' 24 | }, 25 | events = [ 26 | { 27 | eventType : '_session.start', 28 | timestamp: '2016-06-30T17:10:53.940Z', 29 | session: session, 30 | version: 'v2.0', 31 | attributes: {}, 32 | metrics: {} 33 | }, 34 | { 35 | eventType : 'fromConsole3', 36 | timestamp: '2016-06-30T17:11:47.210Z', 37 | session : session, 38 | version: 'v2.0', 39 | attributes: {attr1: 'attrval1'}, 40 | metrics: { METRIC_1_NAME: 1} 41 | } 42 | ], 43 | eventBatch = { 44 | 'events' : events, 45 | 'clientContext': JSON.stringify(clientContext) 46 | }; 47 | 48 | AWS.config.region = 'us-east-1'; 49 | //AWS.config.credentials = new AWS.CognitoIdentityCredentials({ 50 | // IdentityPoolId: 'us-east-1:86d3bed1-94c3-4fdf-b401-ad096cc1f0d7' //Amazon Cognito Identity Pool ID 51 | //}); 52 | mobileanalytics = new AWS.MobileAnalytics(); 53 | mobileanalytics.putEvents(eventBatch, function (err, data) { 54 | 'use strict'; 55 | console.log('err', err); 56 | console.log('data', data); 57 | }); 58 | -------------------------------------------------------------------------------- /aws-mobile-analytics/eventlogger.js: -------------------------------------------------------------------------------- 1 | /*global require, module */ 2 | var AWS = require('aws-sdk'), 3 | mobileanalytics = new AWS.MobileAnalytics(), 4 | denodeify = require('denodeify'); 5 | 6 | module.exports = function EventLogger(request, applicationId) { 7 | 'use strict'; 8 | var packageJSON = require('./package.json'), 9 | initialTimestamp = new Date().toISOString(), 10 | clientContext = { 11 | 'client' : { 12 | 'client_id' : request.context.user || request.lambdaContext.awsRequestId, 13 | 'app_title' : request.lambdaContext.functionName, 14 | 'app_version_name': request.context.stage, 15 | 'app_version_code': packageJSON.version, 16 | 'app_package_name': packageJSON.name 17 | }, 18 | 'env' : { 19 | 'platform' : 'linux' 20 | }, 21 | 'services': { 22 | 'mobile_analytics': { 23 | 'app_id' : applicationId 24 | } 25 | }, 26 | 'custom' : {} 27 | }; 28 | 29 | this.logEvent = function (eventType, additionalProperties, metrics) { 30 | var properties = { 31 | awsRequestId: request.lambdaContext.awsRequestId, 32 | path: request.context.path, 33 | userAgent: request.context.userAgent, 34 | sourceIp: request.context.sourceIp, 35 | cognitoIdentity: request.lambdaContext.cognitoIdentityId, 36 | cognitoAuthenticationProvider: request.lambdaContext.cognitoAuthenticationProvider 37 | }, 38 | event = { 39 | eventType: eventType, 40 | timestamp: new Date().toISOString(), 41 | attributes: properties, 42 | session : { 43 | 'id' : request.lambdaContext.awsRequestId, 44 | 'startTimestamp' : initialTimestamp 45 | }, 46 | version : 'v2.0', 47 | metrics: metrics || {} 48 | }, 49 | params = { 50 | clientContext: JSON.stringify(clientContext), 51 | events: [event] 52 | }; 53 | Object.keys(additionalProperties).forEach(function (property) { 54 | properties[property] = additionalProperties[property]; 55 | }); 56 | mobileanalytics.putEventsAsync = denodeify(mobileanalytics.putEvents); 57 | return mobileanalytics.putEventsAsync(params); 58 | }; 59 | }; 60 | -------------------------------------------------------------------------------- /aws-mobile-analytics/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-mobile-analytics", 3 | "version": "1.0.0", 4 | "description": "An example of how to use AWS mobile analytics with Claudia API Builder", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "claudia create --region us-east-1 --api-module api --policies policies --version dev --configure-analytics", 8 | "deploy": "claudia update --version dev", 9 | "configure": "claudia update --version dev --configure-analytics" 10 | }, 11 | "author": "", 12 | "license": "MIT", 13 | "dependencies": { 14 | "aws-sdk": "^2.4.3", 15 | "claudia-api-builder": "^4", 16 | "denodeify": "^1.2.1" 17 | }, 18 | "devDependencies": { 19 | "claudia": "^4" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /aws-mobile-analytics/policies/analytics.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": [ 7 | "mobileanalytics:PutEvents", 8 | "cognito-sync:*" 9 | ], 10 | "Resource": [ 11 | "*" 12 | ] 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /babel/.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | -------------------------------------------------------------------------------- /babel/README.md: -------------------------------------------------------------------------------- 1 | # Using Babel with Claudia.js 2 | 3 | The AWS Node.js execution runtime still uses Node 4.3, and many modern ES6 features are not available there (notably, imports and async/await). You can use Babel to transpile more modern ES6 code into ES2015. 4 | 5 | This example shows how to create a trivial transpiled project and deploy it using Claudia. 6 | 7 | The transpilation output folder (in this case `bin`) is excluded from version control in [`.gitignore`](.gitignore), but explicitly listed in the `files` section of `package.json`, so it will be deployed by Claudia. 8 | 9 | -------------------------------------------------------------------------------- /babel/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "using-babel", 3 | "version": "1.0.0", 4 | "description": "An example of how to use babel with Claudia to transpile to Node 4.3 available on AWS Lambda", 5 | "files": [ 6 | "bin" 7 | ], 8 | "scripts": { 9 | "transpile": "babel --presets es2015 src --out-dir bin", 10 | "create": "npm run transpile && claudia create --region us-east-1 --api-module bin/api", 11 | "update": "npm run transpile && claudia update" 12 | }, 13 | "license": "MIT", 14 | "dependencies": { 15 | "claudia-api-builder": "^4" 16 | }, 17 | "devDependencies": { 18 | "babel-cli": "^6.18.0", 19 | "babel-preset-es2015": "^6.18.0", 20 | "claudia": "^4" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /babel/src/api.js: -------------------------------------------------------------------------------- 1 | import API from 'claudia-api-builder'; 2 | 3 | const api = new API(); 4 | 5 | api.get('/', () => 'Hello'); 6 | 7 | module.exports = api; 8 | -------------------------------------------------------------------------------- /binary-content/README.md: -------------------------------------------------------------------------------- 1 | # Handling binary content with API Gateway 2 | 3 | This project demonstrates how to configure API Gateway using Claudia API Builder to support binary content handling. There are three endpoints: 4 | 5 | * `/img`: shows how to receive a text request and respond with binary content 6 | * `/info` endpoint shows how to receive binary content and respond with text 7 | * `/thumb`: endpoint shows how to receive and respond with binary content 8 | 9 | ## Try it out 10 | 11 | * run `npm install` to download the dependencies 12 | * run `npm run create` to deploy the project to Lambda. Grab the API ID from the result and replace `` in the example commands below 13 | 14 | ### retrieve a picture (text request, binary result) 15 | 16 | ``` 17 | curl https://.execute-api.us-east-1.amazonaws.com/latest/img -H "Accept: image/png" > 1.png 18 | ``` 19 | 20 | ### get info about a pic (binary request, text result) 21 | 22 | ``` 23 | curl --request POST -H "Content-Type: image/png" --data-binary "@img.png" https://.execute-api.us-east-1.amazonaws.com/latest/info 24 | ``` 25 | 26 | ### make a thumbnail (binary post, binary result) 27 | 28 | 29 | ``` 30 | curl --request POST -H "Content-Type: image/png" -H "Accept: image/png" --data-binary "@img.png" https://.execute-api.us-east-1.amazonaws.com/latest/thumb > thumb.png 31 | ``` 32 | 33 | ## More information 34 | 35 | Check out the [Handling Binary Content Tutorial](https://claudiajs.com/tutorials/binary-content.html) for more information on these features, and see the [Binary Data Type support for API Gateway](https://aws.amazon.com/about-aws/whats-new/2016/11/binary-data-now-supported-by-api-gateway/) for more information on how things are configured under the hood. 36 | 37 | -------------------------------------------------------------------------------- /binary-content/child-process-promise.js: -------------------------------------------------------------------------------- 1 | /*global module, require, console, Promise */ 2 | const childProcess = require('child_process'), 3 | execPromise = function (command) { 4 | 'use strict'; 5 | return new Promise(function (resolve, reject) { 6 | childProcess.exec(command, function (err) { 7 | if (err) { 8 | reject(err); 9 | } else { 10 | resolve(); 11 | } 12 | }); 13 | }); 14 | }, 15 | spawnPromise = function (command, options) { 16 | 'use strict'; 17 | return new Promise(function (resolve, reject) { 18 | const process = childProcess.spawn(command, options), 19 | result = []; 20 | process.stdout.on('data', function (buffer) { 21 | console.log(buffer.toString()); 22 | result.push(buffer.toString()); 23 | }); 24 | process.stderr.on('data', function (buffer) { 25 | console.error(buffer.toString()); 26 | }); 27 | process.on('close', function (code) { 28 | if (code !== 0) { 29 | reject(code); 30 | } else { 31 | resolve(result.join('')); 32 | } 33 | }); 34 | }); 35 | }; 36 | module.exports = { 37 | exec: execPromise, 38 | spawn: spawnPromise 39 | }; 40 | -------------------------------------------------------------------------------- /binary-content/fs-promise.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'), 2 | promisify = function (target, methodName) { 3 | 'use strict'; 4 | target[methodName + 'Promise'] = function () { 5 | const originalArgs = Array.prototype.slice.call(arguments); 6 | return new Promise((resolve, reject) => { 7 | const cb = function (err, data) { 8 | if (err) { 9 | reject(err); 10 | } else { 11 | resolve(data); 12 | } 13 | }; 14 | originalArgs.push(cb); 15 | target[methodName].apply(target, originalArgs); 16 | }); 17 | }; 18 | }; 19 | 20 | promisify(fs, 'writeFile'); 21 | promisify(fs, 'readFile'); 22 | promisify(fs, 'unlink'); 23 | promisify(fs, 'rename'); 24 | 25 | module.exports = fs; 26 | -------------------------------------------------------------------------------- /binary-content/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiajs/example-projects/32d07bea4a59023557ddef88894425c36e340ab2/binary-content/img.png -------------------------------------------------------------------------------- /binary-content/main.js: -------------------------------------------------------------------------------- 1 | /*global exports*/ 2 | const path = require('path'), 3 | os = require('os'), 4 | ApiBuilder = require('claudia-api-builder'), 5 | fs = require('./fs-promise'), 6 | childProcess = require('./child-process-promise'), 7 | api = new ApiBuilder(); 8 | 9 | module.exports = api; 10 | 11 | //api.setBinaryMediaTypes(['image/*']); 12 | 13 | api.get('/img', () => { 14 | 'use strict'; 15 | return fs.readFilePromise(path.join(__dirname, 'img.png')); 16 | }, { success: { contentType: 'image/png', contentHandling: 'CONVERT_TO_BINARY'}}); 17 | 18 | 19 | api.post('/info', (request) => { 20 | 'use strict'; 21 | const tempFileName = path.join(os.tmpdir(), request.lambdaContext.awsRequestId); 22 | let result; 23 | return fs.writeFilePromise(tempFileName, request.body) 24 | .then(() => childProcess.spawn('/usr/bin/identify', [tempFileName])) 25 | .then(picInfo => result = picInfo.replace(/[^\s]*\s/, '')) 26 | .then(() => fs.unlinkPromise(tempFileName)) 27 | .then(() => result); 28 | }, { success: { contentType: 'text/plain' } }); 29 | 30 | api.post('/thumb', (request) => { 31 | 'use strict'; 32 | const tempFileName = path.join(os.tmpdir(), request.lambdaContext.awsRequestId), 33 | thumbFileName = tempFileName + '-thumb.png'; 34 | let result; 35 | return fs.writeFilePromise(tempFileName, request.body) 36 | .then(() => childProcess.spawn('/usr/bin/convert', ['-resize', '150x', tempFileName, thumbFileName])) 37 | .then(() => fs.readFilePromise(thumbFileName)) 38 | .then(fileContents => result = fileContents) 39 | .then(() => fs.unlinkPromise(tempFileName)) 40 | .then(() => fs.unlinkPromise(thumbFileName)) 41 | .then(() => result); 42 | }, { success: { contentType: 'image/png', contentHandling: 'CONVERT_TO_BINARY' } }); 43 | -------------------------------------------------------------------------------- /binary-content/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "binary-content", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "create": "claudia create --region us-east-1 --api-module main", 7 | "update": "claudia update" 8 | }, 9 | "keywords": [], 10 | "author": "Gojko Adzic ", 11 | "license": "MIT", 12 | "devDependencies": { 13 | "claudia": "^4" 14 | }, 15 | "dependencies": { 16 | "claudia-api-builder": "^4" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /bot-with-buttons/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "defaults", 3 | "env": { 4 | "node": true, 5 | "es6": true 6 | }, 7 | "parserOptions": { 8 | "ecmaVersion": 6, 9 | "sourceType": "module" 10 | }, 11 | "rules": { 12 | "semi": ["error", "always"], 13 | "no-console": "off", 14 | "indent": ["error", 2], 15 | "quotes": ["error", "single", {"avoidEscape": true, "allowTemplateLiterals": true}], 16 | "prefer-arrow-callback": "error" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /bot-with-buttons/README.md: -------------------------------------------------------------------------------- 1 | # Chat bot with buttons 2 | 3 | A simple bot demonstrating how to create buttons and post-back actions on Facebook Messenger using Claudia.js. 4 | 5 | [Try it live](https://m.me/factbot) using Facebook Messenger. 6 | 7 | [![](https://claudiajs.com/assets/factbot.gif)](https://m.me/factbot) 8 | 9 | ## Features 10 | 11 | The bot will query [WikiData](https://www.wikidata.org) for anything you send it. If it finds a single entity, it will print out the facts that Wikidata holds on the entity. If it finds multiple entities, it will offer a sequence of buttons for the users to select between matching entities, and then print out the facts from about the selected object. 12 | 13 | Try, for example, typing in 'David Hasselhof', or just 'David'. 14 | 15 | ## Privacy 16 | 17 | This bot keeps no user information. 18 | 19 | -------------------------------------------------------------------------------- /bot-with-buttons/bot.js: -------------------------------------------------------------------------------- 1 | /*global module*/ 2 | 'use strict'; 3 | const builder = require('claudia-bot-builder'), 4 | fbTemplate = builder.fbTemplate, 5 | ws = require('./src/ws'), 6 | eTitle = entity => ((entity.label || '') + ' ' + (entity.description || '')), 7 | format = text => (text && text.substring(0, 80)); 8 | module.exports = builder((request, apiReq) => { 9 | apiReq.lambdaContext.callbackWaitsForEmptyEventLoop = false; 10 | return ws.findEntities(request.text).then(r => r.results).then(entities => { 11 | if (!entities.length) { 12 | return `Unfortunately, could not find anything about ${request.text} in Wikidata`; 13 | } else if (entities.length === 1) { 14 | let title = 'Facts about ' + eTitle(entities[0]); 15 | return ws.entityClaims(entities[0].id).then(claims => title + ':\n' + claims.join('\n')); 16 | } else { 17 | const generic = new fbTemplate.generic(); 18 | entities.slice(0, 9).forEach(entity => { 19 | generic.addBubble(format(entity.label), format(entity.description)) 20 | .addButton('View Facts', entity.id); 21 | }); 22 | return generic.get(); 23 | } 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /bot-with-buttons/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bot-with-buttons", 3 | "private": "true", 4 | "version": "1.0.0", 5 | "description": "", 6 | "scripts": { 7 | "create": "claudia create --api-module bot --region us-east-1", 8 | "configure-fb": "claudia update --configure-fb-bot", 9 | "deploy": "claudia update" 10 | }, 11 | "license": "MIT", 12 | "dependencies": { 13 | "claudia-bot-builder": "^4", 14 | "wikidata-search": "^1.0.3" 15 | }, 16 | "devDependencies": { 17 | "claudia": "^4" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /bot-with-buttons/src/ws-promise-wrap.js: -------------------------------------------------------------------------------- 1 | /*global module */ 2 | module.exports = function promiseWrap (wsCall) { 3 | return function () { 4 | var callArgs = Array.prototype.slice.call(arguments); 5 | return new Promise((resolve, reject) => { 6 | var cbHandler = (result, err) => { 7 | if (err) { 8 | reject(err); 9 | } else { 10 | resolve(result); 11 | } 12 | }; 13 | callArgs.push(cbHandler); 14 | wsCall.apply(this, callArgs); 15 | }); 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /bot-with-buttons/src/ws.js: -------------------------------------------------------------------------------- 1 | /*global module */ 2 | const wikidata = require('wikidata-search'), 3 | wsPromiseWrap = require('./ws-promise-wrap'); 4 | 5 | module.exports = { 6 | findEntities(title) { 7 | var ws = new wikidata.WikidataSearch(); 8 | ws.searchPromise = wsPromiseWrap(ws.search); 9 | ws.set('search', title); 10 | return ws.searchPromise(); 11 | }, 12 | entityClaims (entityId) { 13 | var ws = new wikidata.WikidataSearch(); 14 | ws.getEntitiesPromise = wsPromiseWrap(ws.getEntities); 15 | return ws.getEntitiesPromise([entityId], true).then(res => 16 | res && res.entities && res.entities[0] && res.entities[0].claims && 17 | res.entities[0].claims.map(t => t.property + ': ' + t.value) 18 | ); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /cors-settings/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /cors-settings/README.md: -------------------------------------------------------------------------------- 1 | # cors test example 2 | 3 | ## set up 4 | 5 | 0. run `npm i` to get the deps 6 | 1. (optional) change the region in the `package.json` config section 7 | 2. run `npm run create` to deploy a test lambda 8 | 3. run `npm run serve` to run the web site on port 8080 9 | 4. open http://localhost:8080 -- fill in some data into the form and submit 10 | 11 | ## change the test 12 | 13 | * change the lambda code in `api.js` and run `npm run update` to redeploy 14 | * change the web site code in `web.js` and re-run `npm run serve` to recompile the javascript 15 | 16 | ## clean up after test 17 | 18 | * `npm run destroy` to remove the API 19 | -------------------------------------------------------------------------------- /cors-settings/api.js: -------------------------------------------------------------------------------- 1 | const Api = require('claudia-api-builder'), 2 | api = new Api(); 3 | 4 | api.post('/registrants', function (request) { 5 | return request.body; 6 | }); 7 | 8 | api.corsMaxAge(60); 9 | api.corsOrigin('http://localhost:8080') 10 | 11 | module.exports = api; 12 | -------------------------------------------------------------------------------- /cors-settings/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 |
9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /cors-settings/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-cors", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "create": "claudia create --api-module api --region $npm_package_config_awsRegion", 6 | "update": "claudia update", 7 | "serve": "webpack && http-server -c-1", 8 | "destroy": "claudia destroy" 9 | }, 10 | "config": { 11 | "awsRegion": "us-east-1" 12 | }, 13 | "files": [ 14 | "api.js" 15 | ], 16 | "devDependencies": { 17 | "claudia": "^4", 18 | "http-server": "^0.11.1", 19 | "webpack": "^3.10.0" 20 | }, 21 | "dependencies": { 22 | "claudia-api-builder": "^4" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /cors-settings/web.js: -------------------------------------------------------------------------------- 1 | const claudiaConfig = require('./claudia.json'), 2 | region = require('./package.json').config.awsRegion, 3 | url = `https://${claudiaConfig.api.id}.execute-api.${region}.amazonaws.com/latest`, 4 | sendFetchRequest = function (endpoint, content) { 5 | const headers = new Headers({ 6 | 'Content-Type': 'application/json' 7 | }), 8 | requestParams = { 9 | method: 'POST', 10 | headers: headers, 11 | mode: 'cors', 12 | body: JSON.stringify(content) 13 | }; 14 | console.log('sending fetch request'); 15 | return fetch(`${url}/${endpoint}`, requestParams).then(response => response.text()); 16 | }, 17 | sendXHRRequest = function (endpoint, content) { 18 | console.log('sending xhr request'); 19 | return new Promise((resolve, reject) => { 20 | const oReq = new XMLHttpRequest(); 21 | oReq.addEventListener('load', function () { 22 | resolve(this.responseText); 23 | }); 24 | oReq.addEventListener('error', reject); 25 | oReq.addEventListener('abort', reject); 26 | oReq.open('POST', `${url}/${endpoint}`); 27 | oReq.setRequestHeader('Content-Type', 'application/json'); 28 | oReq.send(JSON.stringify(content)); 29 | }); 30 | }; 31 | 32 | document.addEventListener('DOMContentLoaded', () => { 33 | document.querySelector('#corsform').addEventListener('submit', e => { 34 | e.preventDefault(); 35 | try { 36 | const form = e.target, 37 | inputFields = Array.from(form.querySelectorAll('input[type=text]')), 38 | resultField = form.querySelector('textarea'), 39 | requestTypeXhr = form.querySelector('input[type=radio][value=xhr]'), 40 | requestMethod = (requestTypeXhr.checked) ? sendXHRRequest: sendFetchRequest, 41 | content = {}; 42 | inputFields.forEach(field => { 43 | content[field.getAttribute('name')] = field.value; 44 | }); 45 | requestMethod('registrants', content) 46 | .then(respText => { 47 | resultField.value = respText || 'empty response'; 48 | }).catch(e => { 49 | console.error('received error', e); 50 | resultField.value = e.message || e; 51 | }); 52 | } catch (e) { 53 | console.error('error submitting request', e); 54 | } 55 | }); 56 | }); 57 | 58 | 59 | -------------------------------------------------------------------------------- /cors-settings/webpack.config.js: -------------------------------------------------------------------------------- 1 | /*global require, module, __dirname, process, console */ 2 | const path = require('path'); 3 | module.exports = { 4 | entry: { 'main': path.resolve(__dirname, 'web.js') }, 5 | output: { 6 | path: path.resolve(__dirname, 'build'), 7 | filename: '[name].js' 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /custom-authorizers/README.md: -------------------------------------------------------------------------------- 1 | # Using a custom authorizer 2 | 3 | This example shows how to configure and use a custom authorizer. This is a trivially simple authorizer that just checks for anything in the `Authorization` header. It expects the header to contain a username and a access key/password separated by a dash. 4 | 5 | ## Prerequisites 6 | 7 | Custom authorizer support requires `claudia-api-builder` 1.6.0, and `claudia 1.7.1` 8 | 9 | ## Setting it up 10 | 11 | Follow these steps: 12 | 13 | * `npm install` to grab the dependencies 14 | * `npm run create-authorizer` to set up a new Lambda function for the authorizer 15 | * `npm run create-api` to set up the REST API using the custom authorizer 16 | 17 | ## Trying it out 18 | 19 | Grab the URL of your API (printed by the `create-api` step), and execute using CURL: 20 | 21 | * the root resource has no authorizer attached, so you should be able to directly call it without the `Authorization` header 22 | * the `/locked` GET resource has an authorizer, but the authorizer policy won't allow access to any user. Try it with and without the `Authorization` header and see the difference in results 23 | * the `/unlocked` GET resource has an authorizer, and the authorizer policy allows access to any user. It will print the user ID (first part of the header, before a dash). Try it with and without an authorization header to see the different results. 24 | 25 | ![](demo.gif) 26 | 27 | ## How it works 28 | 29 | Check out the [authorizer.js](authorizer.js) to see the implementation of the trivial authorizer. Then see [index.js](index.js) for how this authorizer is used in the API Gateway. 30 | -------------------------------------------------------------------------------- /custom-authorizers/authorizer.js: -------------------------------------------------------------------------------- 1 | /*global exports, console */ 2 | var generatePolicy = function (authToken, methodArn) { 3 | 'use strict'; 4 | var tmp = methodArn.split(':'), 5 | apiGatewayArnTmp = tmp[5].split('/'), 6 | awsAccountId = tmp[4], 7 | region = tmp[3], 8 | restApiId = apiGatewayArnTmp[0], 9 | stage = apiGatewayArnTmp[1]; 10 | 11 | return { 12 | 'principalId': authToken.split('-')[0], 13 | 'policyDocument': { 14 | 'Version': '2012-10-17', 15 | 'Statement': [{ 16 | 'Effect': 'Allow', 17 | 'Action': [ 18 | 'execute-api:Invoke' 19 | ], 20 | 'Resource': [ 21 | 'arn:aws:execute-api:' + region + ':' + awsAccountId + ':' + restApiId + '/' + stage + '/GET/unlocked' 22 | ] 23 | }] 24 | } 25 | }; 26 | }; 27 | exports.auth = function testAuth(event, context, callback) { 28 | 'use strict'; 29 | console.log('got event', event); 30 | 31 | /* 32 | * { 33 | * "type":"TOKEN", 34 | * "authorizationToken":"", 35 | * "methodArn":"arn:aws:execute-api:::///" 36 | * } 37 | */ 38 | if (event && event.authorizationToken && event.methodArn) { 39 | callback(null, generatePolicy(event.authorizationToken, event.methodArn)); 40 | } else { 41 | callback('Unauthorized'); 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /custom-authorizers/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiajs/example-projects/32d07bea4a59023557ddef88894425c36e340ab2/custom-authorizers/demo.gif -------------------------------------------------------------------------------- /custom-authorizers/index.js: -------------------------------------------------------------------------------- 1 | /*global require, module */ 2 | var ApiBuilder = require('claudia-api-builder'), 3 | api = new ApiBuilder(); 4 | 5 | module.exports = api; 6 | 7 | api.registerAuthorizer('testAuth', { 8 | lambdaName: 'testAuth', 9 | lambdaVersion: true 10 | }); 11 | 12 | api.get('/', function () { 13 | 'use strict'; 14 | return 'OK'; 15 | }); 16 | 17 | api.get('/locked', function () { 18 | 'use strict'; 19 | return 'NOT-OK'; 20 | }, { customAuthorizer: 'testAuth' }); 21 | 22 | api.get('/unlocked', function (request) { 23 | 'use strict'; 24 | return 'OK for ' + request.context.authorizerPrincipalId; 25 | }, { customAuthorizer: 'testAuth' }); 26 | -------------------------------------------------------------------------------- /custom-authorizers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "custom-authorizers", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "create-authorizer": "claudia create --name testAuth --region us-east-1 --handler authorizer.auth --version dev --config claudia-auth.json", 8 | "update-authorizer": "claudia update --version dev --config claudia-auth.json", 9 | "create-api": "claudia create --name testApi --region us-east-1 --api-module index --config claudia-api.json --version dev", 10 | "update-api": "claudia update --config claudia-api.json --version dev" 11 | }, 12 | "author": "", 13 | "license": "MIT", 14 | "dependencies": { 15 | "claudia-api-builder": "^4" 16 | }, 17 | "devDependencies": { 18 | "claudia": "^4" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /deploy-proxy-api/README.md: -------------------------------------------------------------------------------- 1 | # Deploying Proxy APIs easily 2 | 3 | Since September 2016, API Gateway supports proxying requests to Lambda without having to set up specific handlers, methods or conversion templates. This can be a good alternative to using Claudia API Builder if you need to set up something more generic. Claudia 2.0 can automatically deploy a proxy API for your Lambda function -- just add `--deploy-proxy-api` when creating it. 4 | 5 | To try out this example, use: 6 | 7 | 1. `npm install` to grab the dependencies 8 | 2. `npm start` to deploy the [main.js](main.js) to Lambda and create a proxy api 9 | 10 | The procedure will print out a URL. You can send HTTP requests to it, and any sub-URLs, and get back the full request object as the response. 11 | 12 | For more information on this feature, check out [Build an API Gateway API Using Proxy Integration and a Proxy Resource](http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy.html ) from AWS documentation. 13 | -------------------------------------------------------------------------------- /deploy-proxy-api/main.js: -------------------------------------------------------------------------------- 1 | /*global exports*/ 2 | exports.handler = function (event, context) { 3 | 'use strict'; 4 | context.succeed({ 5 | statusCode: 200, 6 | headers: { 'Content-Type': 'application/json' }, 7 | body: JSON.stringify(event) 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /deploy-proxy-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deploy-proxy-api", 3 | "version": "1.0.0", 4 | "description": "a simple example deploying a proxy API with a Lambda function", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "claudia create --region us-east-1 --deploy-proxy-api --handler main.handler" 8 | }, 9 | "author": "Gojko Adzic ", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "claudia": "^4" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /detecting-context/README.md: -------------------------------------------------------------------------------- 1 | # Detecting context 2 | 3 | This example shows how a lambda function can detect the alias it was called with. This is useful, for example, to load the correct configuration file when a single function runs for multiple versions, such as development, production or testing. 4 | 5 | ## Try it out 6 | 7 | Install the dependencies using 8 | 9 | ```bash 10 | npm install 11 | ``` 12 | 13 | Then, set up the function using 14 | 15 | ```bash 16 | npm run setup 17 | ``` 18 | 19 | This will also set up two aliases, `dev` and `production`, for the same function version. You can now invoke the development version using 20 | 21 | ```bash 22 | npm run call-dev 23 | ``` 24 | 25 | Call the production version using: 26 | 27 | ```bash 28 | npm run call-production 29 | ``` 30 | 31 | In both cases, you should see the output stating the function alias and the numeric version. 32 | 33 | ## Try this next 34 | 35 | You can change the code and update the deployment, using 36 | 37 | ```bash 38 | npm run deploy 39 | ``` 40 | 41 | this will re-assign both `dev` and `production` to a new version, so executing the call tests should show an increased numeric version as well. 42 | 43 | Alternatively, update only the development version, using 44 | 45 | ```bash 46 | npm run deploy-dev 47 | ``` 48 | 49 | This should allow you to see different numeric versions for `dev` and `production` when executing the call tests. 50 | 51 | -------------------------------------------------------------------------------- /detecting-context/api.js: -------------------------------------------------------------------------------- 1 | exports.handler = function (event, context) { 2 | var alias = context.invokedFunctionArn.replace(/.*:/g,''); 3 | context.succeed('executing: ' + alias + ' (version ' + context.functionVersion + ')'); 4 | }; 5 | -------------------------------------------------------------------------------- /detecting-context/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "detecting-context", 3 | "version": "1.0.0", 4 | "description": "example showing how to detect if lambda is running as production or development", 5 | "scripts": { 6 | "setup": "claudia create --version dev --region us-east-1 --handler api.handler && claudia set-version --version production", 7 | "deploy": "claudia update --version dev && claudia set-version --version production", 8 | "deploy-dev": "claudia update --version dev", 9 | "call-dev": "claudia test-lambda --version dev", 10 | "call-production": "claudia test-lambda --version production" 11 | }, 12 | "devDependencies": { 13 | "claudia": "^4" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /dynamodb-example/README.md: -------------------------------------------------------------------------------- 1 | # Storing and retrieving from DynamoDB 2 | 3 | This simple example shows how to store, retrieve and delete some data (users) to and from DynamoDB, the AWS document database. It also shows how to use API Gateway stage variables to store configuration information for different Lambda versions (eg development, testing or production resources). 4 | 5 | _updated: 30 July 2016 - using AWS SDK DynamoDB client directly, with promises, and configuring stage variables as post-deploy steps_ 6 | 7 | ## Prerequisites 8 | 9 | Create a table in DynamoDB, with a `string` primary key called `userid`. You can do that from the DynamoDB web console, or using the AWS CLI command line. Here is an example command that will create the table with the minimal provisioned throughput: 10 | 11 | ```bash 12 | aws dynamodb create-table --table-name dynamo-test \ 13 | --attribute-definitions AttributeName=userid,AttributeType=S \ 14 | --key-schema AttributeName=userid,KeyType=HASH \ 15 | --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1 \ 16 | --query TableDescription.TableArn --output text 17 | ``` 18 | 19 | This example project includes an IAM access policy that will grant the lambda function access to all your DynamoDB tables, to make it easier to get started. If you wish, you can edit the [policies/access-dynamodb.json](policies/access-dynamodb.json) file and restrict the access to your new table only. 20 | 21 | ## Get started 22 | 23 | To set this up, first [set up the credentials](https://github.com/claudiajs/claudia/blob/master/getting_started.md#configuring-access-credentials), then: 24 | 25 | 1. run `npm install` to grab the dependencies 26 | 2. run `npm run create` to create the lambda project under the default name on AWS. This will also ask you for the table name, and enter it when required. If you used the example above, the table name will be `dynamo-test` 27 | 3. Test the API with using the [example requests below](#testing) 28 | 29 | For subsequent updates, use the `npm run deploy` command. 30 | 31 | ## The API 32 | 33 | * `POST` to `/user` - stores a new user data object 34 | * `GET` to `/user/{id}` - returns user with id `{id}` 35 | * `DELETE` to `/user/{id}` - deletes the user with id `{id}` 36 | 37 | ## Testing 38 | 39 | Claudia will print the API URL after it is created (typically something in the format `https://[API ID].execute-api.[REGION].amazonaws.com/latest`. Replace `` with that value in the examples below: 40 | 41 | You can test the API by using `curl` (or using a fancier client like [Postman](https://www.getpostman.com/)). Below are some examples with `curl`. 42 | 43 | ### Create new user 44 | 45 | This will create a user from the data stored in [example.json](example.json). Change the data in the file to create other users: 46 | 47 | ```bash 48 | curl -H "Content-Type: application/json" -X POST --data @example.json /user 49 | ``` 50 | 51 | ### Get user 52 | 53 | This will get the user ID 123 (replace the ID to get other users) 54 | 55 | ```bash 56 | curl /user/123 57 | ``` 58 | 59 | ### Delete user 60 | 61 | This will delete the user ID 123 (replace the ID to get other users) 62 | 63 | ```bash 64 | curl -X DELETE /user/123 65 | ``` 66 | 67 | ## How it works 68 | 69 | AWS SDK supports `Promise` calls, and we're using them in the examples directly to make API request processing wait for an external asynchronous call. Just return the promise from the request processor and it all works out of the box. Alternatively, use `.then` on that promise and return something else, to override the result. The `POST` handler creating a new user returns the DynamoDB result directly. The `GET` handler post-processes the result, returning only a sub-object. The `DELETE` handler replaces the result with a human-readable message. 70 | 71 | The table name, stored in the API Gateway stage variables, is passed to each request processor in the `request.env` key-value map. Check out [index.js](index.js) to see it used. 72 | 73 | The value is set during the first deployment, using `--configure-db`. This works using a post-deploy step (check out the last line of [index.js](index.js) for the actual setup, and [Configuring stage variables using post-deployment steps](https://github.com/claudiajs/claudia-api-builder/blob/master/docs/api.md#configuring-stage-variables-using-post-deployment-steps) for more information about the API). 74 | -------------------------------------------------------------------------------- /dynamodb-example/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "userId" : "123", 3 | "name" : "Marcus Hammarberg", 4 | "age" : 43 5 | } 6 | 7 | -------------------------------------------------------------------------------- /dynamodb-example/index.js: -------------------------------------------------------------------------------- 1 | /*global require, module*/ 2 | var ApiBuilder = require('claudia-api-builder'), 3 | AWS = require('aws-sdk'), 4 | api = new ApiBuilder(), 5 | dynamoDb = new AWS.DynamoDB.DocumentClient(); 6 | 7 | module.exports = api; 8 | 9 | // Create new user 10 | api.post('/user', function (request) { 11 | 'use strict'; 12 | var params = { 13 | TableName: request.env.tableName, 14 | Item: { 15 | userid: request.body.userId, 16 | name: request.body.name, 17 | age: request.body.age 18 | } 19 | }; 20 | // return dynamo result directly 21 | return dynamoDb.put(params).promise(); 22 | }, { success: 201 }); // Return HTTP status 201 - Created when successful 23 | 24 | // get user for {id} 25 | api.get('/user/{id}', function (request) { 26 | 'use strict'; 27 | var id, params; 28 | // Get the id from the pathParams 29 | id = request.pathParams.id; 30 | params = { 31 | TableName: request.env.tableName, 32 | Key: { 33 | userid: id 34 | } 35 | }; 36 | 37 | // post-process dynamo result before returning 38 | return dynamoDb.get(params).promise().then(function (response) { 39 | return response.Item; 40 | }); 41 | }); 42 | 43 | // delete user with {id} 44 | api.delete('/user/{id}', function (request) { 45 | 'use strict'; 46 | var id, params; 47 | // Get the id from the pathParams 48 | id = request.pathParams.id; 49 | params = { 50 | TableName: request.env.tableName, 51 | Key: { 52 | userid: id 53 | } 54 | }; 55 | // return a completely different result when dynamo completes 56 | return dynamoDb.delete(params).promise() 57 | .then(function () { 58 | return 'Deleted user with id "' + id + '"'; 59 | }); 60 | }, {success: { contentType: 'text/plain'}}); 61 | 62 | api.addPostDeployConfig('tableName', 'DynamoDB Table Name:', 'configure-db'); 63 | 64 | -------------------------------------------------------------------------------- /dynamodb-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dynamodb-examples", 3 | "version": "2.0.0", 4 | "description": "An example on how to store and get data from dynamodb", 5 | "main": "index.js", 6 | "license": "MIT", 7 | "files": [ 8 | "*.js" 9 | ], 10 | "scripts": { 11 | "create": "claudia create --region us-east-1 --api-module index --policies policies --configure-db", 12 | "deploy": "claudia update", 13 | "reconfigure": "claudia update --configure-db" 14 | }, 15 | "author": "Marcus Hammarberg @marcusoftnet", 16 | "keywords": [ 17 | "claudia", 18 | "aws-lambda", 19 | "dynamodb" 20 | ], 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/claudiajs/example-projects.git" 24 | }, 25 | "dependencies": { 26 | "aws-sdk": "^2.4.11", 27 | "claudia-api-builder": "^4" 28 | }, 29 | "devDependencies": { 30 | "claudia": "^4" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /dynamodb-example/policies/access-dynamodb.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Action": [ 6 | "dynamodb:DeleteItem", 7 | "dynamodb:GetItem", 8 | "dynamodb:PutItem" 9 | ], 10 | "Effect": "Allow", 11 | "Resource": "*" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /env-variables/README.md: -------------------------------------------------------------------------------- 1 | # Using environment variables 2 | 3 | A simple project demonstrating how to manage and use [Lambda environment variables](http://docs.aws.amazon.com/lambda/latest/dg/env_variables.html). 4 | 5 | Check out the `scripts` section of [`package.json`](package.json) for details on what individual commands are doing, and [Managing Lambda Versions Tutorial](https://claudiajs.com/tutorials/versions.html) for more information on what is happening under the hood. 6 | 7 | To try it out, run: 8 | 9 | * `npm install` to fetch the dependencies 10 | * `npm run create` to create the `development` version with some stage variables 11 | * `npm run check-dev` to execute the `development` version of the Lambda function and print out the variables 12 | * `npm run set-production` to create a `production` version from the current code, and changed the variable name 13 | * `npm run check-production` to execute the `production` version and print out the names. Note that the PROGRESS variable was removed because it was not specified in the previous update. 14 | * `npm run check-dev` to execute the `development` version of the Lambda function and print out the variables. Note that the old values are still there, as `development` is pointing to an older version 15 | * `npm run reassign-dev` to reassign the development alias to the latest version without changing any vars. 16 | * `npm run check-dev` to execute the `development` version of the Lambda function and print out the variables. Note that the new values from production are now used. `dev` and `production` are pointing to the same numerical version, and the last update did not change any variables. 17 | * `npm run update` to update the development version and another variable name 18 | * `npm run check-dev` to execute the `development` version of the Lambda function and print out the variables 19 | * `npm run load-from-json` to load both variables into the `development` alias 20 | * `npm run check-dev` to execute the `development` version of the Lambda function and print out the variables 21 | * `npm run check-production` to see the old values in the `production` environment. Note that they are unchanged. 22 | -------------------------------------------------------------------------------- /env-variables/dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "PROGRESS": "Loaded", 3 | "PROJECT_NAME": "Development" 4 | } 5 | -------------------------------------------------------------------------------- /env-variables/lambda.js: -------------------------------------------------------------------------------- 1 | /*global exports, process */ 2 | exports.handler = function (event, context, callback) { 3 | 'use strict'; 4 | callback(null, { 5 | progress: process.env.PROGRESS, 6 | projectName: process.env.PROJECT_NAME 7 | }); 8 | }; 9 | -------------------------------------------------------------------------------- /env-variables/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "env-variables", 3 | "version": "1.0.0", 4 | "description": "A simple project demonstrating how to set and access environment variables in AWS Lambda", 5 | "main": "", 6 | "scripts": { 7 | "create": "claudia create --handler lambda.handler --region us-east-1 --version dev --set-env PROGRESS=CREATED,PROJECT_NAME=Development", 8 | "update": "claudia update --version dev --set-env PROGRESS=UPDATED,PROJECT_NAME=Development", 9 | "set-production": "claudia set-version --version production --set-env PROJECT_NAME=Production", 10 | "reassign-dev": "claudia set-version --version dev", 11 | "load-from-json": "claudia set-version --version dev --set-env-from-json dev.json", 12 | "check-dev": "claudia test-lambda --version dev", 13 | "check-production": "claudia test-lambda --version production" 14 | }, 15 | "license": "MIT", 16 | "devDependencies": { 17 | "claudia": "^4" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /express-app-lambda/README.md: -------------------------------------------------------------------------------- 1 | # Running Express apps in AWS Lambda 2 | 3 | This is a simple example that shows how to deploy an existing [Express](http://expressjs.com/) application, with minimal changes, to AWS Lambda. 4 | 5 | ## Running the example 6 | 7 | 1. run `npm install` to grab the dependencies 8 | 2. run `npm run generate-proxy` to create a simple proxy API for the express app 9 | 3. run `npm run deploy` to send everything up to AWS Lambda 10 | 11 | The third step will print out a URL you can use to access the express app. 12 | 13 | ## Updating the app 14 | 15 | 1. Change [`app.js`](app.js) 16 | 2. (Optionally) use `npm install -S` to install additional dependencies (always save them to `package.json` using `-S`) 17 | 3. Run `npm run update` to send the new version up to AWS. No need to generate the proxy again 18 | 19 | ## More information and limitations 20 | 21 | See the [Running Express Apps in AWS Lambda](https://claudiajs.com/tutorials/serverless-express.html) tutorial. 22 | -------------------------------------------------------------------------------- /express-app-lambda/app.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const express = require('express') 3 | const app = express() 4 | 5 | app.get('/', (req, res) => { 6 | res.sendFile(`${__dirname}/index.html`) 7 | }) 8 | 9 | // app.listen(3000) // <-- comment this line out from your app 10 | 11 | module.exports = app // export your app so aws-serverless-express can use it 12 | -------------------------------------------------------------------------------- /express-app-lambda/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Serverless Express Example 5 | 6 | 7 |

Hello from Express

8 |

this page is served by Express, through 9 | aws-serverless-express and 10 | Claudia.JS Proxy API

11 | 12 | 13 | -------------------------------------------------------------------------------- /express-app-lambda/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "claudia-express", 3 | "version": "1.0.0", 4 | "description": "Example application for running a Node Express app on AWS Lambda using Amazon API Gateway.", 5 | "main": "lambda.js", 6 | "scripts": { 7 | "deploy": "claudia create --handler lambda.handler --deploy-proxy-api --region us-east-1", 8 | "update": "claudia update", 9 | "generate-proxy": "claudia generate-serverless-express-proxy --express-module app" 10 | }, 11 | "license": "Apache-2.0", 12 | "dependencies": { 13 | "aws-serverless-express": "^1.3.0", 14 | "express": "^4.14.0" 15 | }, 16 | "devDependencies": { 17 | "claudia": "5.1.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /fastify-app-lambda/README.md: -------------------------------------------------------------------------------- 1 | # Running fastify apps in AWS Lambda 2 | 3 | This is a simple example that shows how to deploy an existing [fastify](https://github.com/fastify/fastify) application, with minimal changes, to AWS Lambda. 4 | 5 | ## Running the example 6 | 7 | 1. run `npm install` to grab the dependencies 8 | 2. run `npm run deploy` to send everything up to AWS Lambda 9 | 10 | The third step will print out a URL you can use to access the fastify app. 11 | 12 | ## Updating the app 13 | 14 | 1. Change [`app.js`](app.js) 15 | 2. (Optionally) use `npm install -S` to install additional dependencies (always save them to `package.json` using `-S`) 16 | 3. Run `npm run update` to send the new version up to AWS. No need to generate the proxy again 17 | 18 | ## More information and limitations 19 | 20 | See the [Running Express or fastify Apps in AWS Lambda](https://claudiajs.com/tutorials/serverless-express.html) tutorial. 21 | -------------------------------------------------------------------------------- /fastify-app-lambda/app.js: -------------------------------------------------------------------------------- 1 | const fastify = require('fastify'); 2 | 3 | const app = fastify(); 4 | app.get('/', (request, reply) => reply.send({ hello: 'world' })); 5 | 6 | if (require.main !== module) { 7 | // called directly i.e. "node app" 8 | app.listen(3000, (err) => { 9 | if (err) console.error(err); 10 | console.log('server listening on 3000'); 11 | }); 12 | } else { 13 | // required as a module => executed on aws lambda 14 | module.exports = app; 15 | } -------------------------------------------------------------------------------- /fastify-app-lambda/lambda.js: -------------------------------------------------------------------------------- 1 | const awsLambdaFastify = require('aws-lambda-fastify') 2 | const app = require('./app'); 3 | 4 | const proxy = awsLambdaFastify(app) 5 | // or 6 | // const proxy = awsLambdaFastify(app, { binaryMimeTypes: ['application/octet-stream'] }) 7 | 8 | exports.handler = proxy; 9 | // or 10 | // exports.handler = (event, context, callback) => proxy(event, context, callback); 11 | // or 12 | // exports.handler = (event, context) => proxy(event, context); 13 | // or 14 | // exports.handler = async (event, context) => proxy(event, context); -------------------------------------------------------------------------------- /fastify-app-lambda/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "claudia-fastify", 3 | "version": "1.0.0", 4 | "description": "Example application for running a Node fastify app on AWS Lambda using Amazon API Gateway.", 5 | "main": "lambda.js", 6 | "scripts": { 7 | "deploy": "claudia create --handler lambda.handler --deploy-proxy-api --region us-east-1", 8 | "update": "claudia update", 9 | "generate-proxy": "claudia generate-serverless-express-proxy --express-module app" 10 | }, 11 | "license": "Apache-2.0", 12 | "dependencies": { 13 | "aws-lambda-fastify": "1.0.3", 14 | "fastify": "2.6.0" 15 | }, 16 | "devDependencies": { 17 | "claudia": "^5" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /github-repo-labels/README.md: -------------------------------------------------------------------------------- 1 | # Dynamic SVG labels with basic stats for GitHub repositories 2 | 3 | This example project serves simple SVG labels using Lambda, such as the one below: 4 | 5 | [![claudiajs/claudia](https://repolabels.net/claudiajs/claudia/large.svg)](https://github.com/claudiajs/claudia/) 6 | 7 | See [repolabels.net](https://repolabels.net) for a live version of this example, and generate labels for your own repositories. 8 | 9 | This example demonstrates how to: 10 | 11 | - Bind method parameters to URL path components (requests are processed as `{owner}/{repo}/{template}`) 12 | - Customise response content types (this serves `image/svg+xml`) 13 | - Manage development/testing/production API versions 14 | - Connect to a third party REST API (github) 15 | - store keys for development/testing/production access 16 | - Protect API access using API keys 17 | - reduce costs by caching results using CloudFront 18 | 19 | 20 | ## How it works 21 | 22 | Check out the 20 minute video tutorial [How to make a cheap, scalable image server with AWS](https://claudiajs.com/tutorials/image-server.html) for a detailed walk-through. 23 | 24 | [![](https://claudiajs.com/assets/tutorials/image-server-thumb.png)](https://claudiajs.com/tutorials/image-server.html) 25 | 26 | ## Getting started 27 | 28 | Create the development version of the API using `npm run start`, and deploy the production version using `npm run release`. 29 | 30 | Github limits the rate of unauthenticated requests severely, this example allows you to optionally specify two stage variables to authenticate requests to GitHub, and increase rate limits. Check out your deployed API in the AWS web console, and add two stage variables to the production stage - `githubClientId` and `githubSecret`. [Create a GitHub application](https://github.com/settings/applications/new) to get access keys. You can optionally also create a development GitHub application and store its keys in the development stage. 31 | 32 | You can change the development version and run `npm run deploy`, and you will see that the development version changed, but the production version did not. For example, uncomment `apiKeyRequired: true` in the last line of `web.js` before deploying. This will cause the API calls to require an API key before allowing the request. See [Use an API Key in API Gateway](http://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-api-keys.html) for more information on how to create an use an API key. 33 | 34 | To push the currently released version to production, run `npm run release` again. 35 | -------------------------------------------------------------------------------- /github-repo-labels/api.js: -------------------------------------------------------------------------------- 1 | /*global require, module */ 2 | var API = require('claudia-api-builder'), 3 | TimeAgo = require('time-ago'), 4 | numeral = require('numeral'), 5 | formatter = new TimeAgo(), 6 | got = require('got'), 7 | fs = require('fs-promise'), 8 | api = new API(); 9 | 10 | module.exports = api; 11 | 12 | var getRepoDetails = function (owner, repo, env) { 13 | 'use strict'; 14 | var appAuthorization = '', url; 15 | if (env.githubClientId && env.githubSecret) { 16 | appAuthorization = '?client_id=' + env.githubClientId + '&client_secret=' + env.githubSecret; 17 | } 18 | url = 'https://api.github.com/repos/' + owner + '/' + repo + appAuthorization; 19 | return got(url).then(function (response) { 20 | return JSON.parse(response.body); 21 | }); 22 | }; 23 | api.get('{owner}/{repo}/{template}', function (request) { 24 | 'use strict'; 25 | var template; 26 | return fs.readFile('svg/' + request.pathParams.template, 'utf8').then(function (contents) { 27 | template = contents; 28 | }).then(function () { 29 | return getRepoDetails(request.pathParams.owner, request.pathParams.repo, request.env); 30 | }).then(function (repoDetails) { 31 | var displayName = repoDetails.full_name.length <= 20 ? repoDetails.full_name: repoDetails.name, 32 | fmt = function (number) { 33 | if (number > 999 && number < 100000) { 34 | return '0.0a'; 35 | } 36 | return '0a'; 37 | }, 38 | replacements = { 39 | name: displayName, 40 | forks: numeral(repoDetails.forks_count).format(fmt(repoDetails.forks_count)), 41 | stars: numeral(repoDetails.stargazers_count).format(fmt(repoDetails.stargazers_count)), 42 | created: formatter.ago(repoDetails.created_at), 43 | updated: formatter.ago(repoDetails.updated_at) 44 | }; 45 | Object.keys(replacements).forEach(function (key) { 46 | template = template.replace('(' + key + ')', replacements[key]); 47 | }); 48 | return template; 49 | }); 50 | }, { /* apiKeyRequired: true, */ success: { contentType: 'image/svg+xml'}}); 51 | -------------------------------------------------------------------------------- /github-repo-labels/idraw/large.idraw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiajs/example-projects/32d07bea4a59023557ddef88894425c36e340ab2/github-repo-labels/idraw/large.idraw -------------------------------------------------------------------------------- /github-repo-labels/idraw/small.idraw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiajs/example-projects/32d07bea4a59023557ddef88894425c36e340ab2/github-repo-labels/idraw/small.idraw -------------------------------------------------------------------------------- /github-repo-labels/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "repolabels", 3 | "version": "1.0.0", 4 | "description": "generate nice SVG labels for github repositories", 5 | "scripts": { 6 | "start": "claudia create --name github-repo-labels --region us-east-1 --version dev --api-module web", 7 | "deploy": "claudia update --version dev", 8 | "release": "claudia set-version --version production" 9 | }, 10 | "author": "Gojko Adzic ", 11 | "license": "MIT", 12 | "dependencies": { 13 | "claudia-api-builder": "^4", 14 | "fs-promise": "^1.0.0", 15 | "got": "^6.6.3", 16 | "numeral": "^2.0.2", 17 | "time-ago": "^0.1.0" 18 | }, 19 | "devDependencies": { 20 | "claudia": "^4" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /graphql-example/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "crockford", 3 | "env": { 4 | "node": true, 5 | "es6": true 6 | }, 7 | "parserOptions": { 8 | "ecmaVersion": 6, 9 | "sourceType": "nodejs" 10 | }, 11 | "rules": { 12 | "semi": ["error", "always"], 13 | "strict": ["error", "global"], 14 | "no-unused-vars": "error", 15 | "indent": ["error", "tab" ], 16 | "no-const-assign": "error", 17 | "one-var": "error", 18 | "prefer-const": "error", 19 | "no-var": "error", 20 | "prefer-arrow-callback": "error", 21 | "no-plusplus": ["error", { "allowForLoopAfterthoughts": true }], 22 | "quotes": ["error", "single", {"avoidEscape": true, "allowTemplateLiterals": true}] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /graphql-example/.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | -------------------------------------------------------------------------------- /graphql-example/GraphiQL_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiajs/example-projects/32d07bea4a59023557ddef88894425c36e340ab2/graphql-example/GraphiQL_app.png -------------------------------------------------------------------------------- /graphql-example/README.md: -------------------------------------------------------------------------------- 1 | # GraphQL Endpoint and DynamoDB 2 | 3 | Inspired by Kevin Old's [project](http://kevinold.com/2016/02/01/serverless-graphql.html), 4 | this is a sample implementation 5 | a [GraphQL](http://graphql.org/) service on AWS Lambda using Claudia. The service offers CRUD operations via just 1 endpoint. The data persistence is done via 6 | DynamoDB, similar to the [dynamodb-example](https://github.com/claudiajs/example-projects/tree/master/dynamodb-example). 7 | 8 | ## Prerequisites 9 | 10 | Create a table in DynamoDB, with a `string` primary key called `userid`. You can do that from the DynamoDB web console, or using the AWS CLI command line. Here is an example command that will create the table with the minimal provisioned throughput: 11 | 12 | ```bash 13 | aws dynamodb create-table --table-name claudia-graphql-example \ 14 | --attribute-definitions AttributeName=userid,AttributeType=S \ 15 | --key-schema AttributeName=userid,KeyType=HASH \ 16 | --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1 \ 17 | --query TableDescription.TableArn --output text 18 | ``` 19 | 20 | This example project includes an IAM access policy that will grant the lambda function access to all your DynamoDB tables, to make it easier to get started. If you wish, you can edit the [policies/access-dynamodb.json](policies/access-dynamodb.json) file and restrict the access to your new table only. 21 | 22 | ## Get started 23 | 24 | To set this up, first [set up the credentials](https://github.com/claudiajs/claudia/blob/master/getting_started.md#configuring-access-credentials), then: 25 | 26 | 1. run `npm install` to grab the dependencies 27 | 2. run `npm run create` to create the lambda project under the default name on AWS. 28 | 3. run `./test/run.sh` to execute cURL scripts to test the CRUD operations against the lambda endpoint. 29 | 30 | For subsequent updates, use the `npm run deploy` command. 31 | 32 | ## The API 33 | 34 | With GraphQL, there is only 1 endpoint `/latest/graphql` for all CRUD operations. User construct a GraphQL query string based on the schema definition defined in `src/schema.js`, and post the string to the endpoint. 35 | 36 | 37 | ### Create and Update 38 | Post `application/graphql` that looks like this: 39 | 40 | ``` 41 | mutation { 42 | addUser (userid:"2", name:"John Doe", age:29) { 43 | userid 44 | name 45 | age 46 | } 47 | } 48 | ``` 49 | 50 | ### Read 51 | Post `application/graphql` that looks like this: 52 | 53 | ``` 54 | user (userid:"4") { 55 | userid 56 | name 57 | age 58 | } 59 | ``` 60 | 61 | ### DELETE 62 | Post `application/graphql` that looks like this: 63 | 64 | ``` 65 | mutation { 66 | deleteUser (userid:"4") { 67 | userid 68 | name 69 | age 70 | } 71 | } 72 | 73 | ``` 74 | 75 | ## Testing 76 | 77 | ### Via cURL 78 | Run `./test/run.sh` to launch the cURL scripts that perform the various operations. 79 | 80 | ### Via GraphiQL 81 | GraphiQL is an IDE that help user edit and test queries and discover the schema. You can download a GraphiQL app at https://github.com/skevy/graphiql-app 82 | 83 | ![GraphiQL App](./GraphiQL_app.png "GraphiQL App") 84 | 85 | -------------------------------------------------------------------------------- /graphql-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-example", 3 | "version": "2.0.0", 4 | "description": "An example on using a GraphQL endpoint to store and get data to/from DynamoDB", 5 | "scripts": { 6 | "create": "claudia create --name claudia-graphql-example --region us-east-1 --api-module src/index --policies policies", 7 | "deploy": "claudia update" 8 | }, 9 | "keywords": [ 10 | "claudia", 11 | "aws-lambda", 12 | "DynamoDB", 13 | "GraphQL" 14 | ], 15 | "author": "Fancis Au-Yeung @aidanbon", 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/claudiajs/example-projects.git" 19 | }, 20 | "dependencies": { 21 | "aws-sdk": "^2.7.27", 22 | "claudia-api-builder": "^4", 23 | "graphql": "0.4.18" 24 | }, 25 | "devDependencies": { 26 | "claudia": "^4" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /graphql-example/policies/access-dynamodb.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Action": [ 6 | "dynamodb:DeleteItem", 7 | "dynamodb:GetItem", 8 | "dynamodb:Scan", 9 | "dynamodb:PutItem" 10 | ], 11 | "Effect": "Allow", 12 | "Resource": "*" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /graphql-example/src/dataStore.js: -------------------------------------------------------------------------------- 1 | /* 2 | * dataStore.js 3 | * Data persistency / CRUD layer 4 | */ 5 | 'use strict'; 6 | const AWS = require('aws-sdk'), 7 | docClient = new AWS.DynamoDB.DocumentClient(), 8 | getTableName = function () { 9 | return 'claudia-graphql-example'; 10 | }, 11 | addUser = function (user) { 12 | const params = { 13 | TableName: getTableName(), 14 | Item: { 15 | userid: user.userid, 16 | name: user.name, 17 | age: user.age 18 | } 19 | }; 20 | return docClient.put(params).promise().then(() => user); 21 | }, 22 | getUser = function (userid) { 23 | const params = { 24 | TableName: getTableName(), 25 | AttributesToGet: ['userid', 'name', 'age'] 26 | }; 27 | if (userid) { 28 | //search by userid 29 | params.Key = {userid: userid}; 30 | return docClient.get(params).promise() 31 | .then(data => [data.Item]); 32 | } else { 33 | //get all users 34 | return docClient.scan(params).promise() 35 | .then(data => data.Items); 36 | } 37 | }, 38 | deleteUser = function (userid) { 39 | let toBeDeletedUser; 40 | 41 | return getUser(userid) 42 | .then(resultArr => { 43 | toBeDeletedUser = resultArr[0]; 44 | if (!toBeDeletedUser || !toBeDeletedUser.userid) { 45 | throw `Delete user failed: no user with userid ${userid}`; 46 | } 47 | }).then(() => { 48 | const params = { 49 | TableName: getTableName(), 50 | Key: { 51 | userid: userid 52 | } 53 | }; 54 | return docClient.delete(params).promise(); 55 | }).then(() => toBeDeletedUser); 56 | }; //deleteUser 57 | 58 | module.exports = { addUser: addUser, getUser: getUser, deleteUser: deleteUser }; 59 | -------------------------------------------------------------------------------- /graphql-example/src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * index.js 3 | * API handler 4 | */ 5 | 'use strict'; 6 | const ApiBuilder = require('claudia-api-builder'), 7 | Graphql = require('graphql'), 8 | schema = require('./schema'), 9 | api = new ApiBuilder(); 10 | 11 | api.post('/graphql', request => { 12 | // request.body is the GraphQL query string. It must exist 13 | if (typeof request.body !== 'string') { 14 | return 'POST body must be a string'; 15 | } 16 | return Graphql.graphql(schema, request.body); 17 | }); 18 | 19 | module.exports = api; 20 | -------------------------------------------------------------------------------- /graphql-example/src/schema.js: -------------------------------------------------------------------------------- 1 | /* 2 | * schema.js 3 | * GraphQL schema definition 4 | */ 5 | 'use strict'; 6 | const dataStore = require('./dataStore'), 7 | Graphql = require('graphql'), 8 | userType = new Graphql.GraphQLObjectType({ 9 | name: 'User', 10 | fields: { 11 | userid: {type: new Graphql.GraphQLNonNull(Graphql.GraphQLString)}, 12 | name: {type: new Graphql.GraphQLNonNull(Graphql.GraphQLString)}, 13 | age: {type: Graphql.GraphQLInt} 14 | } 15 | }), 16 | queryType = new Graphql.GraphQLObjectType({ 17 | name: 'Query', 18 | description: 'query users', 19 | fields: { 20 | user: { 21 | type: new Graphql.GraphQLList(userType), 22 | args: { 23 | userid: {type: Graphql.GraphQLString} 24 | }, 25 | resolve: (source, args) => dataStore.getUser(args.userid) 26 | } 27 | } 28 | }), 29 | mutationType = new Graphql.GraphQLObjectType({ 30 | name: 'Mutation', 31 | description: 'Mutation of the users', 32 | fields: { 33 | addUser: { 34 | type: userType, 35 | args: { 36 | userid: {type: new Graphql.GraphQLNonNull(Graphql.GraphQLString)}, 37 | name: {type: new Graphql.GraphQLNonNull(Graphql.GraphQLString)}, 38 | age: {type: Graphql.GraphQLInt} 39 | }, 40 | resolve: (source, args) => dataStore.addUser(args) 41 | }, 42 | deleteUser: { 43 | type: userType, 44 | args: { 45 | userid: {type: new Graphql.GraphQLNonNull(Graphql.GraphQLString)} 46 | }, 47 | resolve: (source, args) => dataStore.deleteUser(args.userid) 48 | } 49 | } 50 | }); 51 | 52 | module.exports = new Graphql.GraphQLSchema({ 53 | query: queryType, 54 | mutation: mutationType 55 | }); 56 | -------------------------------------------------------------------------------- /graphql-example/test/add-user.sh: -------------------------------------------------------------------------------- 1 | # 2 | # add-user.sh 3 | # Add 2 users 4 | # 5 | if [ ! -n "$API_ID" ]; then 6 | echo "Missing API_ID." 7 | exit 1 8 | fi 9 | 10 | curl -H 'Content-Type: application/graphql' -X POST \ 11 | 'https://'${API_ID}'.execute-api.us-east-1.amazonaws.com/latest/graphql' \ 12 | -d 'mutation { 13 | addUser (userid:"4", name:"Mary Lamb", age:25) { 14 | userid name age 15 | } 16 | }' 17 | 18 | echo 19 | 20 | curl -H 'Content-Type: application/graphql' -X POST \ 21 | 'https://'${API_ID}'.execute-api.us-east-1.amazonaws.com/latest/graphql' \ 22 | -d 'mutation { 23 | addUser (userid:"2", name:"John Doe", age:29) { 24 | userid name age 25 | } 26 | }' 27 | -------------------------------------------------------------------------------- /graphql-example/test/all-users.sh: -------------------------------------------------------------------------------- 1 | # 2 | # all-users.sh 3 | # 4 | if [ ! -n "$API_ID" ]; then 5 | echo "Missing API_ID." 6 | exit 1 7 | fi 8 | 9 | curl -H 'Content-Type: application/graphql' -X POST \ 10 | -d '{user {userid name age}}' \ 11 | 'https://'${API_ID}'.execute-api.us-east-1.amazonaws.com/latest/graphql' 12 | -------------------------------------------------------------------------------- /graphql-example/test/del-user.sh: -------------------------------------------------------------------------------- 1 | # 2 | # del_user.sh 3 | # 4 | if [ ! -n "$API_ID" ]; then 5 | echo "Missing API_ID." 6 | exit 1 7 | fi 8 | 9 | curl -H 'Content-Type: application/graphql' -X POST \ 10 | 'https://'${API_ID}'.execute-api.us-east-1.amazonaws.com/latest/graphql' \ 11 | -d 'mutation { 12 | deleteUser (userid:"4") { 13 | userid name age 14 | } 15 | }' 16 | -------------------------------------------------------------------------------- /graphql-example/test/get-a-user.sh: -------------------------------------------------------------------------------- 1 | # 2 | # get-a-user.sh 3 | # 4 | if [ ! -n "$API_ID" ]; then 5 | echo "Missing API_ID." 6 | exit 1 7 | fi 8 | 9 | curl -H 'Content-Type: application/graphql' -X POST \ 10 | 'https://'${API_ID}'.execute-api.us-east-1.amazonaws.com/latest/graphql' \ 11 | -d '{ 12 | user (userid:"4") { 13 | userid 14 | name 15 | age 16 | } 17 | }' 18 | -------------------------------------------------------------------------------- /graphql-example/test/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # all.sh 4 | # Run all GraphQL GRUD tests. 5 | # 6 | cd $(dirname $0) 7 | 8 | API_ID=$(node -e 'console.log(require("../claudia.json").api.id)') 9 | export API_ID 10 | 11 | echo; echo "-------- show all users" 12 | ./all-users.sh 13 | 14 | echo; echo "-------- add 2 users" 15 | ./add-user.sh 16 | 17 | echo; echo "-------- show all users" 18 | ./all-users.sh 19 | 20 | echo; echo "-------- get a user with userid 4" 21 | ./get-a-user.sh 22 | 23 | echo; echo "-------- delete a user with userid 4 " 24 | ./del-user.sh 25 | 26 | echo; echo "-------- show all users" 27 | ./all-users.sh 28 | 29 | echo; echo 30 | -------------------------------------------------------------------------------- /hello-world/README.md: -------------------------------------------------------------------------------- 1 | A trivial Node.js microservice example, that just returns "Hello World" when called. To try it out, first [set up the credentials](https://github.com/claudiajs/claudia/blob/master/getting_started.md#configuring-access-credentials), then: 2 | 3 | 1. run `npm install` to grab the dependencies 4 | 2. run `npm start` to set up the lambda project under the default name on AWS 5 | 3. run `npm test` to execute the lambda from the command line and print the output on the console 6 | 7 | Check out the [package.json](package.json) scripts section to see how Claudia gets invoked for the `start` and `test` scripts. Check out the [main.js](main.js) file to see how the Lambda function works. For a bit more fun, modify the message in [main.js](main.js), then run `npm run deploy` to deploy the code to AWS, then run the test again to see the change. 8 | -------------------------------------------------------------------------------- /hello-world/main.js: -------------------------------------------------------------------------------- 1 | /*global exports, console*/ 2 | exports.handler = function (event, context) { 3 | 'use strict'; 4 | console.log(event); 5 | context.succeed('hello world'); 6 | }; 7 | -------------------------------------------------------------------------------- /hello-world/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world", 3 | "description": "ClaudiaJS hello-world example", 4 | "version": "1.0.0", 5 | "private": true, 6 | "files": [ 7 | "main.js" 8 | ], 9 | "scripts": { 10 | "start": "claudia create --name hello-world --region us-east-1 --handler main.handler", 11 | "test": "claudia test-lambda", 12 | "deploy": "claudia update" 13 | }, 14 | "devDependencies": { 15 | "claudia": "^4" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /intercepting-requests/README.md: -------------------------------------------------------------------------------- 1 | # Intercepting Web requests 2 | 3 | Claudia API Builder allows you to intercept web API requests, filter and change them before routing. This example demonstrates how to set up a simple intercept 4 | that kills all requests without a query string name parameter. 5 | 6 | ## To try it out 7 | 8 | * run `npm start` to install the app 9 | * try the API `/hello` URL in your browser, it should report an error 10 | * add `?name=Tom` to the URL and try again, you should see a successful greeting. 11 | 12 | ## How it works 13 | 14 | The `intercept` method gets called before the routing, and can prevent the requests from being executed by throwing an exception, or returning a promise that later rejects. See [web.js](web.js) for how it's set up in this case, and check out [Intercepting Requests](https://github.com/claudiajs/claudia-api-builder/blob/master/docs/api.md#intercepting-requests) API docs for more information 15 | -------------------------------------------------------------------------------- /intercepting-requests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "intercepting-requests", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "start": "claudia create --region us-east-1 --api-module web", 6 | "update": "claudia update" 7 | }, 8 | "author": "", 9 | "license": "MIT", 10 | "dependencies": { 11 | "claudia-api-builder": "^4" 12 | }, 13 | "devDependencies": { 14 | "claudia": "^4" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /intercepting-requests/web.js: -------------------------------------------------------------------------------- 1 | /*global require, module */ 2 | var ApiBuilder = require('claudia-api-builder'), 3 | api = new ApiBuilder(); 4 | 5 | module.exports = api; 6 | 7 | api.get('/hello', function (request) { 8 | 'use strict'; 9 | return 'Hello ' + request.queryString.name; 10 | }); 11 | 12 | api.get('/ping', function (request) { 13 | 'use strict'; 14 | return 'Pinging ' + request.queryString.name; 15 | }); 16 | 17 | api.intercept(function (request) { 18 | 'use strict'; 19 | if (!request.queryString.name) { 20 | throw 'You must provide a name'; 21 | } else { 22 | return request; 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /iot-topic-filter/README.md: -------------------------------------------------------------------------------- 1 | # IOT Topic Filter Example 2 | 3 | This is a trivial example that shows how to trigger Lambda functions when a message is posted to [AWS IOT Topics](http://docs.aws.amazon.com/iot/latest/developerguide/topics.html). The lambda function just logs messages, so you can post a message to the topic and then check CloudWatch logs to see the execution result. 4 | 5 | ## Try it out 6 | 7 | 1. grab the dependencies: `npm install` 8 | 2. create the lambda function: `npm run create` 9 | 3. set up a IOT topic filter: `npm run subscribe` 10 | 4. publish an event to the topic: `node post.js` 11 | 5. list cloudwatch logs associated with your function: `npm run check-logs` 12 | 13 | ## More information 14 | 15 | * [claudia add-iot-topic-rule options](https://github.com/claudiajs/claudia/blob/master/docs/add-iot-topic-rule.md) 16 | * [IOT SQL Reference](http://docs.aws.amazon.com/iot/latest/developerguide/iot-sql-reference.html) 17 | * [AWS IOT Topics](http://docs.aws.amazon.com/iot/latest/developerguide/topics.html) 18 | -------------------------------------------------------------------------------- /iot-topic-filter/lambda.js: -------------------------------------------------------------------------------- 1 | exports.handler = function (event, context, callback) { 2 | 'use strict'; 3 | console.log('got event', event); 4 | callback(null, 'OK'); 5 | }; 6 | -------------------------------------------------------------------------------- /iot-topic-filter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "claudia-iot-topic-filter", 3 | "version": "1.0.0", 4 | "private": "true", 5 | "description": "an example for triggering Lambda functions using IOT topic filters", 6 | "main": "lambda.js", 7 | "scripts": { 8 | "create": "claudia create --region us-east-1 --handler lambda.handler", 9 | "update": "claudia update", 10 | "subscribe": "claudia add-iot-topic-rule --sql \"select * from 'test/+'\"", 11 | "check-logs": "aws logs filter-log-events --log-group /aws/lambda/$npm_package_name --filter 'got event'" 12 | }, 13 | "keywords": [], 14 | "author": "Gojko Adzic ", 15 | "license": "MIT", 16 | "devDependencies": { 17 | "claudia": "^4" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /iot-topic-filter/post.js: -------------------------------------------------------------------------------- 1 | /*eslint "strict": ["global"] */ 2 | 'use strict'; 3 | const aws = require('aws-sdk'), 4 | region = 'us-east-1', 5 | iot = new aws.Iot({region: region}), 6 | postToEndpoint = function (endpoint, topic, message) { 7 | console.log('posting to', endpoint, topic, message); 8 | const iotdata = new aws.IotData({region: region, endpoint: endpoint}); 9 | return iotdata.publish({ topic: topic, payload: message }).promise(); 10 | }, 11 | postToDefaultEndpoint = function (topic, message) { 12 | return iot.describeEndpoint().promise() 13 | .then(data => postToEndpoint(data.endpointAddress, topic, message)); 14 | }; 15 | 16 | postToDefaultEndpoint('test/999', JSON.stringify({message: 'from the script'})) 17 | .then(() => console.log('posted successfully')) 18 | .catch(e => console.log('error posting', e)); 19 | 20 | 21 | -------------------------------------------------------------------------------- /pandoc-s3-converter/README.md: -------------------------------------------------------------------------------- 1 | # Pandoc S3 Lambda converter 2 | 3 | Lambda function that waits for files uploaded to S3, converts them to docx using the [Pandoc Lambda Layer](https://github.com/effortless-serverless/pandoc-aws-lambda-binary) and uploads back to S3. 4 | 5 | This example shows how to wire up S3 file conversion that runs an external processor, in this case [Pandoc](https://pandoc.org), a Swiss army knife for document conversion. Check out the [Running Pandoc on Lambda Guide](https://claudiajs.com/tutorials/pandoc-lambda.html) for more information. 6 | 7 | ## Prerequisites 8 | 9 | * create a S3 bucket for file uploads 10 | 11 | ## Setting up the converter 12 | 13 | 1. run `npm install` to fetch the dependencies 14 | 2. run `npm start` to deploy the initial lambda version 15 | 3. modify the [package.json](package.json) `connect` script to use your bucket name 16 | 4. run `npm run connect` to configure your S3 bucket to invoke the lambda function when a new file is uploaded to the `/in` directory 17 | 18 | ## Try it out 19 | 20 | Once you've installed everything, send a test file, for example a markdown file, to your bucket using the S3 console, or the AWS CLI tools. The command lines below assume the bucket is called `pandoc-test-bucket`, so adjust the commands for your bucket name accordingly. 21 | 22 | ```bash 23 | aws s3 cp example.md s3://pandoc-test-bucket/in/example.md 24 | ``` 25 | 26 | Wait a few seconds, and then check if the `/out` folder of your S3 bucket 27 | 28 | ```bash 29 | aws s3 ls s3://pandoc-test-bucket/out/ 30 | ``` 31 | 32 | Download the file with the same base name, but the `docx` extension, from the `/out` folder: 33 | 34 | ```bash 35 | aws s3 cp s3://pandoc-test-bucket/out/example.docx . 36 | ``` 37 | 38 | -------------------------------------------------------------------------------- /pandoc-s3-converter/child-process-promise.js: -------------------------------------------------------------------------------- 1 | /*global module, require, console, Promise */ 2 | var childProcess = require('child_process'), 3 | execPromise = function (command) { 4 | 'use strict'; 5 | return new Promise(function (resolve, reject) { 6 | childProcess.exec(command, function (err) { 7 | if (err) { 8 | reject(err); 9 | } else { 10 | resolve(); 11 | } 12 | }); 13 | }); 14 | }, 15 | spawnPromise = function (command, options) { 16 | 'use strict'; 17 | return new Promise(function (resolve, reject) { 18 | var process = childProcess.spawn(command, options); 19 | process.stdout.on('data', function (buffer) { 20 | console.log(buffer.toString()); 21 | }); 22 | process.stderr.on('data', function (buffer) { 23 | console.error(buffer.toString()); 24 | }); 25 | process.on('close', function (code) { 26 | if (code !== 0) { 27 | reject(code); 28 | } else { 29 | resolve(); 30 | } 31 | }); 32 | }); 33 | }; 34 | module.exports = { 35 | exec: execPromise, 36 | spawn: spawnPromise 37 | }; 38 | -------------------------------------------------------------------------------- /pandoc-s3-converter/convert.js: -------------------------------------------------------------------------------- 1 | /*global require, module, console */ 2 | var path = require('path'), 3 | fs = require('fs'), 4 | os = require('os'), 5 | uuid = require('uuid'), 6 | pandocBinaryPath = '/opt/bin/pandoc', 7 | cpPromise = require('./child-process-promise'), 8 | s3 = require('./s3-util'); 9 | 10 | module.exports = function convert(bucket, fileKey) { 11 | 'use strict'; 12 | var targetPath, sourcePath; 13 | console.log('converting', bucket, fileKey); 14 | return s3.download(bucket, fileKey).then(function (downloadedPath) { 15 | sourcePath = downloadedPath; 16 | targetPath = path.join(os.tmpdir(), uuid.v4() + '.docx'); 17 | return cpPromise.spawn(pandocBinaryPath, [sourcePath, '-o', targetPath]); 18 | }).then(function () { 19 | var uploadKey = fileKey.replace(/^in/, 'out').replace(/\.[A-z0-9]+$/, '.docx'); 20 | return s3.upload(bucket, uploadKey, targetPath); 21 | }).then(function () { 22 | console.log('deleting', targetPath, sourcePath); 23 | fs.unlinkSync(targetPath); 24 | fs.unlinkSync(sourcePath); 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /pandoc-s3-converter/main.js: -------------------------------------------------------------------------------- 1 | /*global exports, require*/ 2 | var convert = require('./convert'); 3 | exports.handler = function (event, context) { 4 | 'use strict'; 5 | var eventRecord = event.Records && event.Records[0]; 6 | if (!eventRecord) { 7 | return context.fail('no records in the event'); 8 | } 9 | if (eventRecord.eventSource !== 'aws:s3' || !eventRecord.s3) { 10 | context.fail('unsupported event source'); 11 | } 12 | convert(eventRecord.s3.bucket.name, eventRecord.s3.object.key).then(context.done, context.fail); 13 | }; 14 | -------------------------------------------------------------------------------- /pandoc-s3-converter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pandoc-s3-converter", 3 | "version": "1.0.0", 4 | "description": "Lambda function that waits for files uploaded to S3, converts them to docx using Pandoc and uploads back to S3", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "claudia create --region us-east-1 --handler main.handler --layers arn:aws:lambda:us-east-1:145266761615:layer:pandoc:1 --timeout 60 --memory 512", 8 | "connect": "claudia add-s3-event-source --bucket pandoc-test-bucket --prefix in", 9 | "update": "claudia update" 10 | }, 11 | "author": "Gojko Adzic ", 12 | "license": "MIT", 13 | "dependencies": { 14 | "uuid": "^2.0.2" 15 | }, 16 | "optionalDependencies": { 17 | "aws-sdk": "^2.6.2" 18 | }, 19 | "devDependencies": { 20 | "claudia": "^5.3.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /pandoc-s3-converter/s3-util.js: -------------------------------------------------------------------------------- 1 | /*global module, require, Promise, console */ 2 | 3 | var aws = require('aws-sdk'), 4 | path = require('path'), 5 | fs = require('fs'), 6 | os = require('os'), 7 | uuid = require('uuid'), 8 | s3 = new aws.S3(), 9 | downloadFromS3 = function (bucket, fileKey) { 10 | 'use strict'; 11 | console.log('downloading', bucket, fileKey); 12 | return new Promise(function (resolve, reject) { 13 | var filePath = path.join(os.tmpdir(), uuid.v4() + path.extname(fileKey)), 14 | file = fs.createWriteStream(filePath), 15 | stream = s3.getObject({ 16 | Bucket: bucket, 17 | Key: fileKey 18 | }).createReadStream(); 19 | 20 | stream.setEncoding('utf8'); 21 | 22 | stream.on('error', reject); 23 | file.on('error', reject); 24 | file.on('finish', function () { 25 | console.log('downloaded', bucket, fileKey); 26 | resolve(filePath); 27 | }); 28 | stream.pipe(file); 29 | }); 30 | }, uploadToS3 = function (bucket, fileKey, filePath, acl) { 31 | 'use strict'; 32 | console.log('uploading', bucket, fileKey, filePath, acl); 33 | return s3.upload({ 34 | Bucket: bucket, 35 | Key: fileKey, 36 | Body: fs.createReadStream(filePath), 37 | ACL: acl || 'private' 38 | }).promise(); 39 | }; 40 | 41 | module.exports = { 42 | download: downloadFromS3, 43 | upload: uploadToS3 44 | }; 45 | -------------------------------------------------------------------------------- /recursive-invoke/README.md: -------------------------------------------------------------------------------- 1 | # Recursive invocation example 2 | 3 | This example demonstrates how a lambda function can recursively invoke itself, to work around timing limitations. 4 | 5 | ## Running the example 6 | 7 | * call `npm install` to grab the dependencies 8 | * call `npm start` to set up the function 9 | * call `npm test` to send an initial test event, which will cause several recursive calls 10 | * Check the execution logs using CloudWatch: 11 | 12 | ```bash 13 | aws logs filter-log-events --log-group /aws/lambda/recursive-invoke | grep received 14 | ``` 15 | 16 | You should see several calls, with the counter decrementing from 5 to 0. 17 | 18 | ## How it works 19 | 20 | The key trick is to call `claudia create` with `--allow-recursion` (check the [package.json](package.json) `start` script). This will set up IAM permissions so the Lambda function is allowed to execute itself. 21 | 22 | The actual recursive call is on line 10 of [index.js](index.js). The `Qualifier` setting ensures that the same version of the function gets invoked, so you can safely run multiple versions of the function (eg for development, testing and production). 23 | 24 | -------------------------------------------------------------------------------- /recursive-invoke/counter.json: -------------------------------------------------------------------------------- 1 | { 2 | "calls": 5, 3 | "name": "Tom" 4 | } 5 | -------------------------------------------------------------------------------- /recursive-invoke/index.js: -------------------------------------------------------------------------------- 1 | /*global require, module, console */ 2 | var aws = require('aws-sdk'); 3 | module.exports.handler = function (event, context) { 4 | 'use strict'; 5 | var lambda = new aws.Lambda(); 6 | console.log('received', event); 7 | if (event.calls > 0) { 8 | console.log('calling recursive'); 9 | event.calls = event.calls - 1; 10 | lambda.invoke({ 11 | FunctionName: context.functionName, 12 | InvocationType: 'Event', 13 | Payload: JSON.stringify(event), 14 | Qualifier: context.functionVersion 15 | }, context.done); 16 | } else { 17 | context.succeed('calls: ' + (event.calls) + ' (version ' + context.functionVersion + ')'); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /recursive-invoke/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "recursive-invoke", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "claudia create --region us-east-1 --handler index.handler --allow-recursion --version dev", 8 | "test": "claudia test-lambda --event counter.json --version dev" 9 | }, 10 | "author": "", 11 | "license": "MIT", 12 | "dependencies": { 13 | "aws-sdk": "^2.4.6" 14 | }, 15 | "devDependencies": { 16 | "claudia": "^4" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /recursive-invoke/policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "InvokePermission", 6 | "Effect": "Allow", 7 | "Action": [ 8 | "lambda:InvokeFunction" 9 | ], 10 | "Resource": "arn:aws:lambda:us-east-1:818931230230:function:recursive-invoke" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /s3-file-processing/README.md: -------------------------------------------------------------------------------- 1 | A simple example project that demonstrates acting on S3 events and using Claudia to automatically wire up events and assign related privileges for S3 access. This service will uppercase text files. 2 | 3 | To try it out, first create a bucket on S3 where you'll upload the files (no special privileges needed), then: 4 | 5 | 1. install the dependencies using `npm install` 6 | 2. create the function using `npm start` 7 | 3. run `claudia add-s3-event-source --bucket BUCKET_NAME --prefix in/` to connect bucket events to the lambda function using (replace the `BUCKET_NAME` with your bucket name) 8 | 4. upload a text file to your bucket into the /in folder 9 | 5. check for the file in the /out folder on s3, should be uppercased 10 | 11 | Check out [package.json](package.json) to see how the Lambda is created using the `start` script, and [main.js](main.js) to see how the S3 event gets received by the Lambda function. The actual file conversion is in [convert.js](convert.js), where it's just using Node streams to transform the incoming bucket file into the outgoing bucket file. The Lambda context callback conveniently works well when directly passed to the S3 upload function. 12 | -------------------------------------------------------------------------------- /s3-file-processing/convert.js: -------------------------------------------------------------------------------- 1 | /*global require, module */ 2 | var aws = require('aws-sdk'); 3 | module.exports = function convert(bucket, fileKey, callback) { 4 | 'use strict'; 5 | var s3 = new aws.S3(), 6 | Transform = require('stream').Transform, 7 | uppercase = new Transform({decodeStrings: false}), 8 | stream; 9 | uppercase._transform = function (chunk, encoding, done) { 10 | done(null, chunk.toUpperCase()); 11 | }; 12 | stream = s3.getObject({ 13 | Bucket: bucket, 14 | Key: fileKey 15 | }).createReadStream(); 16 | stream.setEncoding('utf8'); 17 | stream.pipe(uppercase); 18 | s3.upload({ 19 | Bucket: bucket, 20 | Key: fileKey.replace(/^in/, 'out'), 21 | Body: uppercase, 22 | ACL: 'private' 23 | }, callback); 24 | 25 | }; 26 | -------------------------------------------------------------------------------- /s3-file-processing/main.js: -------------------------------------------------------------------------------- 1 | /*global exports, console, require*/ 2 | var convert = require('./convert'); 3 | exports.handler = function (event, context) { 4 | 'use strict'; 5 | console.log('processing', JSON.stringify(event)); 6 | var eventRecord = event.Records && event.Records[0]; 7 | if (eventRecord) { 8 | if (eventRecord.eventSource === 'aws:s3' && eventRecord.s3) { 9 | convert(eventRecord.s3.bucket.name, eventRecord.s3.object.key, context.done); 10 | } else { 11 | context.fail('unsupported event source'); 12 | } 13 | } else { 14 | context.fail('no records in the event'); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /s3-file-processing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "s3-file-procesing", 3 | "description": "Simple example that shows how to act on S3 events", 4 | "version": "1.0.0", 5 | "private": true, 6 | "files": [ 7 | "*.js" 8 | ], 9 | "scripts": { 10 | "start": "claudia create --name s3-processing --region us-east-1 --handler main.handler", 11 | "test": "claudia test-lambda", 12 | "deploy": "claudia update" 13 | }, 14 | "devDependencies": { 15 | "claudia": "^4" 16 | }, 17 | "dependencies": { 18 | "aws-sdk": "^2.6.4" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /sam-packaging/.gitignore: -------------------------------------------------------------------------------- 1 | lambda.zip 2 | -------------------------------------------------------------------------------- /sam-packaging/README.md: -------------------------------------------------------------------------------- 1 | # SAM Packaging example 2 | 3 | This is a demo project to show how to use `claudia pack` to prepare a code package for the AWS Serverless Application Model and CloudFormation. 4 | 5 | SAM doesn't know about NPM. But Claudia does, and that's one of its best features. Until version 5, if you wanted to get the benefits from the Claudia packaging pipeline, you had to use Claudia to deploy as well. With version 5, we introduced a new command, `claudia pack`, that just creates a clean deployable ZIP with all the knowledge of NPM, so you can use it inside SAM, CloudFormation or with any other tool. `claudia pack` will use your NPM configuration to include only the required files, include only production dependencies, repackage all the local `file:` dependencies correctly, remove duplicated dependencies to reduce the package size, copy everything into `node_modules` without symbolic links and fix potential file permission problems in all the dependencies. You can even customise packaging by including a post-package script in your NPM project, and claudia will run it after the files are ready but before producing the final zip. Because development dependencies will be excluded automatically, it's safe to include claudia as a development dependency in your SAM projects directly, so you don't have to install it to your global modules. 6 | 7 | ## Prerequisites 8 | 9 | - AWS command line tools (aws-cli) installed to run CloudFormation scripts 10 | - S3 bucket for deployments 11 | 12 | ## Try it out 13 | 14 | ### Step 1: install the dependencies 15 | 16 | ``` 17 | npm i 18 | ``` 19 | 20 | ### Step 2: create the zip 21 | 22 | ``` 23 | npm run pack-zip 24 | ``` 25 | 26 | This will create a lambda.zip in your local directory, with all the files packaged for deployment, including production dependencies but excluding any development or optional dependencies. 27 | 28 | ### Step 3: 29 | 30 | Run CloudFormation to deploy the stack using SAM (replace the region, bucket name and stack name in the commands below according to your deployment needs): 31 | 32 | ``` 33 | aws cloudformation package --template-file template.yaml --output-template-file output.yaml --s3-bucket --region us-east-1 34 | 35 | aws cloudformation deploy --template-file output.yaml --stack-name samStack --region us-east-1 --capabilities CAPABILITY_IAM 36 | ``` 37 | 38 | ### Step 4: Get the URL of your new deployment: 39 | 40 | ``` 41 | aws cloudformation describe-stacks --region us-east-1 --query 'Stacks[?StackName==`samStack`].Outputs' --output table 42 | ``` 43 | 44 | ### Step 5: open the URL 45 | 46 | The URL should open in your browser directly, showing a simple HTML page served by [src/index.js](src/index.js) 47 | -------------------------------------------------------------------------------- /sam-packaging/output.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: A simple demonstration project for claudia packaging 3 | Outputs: 4 | ApiURL: 5 | Description: API endpoint URL for Prod environment 6 | Value: 7 | Fn::Sub: https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/ 8 | Resources: 9 | IndexFunction: 10 | Properties: 11 | CodeUri: s3://desole-packaging/76c57608c10b8f604dad0260752c399e 12 | Events: 13 | GetResource: 14 | Properties: 15 | Method: get 16 | Path: / 17 | Type: Api 18 | Handler: src/index.handler 19 | Runtime: nodejs8.10 20 | Type: AWS::Serverless::Function 21 | Transform: AWS::Serverless-2016-10-31 22 | -------------------------------------------------------------------------------- /sam-packaging/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sam-packaging", 3 | "version": "1.0.0", 4 | "description": "", 5 | "files": [ 6 | "src", 7 | "*.js" 8 | ], 9 | "scripts": { 10 | "pack-zip": "claudia pack --no-optional-dependencies --output lambda.zip --force" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "claudia": "^5" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sam-packaging/src/html-response.js: -------------------------------------------------------------------------------- 1 | module.exports = (body, statusCode) => { 2 | return { 3 | statusCode: (statusCode || 200), 4 | body: body, 5 | headers: { 6 | 'Content-Type': 'text/html' 7 | } 8 | }; 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /sam-packaging/src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const htmlResponse = require('./html-response'); 3 | 4 | exports.handler = (event, context) => { 5 | const body = ` 6 | 7 |

Hello From SAM

8 |

This ZIP was packaged using Claudia

9 | 10 | `; 11 | return Promise.resolve(htmlResponse(body)); 12 | }; 13 | -------------------------------------------------------------------------------- /sam-packaging/template.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: AWS::Serverless-2016-10-31 3 | Description: A simple demonstration project for claudia packaging 4 | Resources: 5 | IndexFunction: 6 | Type: AWS::Serverless::Function 7 | Properties: 8 | Handler: src/index.handler 9 | Runtime: nodejs8.10 10 | CodeUri: lambda.zip 11 | Events: 12 | GetResource: 13 | Type: Api 14 | Properties: 15 | Path: / 16 | Method: get 17 | Outputs: 18 | ApiURL: 19 | Description: "API endpoint URL for Prod environment" 20 | Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/" 21 | 22 | -------------------------------------------------------------------------------- /simple-bot/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Gojko Adzic 3 | 4 | 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: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | 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. 9 | -------------------------------------------------------------------------------- /simple-bot/README.md: -------------------------------------------------------------------------------- 1 | # Simple chat-bot example 2 | 3 | This chat-bot shows how to receive and respond with simple text messages, using the same code for all four supported platforms. 4 | 5 | ![](https://claudiajs.com/assets/supportbot-facebook.gif) 6 | ![](https://claudiajs.com/assets/supportbot-slack.gif) 7 | 8 | ## Try live 9 | 10 | Add to Slack 11 | 12 | * [Chat on Skype](https://join.skype.com/bot/08f53028-dd61-4769-907d-29bdce505f16) 13 | 14 | * [Chat on Telegram](https://telegram.me/claudia-test-bot) 15 | 16 | ## Setting up your own copy 17 | 18 | Use `npm run create` to set up the initial installation, then use: 19 | 20 | * `npm run configure-slack` to set up Slack slash command configuration. Follow the instructions from [Slack API Docs](https://api.slack.com/) to set up an app with a slash command, then [Create a Slack Button](https://api.slack.com/docs/slack-button) so people can add your app to their channels 21 | * `npm run configure-facebook` to set up the Facebook messenger integration. Follow the instructions from the [Facebook Messenger Getting Started](https://developers.facebook.com/docs/messenger-platform/quickstart) guide, then submit the app for [App Review](https://developers.facebook.com/docs/messenger-platform/app-review) so the others can interact with it 22 | * `npm run configure-skype` to set up Skype integration. Create an application and an application password at the [Microsoft App Portal](https://apps.dev.microsoft.com/Login?ru=https%3a%2f%2fapps.dev.microsoft.com%2f), and enter into Claudia when asked. Then create a bot at the [Skype Bot Developer Page](https://developer.microsoft.com/en-us/skype/bots/manage/Create), tick 'Send and receive messages and content in 1:1 chat', and enter the webhook URL printed by the Claudia installer 23 | * `npm run configure-telegram` to set up the Telegram integration 24 | 25 | ## Privacy 26 | 27 | This bot collects no private information 28 | 29 | ## License and Terms of Service 30 | 31 | [MIT](LICENSE) 32 | -------------------------------------------------------------------------------- /simple-bot/api.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const botBuilder = require('claudia-bot-builder'); 4 | const excuse = require('huh'); 5 | 6 | module.exports = botBuilder(request => 7 | `Thank you for sending ${request.text}. Your message is very important to us. The problem was caused by ${excuse.get()}` 8 | ); 9 | -------------------------------------------------------------------------------- /simple-bot/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-bot", 3 | "private": "true", 4 | "version": "1.0.0", 5 | "description": "Simple bot example for claudia bot builder", 6 | "scripts": { 7 | "create": "claudia create --region us-east-1 --api-module api", 8 | "configure-facebook": "claudia update --configure-fb-bot", 9 | "configure-skype": "claudia update --configure-skype-bot", 10 | "configure-telegram": "claudia update --configure-telegram-bot", 11 | "configure-slack": "claudia update --configure-slack-slash-command", 12 | "start": "ntl" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/stojanovic/bot-builder.git" 17 | }, 18 | "license": "MIT", 19 | "dependencies": { 20 | "claudia-bot-builder": "^4", 21 | "huh": "^1.1.4" 22 | }, 23 | "devDependencies": { 24 | "claudia": "^4", 25 | "ntl": "^1.1.2" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /simple-bot/slack-thanks.md: -------------------------------------------------------------------------------- 1 | # Thanks for installing the app 2 | 3 | Your app is now ready to use. 4 | 5 | ## Slack 6 | 7 | Start by typing `/why` and add a description of an IT problem. The helpful support bot will reply with a nice excuse. 8 | 9 | ## More info 10 | 11 | Check out the [Simple Bot Example Source Code](https://github.com/claudiajs/example-projects/tree/master/simple-bot) to see how this bot was built and deployed. 12 | -------------------------------------------------------------------------------- /slack-delayed-response/README.md: -------------------------------------------------------------------------------- 1 | # Slack slash commands with delayed responses 2 | 3 | This example project shows how to implement delayed responses to Slack slash commands. Slack limits the time to the first response to 3 seconds, so if you're trying to run a longer task, implementing everything in a single response won't work. 4 | 5 | Slack Slash commands support delayed and multiple responses, allowing a bot to respond to a command up to 5 times in 30 minutes. 6 | 7 | _Claudia Bot Builder_, since 1.4.0, offers a simple solution for delayed responses. This project demonstrates how to do that by implementing a simple alert timer. You can tell it how many seconds to wait, and it will ping you after that period. 8 | 9 | ## How to run it 10 | 11 | 1. Run `npm install` to grab all the dependencies 12 | 2. Run `npm run create` to set up a Lambda function, and follow the instructions to connect it to Slack. (Refer to [Setting up a Slack Slash Command](https://github.com/claudiajs/claudia-bot-builder/blob/master/docs/GETTING_STARTED.md#slack-app-slash-command-configuration) if you need more info) 13 | 3. In your Slack channel, Run `/delay 10` 14 | 15 | 16 | You'll see an immediate response that the timer is scheduled, and a delayed response 10 seconds later. 17 | 18 |
19 | 20 |
21 | 22 | ## How it works 23 | 24 | The code is in [bot.js](bot.js). 25 | 26 | Check out the [Delayed Responses to Slack Slash Commands](https://claudiajs.com/tutorials/slack-delayed-responses.html) Tutorial for a detailed explanation of how this code works. 27 | 28 | ## More information 29 | 30 | Check out the [Slack API Docs](https://api.slack.com/slash-commands#responding_to_a_command) for more information on delayed responses, and Claudia API Builder [API Docs](https://github.com/claudiajs/claudia-api-builder/blob/master/docs/api.md#intercepting-requests) for more information on intercepting requests. 31 | -------------------------------------------------------------------------------- /slack-delayed-response/bot.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const promiseDelay = require('promise-delay'); 4 | const aws = require('aws-sdk'); 5 | const lambda = new aws.Lambda(); 6 | const botBuilder = require('claudia-bot-builder'); 7 | const slackDelayedReply = botBuilder.slackDelayedReply; 8 | 9 | const api = botBuilder((message, apiRequest) => { 10 | const seconds = parseInt(message.text, 10); 11 | if (Number.isInteger(seconds) && seconds > 0 && seconds < 61) { 12 | 13 | // Invoke the same Lambda function asynchronously, and do not wait for the response 14 | // this allows the initial request to end within three seconds, as requiured by Slack 15 | 16 | return new Promise((resolve, reject) => { 17 | lambda.invoke({ 18 | FunctionName: apiRequest.lambdaContext.functionName, 19 | InvocationType: 'Event', 20 | Payload: JSON.stringify({ 21 | slackEvent: message // this will enable us to detect the event later and filter it 22 | }), 23 | Qualifier: apiRequest.lambdaContext.functionVersion 24 | }, (err, done) => { 25 | if (err) return reject(err); 26 | 27 | resolve(); 28 | }); 29 | }) 30 | .then(() => { 31 | return { // the initial response 32 | text: `Ok, I'll ping you in ${seconds}s.`, 33 | response_type: 'in_channel' 34 | } 35 | }) 36 | .catch(() => { 37 | return `Could not setup timer :(` 38 | }); 39 | } else { 40 | return `Number of seconds needs to be between 1 and 60` 41 | } 42 | }); 43 | 44 | // this will be executed before the normal routing. 45 | // we detect if the event has a flag set by line 21, 46 | // and if so, avoid normal procesing, running a delayed response instead 47 | 48 | api.intercept((event) => { 49 | if (!event.slackEvent) // if this is a normal web request, let it run 50 | return event; 51 | 52 | const message = event.slackEvent; 53 | const seconds = parseInt(message.text, 10); 54 | 55 | return promiseDelay(seconds * 1000) 56 | .then(() => { 57 | return slackDelayedReply(message, { 58 | text: `${seconds} seconds passed.`, 59 | response_type: 'in_channel' 60 | }) 61 | }) 62 | .then(() => false); // prevent normal execution 63 | }); 64 | 65 | module.exports = api; 66 | -------------------------------------------------------------------------------- /slack-delayed-response/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slack-delayed-response", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "ntl", 8 | "create": "claudia create --api-module bot --region us-east-1 --timeout 120 --allow-recursion --configure-slack-slash-command", 9 | "update": "claudia update" 10 | }, 11 | "author": "Slobodan Stojanovic (http://slobodan.me/)", 12 | "license": "MIT", 13 | "dependencies": { 14 | "aws-sdk": "^2.4.6", 15 | "claudia-bot-builder": "^4", 16 | "promise-delay": "^2.1.0" 17 | }, 18 | "devDependencies": { 19 | "claudia": "^4" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /slack-delayed-response/slack-delayed-response.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiajs/example-projects/32d07bea4a59023557ddef88894425c36e340ab2/slack-delayed-response/slack-delayed-response.gif -------------------------------------------------------------------------------- /stripe-checkout-payment/README.md: -------------------------------------------------------------------------------- 1 | # Stripe Checkout Payment Serverless API 2 | 3 | This simple example shows how to implement a Stripe Checkout to create charge serverless function with AWS Lambda, and charge your users credit cards. 4 | 5 | Stripe allows your application to create and execute payments. 6 | 7 | ## NOTE! 8 | You need to have proper a front-end implementation. A very basic, simple example implementation in vanilla JavaScript (no frameworks) has been provided. 9 | 10 | 11 | 12 | ## Prerequisites 13 | 14 | 1. Create a Stripe account 15 | 2. Set your AWS credentials locally 16 | 3. Replace with your Stripe secret key variable STRIPE_SECRET_KEY value in the `package.json` `create` script. 17 | 4. Setup a frontend site to send data in the format expected by the service (stripeToken, amount, currency) 18 | 19 | ## How to run it 20 | 21 | 1. Run `npm install` to grab all the dependencies 22 | 2. Run `npm run create` to set up a Lambda function with the `STRIPE_SECRET_KEY` environment variable. 23 | 2. Run `npm run update` to update your Lambda function if needed. 24 | 25 | 26 | That's it. 27 | 28 | ## How does it work 29 | 30 | The code is in the [index.js](index.js). 31 | 32 | The frontend part is in the [index](index.html). It's server-rendered by your Lambda. 33 | 34 | 1. Open your Lambda function endpoint in the browser, type in the amount you want to charge, and click pay. 35 | 2. Type in a test card (4111 1111 1111 1111, EXP: 11/19, CCV: 1111, ZIP: 111111) and your email and click pay. 36 | 3. The Stripe modal form makes a request to Stripe - Stripe verifies it, handles the data and returns to your frontend the Stripe Token (valid for a few minutes) to initiate a charge. 37 | 4. Your frontend packs the stripe ID along with the same amount (but in cents) and makes a request to your Lambda function endpoint `/create-payment`. 38 | 5. The endpoint receives the data, makes a Stripe charge, and returns either a response or an error (in case something is wrong). 39 | 6. That's it! 40 | 41 | 42 | ## More information 43 | 44 | To separate the frontend, in case you already have a frontend app: 45 | 1. Remove the `index.html` file from the project root, open it and add to the endpoint address your AWS Lambda URL. 46 | 2. Remove the `api.get` endpoint and handler completely. 47 | 48 | Check out the [Stripe Checkout Docs](https://stripe.com/docs/checkout) for more information on Stripe Checkout. 49 | -------------------------------------------------------------------------------- /stripe-checkout-payment/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Front-end part for the Stripe Checkout Payment with Claudia.js

6 | 7 |
8 | 9 |
10 | 11 | 12 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /stripe-checkout-payment/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const ApiBuilder = require('claudia-api-builder'); 5 | const api = new ApiBuilder(); 6 | const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); 7 | 8 | module.exports = api; 9 | 10 | api.get('/', () => { 11 | return new Promise((resolve, reject) => { 12 | fs.readFile('index.html', 'utf8', (err, file) => { 13 | if (err) throw err; 14 | resolve(file); 15 | }); 16 | }); 17 | }, { success: { contentType: 'text/html'}}); 18 | 19 | api.post('/create-payment', request => { 20 | return stripe.charges.create({ 21 | source: request.body.stripeToken, 22 | amount: request.body.amount, 23 | currency: request.body.currency, 24 | description: 'Stripe Charge Description' 25 | }).then(charge => { 26 | return { message: 'Payment Initiated!', charge: charge }; 27 | }).catch((err) => { 28 | return { message: 'Payment Initialization Error', error: err }; 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /stripe-checkout-payment/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stripe-checkout-payment", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "create": "claudia create --api-module index --region us-east-1 --set-env STRIPE_SECRET_KEY=Your-Stripe-Secret-Key", 8 | "update": "claudia update" 9 | }, 10 | "keywords": [], 11 | "author": "Aleksandar Simovic ", 12 | "license": "MIT", 13 | "dependencies": { 14 | "claudia-api-builder": "^4", 15 | "stripe": "^5.3.0" 16 | }, 17 | "devDependencies": { 18 | "claudia": "^4" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /svg-to-pdf-s3-converter/README.md: -------------------------------------------------------------------------------- 1 | # SVG to PDF S3 Lambda converter 2 | 3 | Lambda function that waits for SVG files uploaded to S3, converts them to PDF using rsvg-convert (librsvg and cairo) and uploads back to S3. 4 | 5 | This example shows how to wire up S3 file conversion that runs an external processor, in this case [rsvg-convert](http://live.gnome.org/LibRsvg). This example project uses a Lambda Layer (`arn:aws:lambda:us-east-1:145266761615:layer:rsvg-convert:2`) containing the [pre-built binary version of rsvg-convert for AWS Lambda](https://github.com/effortless-serverless/rsvg-convert-aws-lambda-binary). 6 | 7 | ## Prerequisites 8 | 9 | * create a S3 bucket for file uploads 10 | 11 | ## Setting up the converter 12 | 13 | 1. run `npm install` to fetch the dependencies 14 | 2. run `npm start` to deploy the initial lambda version 15 | 3. modify the [package.json](package.json) `connect` script to use your bucket name 16 | 4. run `npm run connect` to configure your S3 bucket to invoke the lambda function when a new file is uploaded to the `/in` directory 17 | 18 | ## Try it out 19 | 20 | Once you've installed everything, send a test SVG file to your bucket using the S3 console, or the AWS CLI tools. The command lines below assume the bucket is called `test-svg-bucket`, so adjust the commands for your bucket name accordingly. 21 | 22 | ```bash 23 | aws s3 cp example.svg s3://test-svg-bucket/in/example.svg 24 | ``` 25 | 26 | Wait a few seconds, and then check if the `/out` folder of your S3 bucket 27 | 28 | ```bash 29 | aws s3 ls s3://test-svg-bucket/out/ 30 | ``` 31 | 32 | Download the file with the same base name, but the `pdf` extension, from the `/out` folder: 33 | 34 | ```bash 35 | aws s3 cp s3://test-svg-bucket/out/example.pdf . 36 | ``` 37 | 38 | -------------------------------------------------------------------------------- /svg-to-pdf-s3-converter/child-process-promise.js: -------------------------------------------------------------------------------- 1 | /*global module, require, console, Promise */ 2 | var childProcess = require('child_process'), 3 | execPromise = function (command) { 4 | 'use strict'; 5 | return new Promise(function (resolve, reject) { 6 | childProcess.exec(command, function (err) { 7 | if (err) { 8 | reject(err); 9 | } else { 10 | resolve(); 11 | } 12 | }); 13 | }); 14 | }, 15 | spawnPromise = function (command, options) { 16 | 'use strict'; 17 | return new Promise(function (resolve, reject) { 18 | var process = childProcess.spawn(command, options); 19 | process.stdout.on('data', function (buffer) { 20 | console.log(buffer.toString()); 21 | }); 22 | process.stderr.on('data', function (buffer) { 23 | console.error(buffer.toString()); 24 | }); 25 | process.on('close', function (code) { 26 | if (code !== 0) { 27 | reject(code); 28 | } else { 29 | resolve(); 30 | } 31 | }); 32 | }); 33 | }; 34 | module.exports = { 35 | exec: execPromise, 36 | spawn: spawnPromise 37 | }; 38 | -------------------------------------------------------------------------------- /svg-to-pdf-s3-converter/convert.js: -------------------------------------------------------------------------------- 1 | /*global require, module, console, __dirname */ 2 | var path = require('path'), 3 | fs = require('fs'), 4 | os = require('os'), 5 | uuid = require('uuid'), 6 | rsvgBinaryPath = '/opt/bin/rsvg-convert', 7 | cpPromise = require('./child-process-promise'), 8 | s3 = require('./s3-util'); 9 | 10 | module.exports = function convert(bucket, fileKey) { 11 | 'use strict'; 12 | var targetPath, sourcePath; 13 | console.log('converting', bucket, fileKey); 14 | return s3.download(bucket, fileKey).then(function (downloadedPath) { 15 | sourcePath = downloadedPath; 16 | targetPath = path.join(os.tmpdir(), uuid.v4() + '.pdf'); 17 | return cpPromise.spawn(rsvgBinaryPath, [sourcePath, '-o', targetPath, '-f', 'pdf']); 18 | }).then(function () { 19 | var uploadKey = fileKey.replace(/^in/, 'out').replace(/\.[A-z0-9]+$/, '.pdf'); 20 | console.log('got to upload', targetPath, sourcePath); 21 | return s3.upload(bucket, uploadKey, targetPath); 22 | }).then(function () { 23 | console.log('deleting', targetPath, sourcePath); 24 | fs.unlinkSync(targetPath); 25 | fs.unlinkSync(sourcePath); 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /svg-to-pdf-s3-converter/main.js: -------------------------------------------------------------------------------- 1 | /*global exports, require*/ 2 | var convert = require('./convert'); 3 | exports.handler = function (event, context) { 4 | 'use strict'; 5 | var eventRecord = event.Records && event.Records[0]; 6 | if (!eventRecord) { 7 | return context.fail('no records in the event'); 8 | } 9 | if (eventRecord.eventSource !== 'aws:s3' || !eventRecord.s3) { 10 | context.fail('unsupported event source'); 11 | } 12 | convert(eventRecord.s3.bucket.name, eventRecord.s3.object.key).then(context.done, context.fail); 13 | }; 14 | -------------------------------------------------------------------------------- /svg-to-pdf-s3-converter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svg-to-pdf-s3-converter", 3 | "version": "1.0.0", 4 | "description": "Lambda function that waits for SVG files uploaded to S3, converts them to PDF using rsvg-convert and uploads back to S3", 5 | "scripts": { 6 | "start": "claudia create --region us-east-1 --handler main.handler --layers arn:aws:lambda:us-east-1:145266761615:layer:rsvg-convert:2 --no-optional-dependencies --timeout 60 --memory 512", 7 | "connect": "claudia add-s3-event-source --bucket test-svg-bucket --prefix in", 8 | "update": "claudia update --no-optional-dependencies" 9 | }, 10 | "author": "Gojko Adzic ", 11 | "license": "MIT", 12 | "dependencies": { 13 | "uuid": "^2.0.2" 14 | }, 15 | "optionalDependencies": { 16 | "aws-sdk": "^2.6.2" 17 | }, 18 | "devDependencies": { 19 | "claudia": "^5.3.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /svg-to-pdf-s3-converter/s3-util.js: -------------------------------------------------------------------------------- 1 | /*global module, require, Promise, console */ 2 | 3 | var aws = require('aws-sdk'), 4 | path = require('path'), 5 | fs = require('fs'), 6 | os = require('os'), 7 | uuid = require('uuid'), 8 | s3 = new aws.S3(), 9 | downloadFromS3 = function (bucket, fileKey) { 10 | 'use strict'; 11 | console.log('downloading', bucket, fileKey); 12 | return new Promise(function (resolve, reject) { 13 | var filePath = path.join(os.tmpdir(), uuid.v4() + path.extname(fileKey)), 14 | file = fs.createWriteStream(filePath), 15 | stream = s3.getObject({ 16 | Bucket: bucket, 17 | Key: fileKey 18 | }).createReadStream(); 19 | 20 | stream.setEncoding('utf8'); 21 | 22 | stream.on('error', reject); 23 | file.on('error', reject); 24 | file.on('finish', function () { 25 | console.log('downloaded', bucket, fileKey); 26 | resolve(filePath); 27 | }); 28 | stream.pipe(file); 29 | }); 30 | }, uploadToS3 = function (bucket, fileKey, filePath, acl) { 31 | 'use strict'; 32 | console.log('uploading', bucket, fileKey, filePath, acl); 33 | return new Promise(function (resolve, reject) { 34 | s3.upload({ 35 | Bucket: bucket, 36 | Key: fileKey, 37 | Body: fs.createReadStream(filePath), 38 | ACL: acl || 'private' 39 | }, function (error, result) { 40 | if (error) { 41 | reject(error); 42 | } else { 43 | resolve(result); 44 | } 45 | }) 46 | }); 47 | }; 48 | 49 | module.exports = { 50 | download: downloadFromS3, 51 | upload: uploadToS3 52 | }; 53 | -------------------------------------------------------------------------------- /twilio-shippo-webhook/app.js: -------------------------------------------------------------------------------- 1 | var ApiBuilder = require('claudia-api-builder'), api = new ApiBuilder(), 2 | twilio = require('twilio')('TWILIO_ACCOUNT_SID', 'TWILIO_AUTH_TOKEN'); 3 | 4 | module.exports = api; 5 | 6 | api.post('/sms-updates', function(req) { 7 | // This logic is just here to handle if a location was not included with your 8 | // tracking number that you are requesting. 9 | var body = req.body, 10 | trackingStatus = body.tracking_status, 11 | trackingLocation = ''; 12 | 13 | if (trackingStatus.location) { 14 | if (trackingStatus.location.city) { 15 | trackingLocation = trackingStatus.location.city + ', ' | 16 | trackingStatus.location.state 17 | } 18 | } else { 19 | trackingLocation = 'UNKNOWN'; 20 | } 21 | 22 | return twilio 23 | .sendMessage({ 24 | to: '+1-TEST_NUMBER', // This should be your destination number 25 | from: '+1-TWILIO_NUMBER', // This is your Twilio number in your account 26 | body: 'Tracking #: ' + body.tracking_number + 27 | '\nStatus: ' + trackingStatus.status + 28 | '\nLocation: ' + trackingLocation 29 | }) 30 | .then(function( 31 | success) { // We are using a promise here to help Claudiajs 32 | // make sure the request is executed, otherwise 33 | // our function will exit before it executes 34 | console.log(success); 35 | }) 36 | .catch(function(error) { 37 | console.log(error); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /twilio-shippo-webhook/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "twilio-shippo", 3 | "version": "1.0.0", 4 | "description": "An example of how to get tracking updates texted to you using Shippo's webhooks", 5 | "main": "app.js", 6 | "scripts": { 7 | "create": "claudia create --name twilio-shippo --region us-west-2 --api-module app --profile claudia", 8 | "update": "claudia update --profile claudia", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "Richard Moot", 12 | "license": "ISC", 13 | "dependencies": { 14 | "claudia-api-builder": "^4", 15 | "twilio": "^2.11.1" 16 | }, 17 | "devDependencies": { 18 | "claudia": "^4" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /using-npm-modules/README.md: -------------------------------------------------------------------------------- 1 | A trivial Node.js microservice example, that just returns a different superlative message each time, as a demonstration of using external NPM modules packaged with the Lambda function. (In this case, the superlatives come from the [Superb](https://www.npmjs.com/package/superb) NPM module. 2 | 3 | To try it out, first [set up the credentials](https://github.com/claudiajs/claudia/blob/master/getting_started.md#configuring-access-credentials), then: 4 | 5 | 1. run `npm install` to grab the dependencies 6 | 2. run `npm start` to set up the lambda project under the default name on AWS 7 | 3. run `npm test` to execute the lambda from the command line and print the output on the console 8 | 9 | Re-run the test again to see a different message. Check out the [package.json](package.json) scripts section to see how Claudia gets invoked for the `start` and `test` scripts. Check out the [main.js](main.js) file to see how the Lambda function works. For a bit more fun, modify the code in [main.js](main.js), then run `npm run deploy` to deploy the code to AWS, then run the test again to see the change. 10 | -------------------------------------------------------------------------------- /using-npm-modules/main.js: -------------------------------------------------------------------------------- 1 | /*global exports, require */ 2 | var superb = require('superb'); 3 | exports.handler = function (event, context) { 4 | 'use strict'; 5 | context.succeed(superb.random()); 6 | }; 7 | -------------------------------------------------------------------------------- /using-npm-modules/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "using-npm-modules", 3 | "description": "ClaudiaJS example for external modules", 4 | "version": "1.0.0", 5 | "private": true, 6 | "files": [ 7 | "main.js" 8 | ], 9 | "scripts": { 10 | "start": "claudia create --name using-npm-modules --region us-east-1 --handler main.handler", 11 | "test": "claudia test-lambda", 12 | "deploy": "claudia update" 13 | }, 14 | "devDependencies": { 15 | "claudia": "^5" 16 | }, 17 | "dependencies": { 18 | "superb": "^3.0.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /web-api-custom-cors/README.md: -------------------------------------------------------------------------------- 1 | A simple example demonstrating how to control CORS allowed origins and headers in API responses. 2 | 3 | 1. run `npm install` to grab the dependencies 4 | 2. run `npm start` to set up the lambda project under the default name on AWS 5 | 3. Check out the API ID in `claudia.json` (the previous step creates the file) 6 | 4. execute OPTIONS with a domain that does not belong to claudiajs.com, and you should see an empty `Access-Control-Allow-Origin` header 7 | 8 | ```bash 9 | curl -X OPTIONS -H 'Origin: https://www.example.com' -i https://.execute-api.us-east-1.amazonaws.com/latest/echo 10 | ``` 11 | 12 | 5. try again, but use a subdomain of `claudiajs.com` and you'll see that domain in the allowed origins 13 | 14 | ```bash 15 | curl -X OPTIONS -H 'Origin: https://www.example.claudiajs.com' -i https://.execute-api.us-east-1.amazonaws.com/latest/echo 16 | ``` 17 | 18 | Check out [web.js](web.js) to see how the paths are set up. For more information, see the [Claudia Api Builder](https://github.com/claudiajs/claudia-api-builder) documentation. 19 | 20 | -------------------------------------------------------------------------------- /web-api-custom-cors/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-api-custom-cors", 3 | "description": "An example for returning custom CORS origins/headers", 4 | "version": "1.0.0", 5 | "private": true, 6 | "files": [ 7 | "*.js" 8 | ], 9 | "scripts": { 10 | "start": "claudia create --name web-api-custom-cors --region us-east-1 --api-module web", 11 | "deploy": "claudia update" 12 | }, 13 | "devDependencies": { 14 | "claudia": "^5" 15 | }, 16 | "dependencies": { 17 | "claudia-api-builder": "^4" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /web-api-custom-cors/web.js: -------------------------------------------------------------------------------- 1 | /*global require, module*/ 2 | var ApiBuilder = require('claudia-api-builder'), 3 | api = new ApiBuilder(); 4 | module.exports = api; 5 | 6 | // override default access-control-allow-headers 7 | 8 | api.corsHeaders('Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Api-Version'); 9 | 10 | /* define a global function that returns an allowed cors origin. this will be used for the OPTIONS access-control-allow-origin response header */ 11 | api.corsOrigin(function (request) { 12 | 'use strict'; 13 | console.log('got request', JSON.stringify(request)); 14 | if (/claudiajs.com$/.test(request.normalizedHeaders.origin)) { 15 | return request.normalizedHeaders.origin; 16 | } 17 | return ''; 18 | }); 19 | 20 | // cors headers will automatically be added to all requests 21 | 22 | api.get('/echo', function (request) { 23 | 'use strict'; 24 | return request; 25 | }); 26 | 27 | // you can customise individual responses as well 28 | 29 | api.get('/programmatic-headers', function () { 30 | 'use strict'; 31 | return new api.ApiResponse('OK', {'Access-Control-Allow-Origin': 'https://www.claudiajs.com'}); 32 | }, {success: {headers: ['Access-Control-Allow-Origin']}}); 33 | 34 | -------------------------------------------------------------------------------- /web-api-custom-headers/README.md: -------------------------------------------------------------------------------- 1 | A simple example demonstrating how to use custom headers in API responses. 2 | 3 | 1. run `npm install` to grab the dependencies 4 | 2. run `npm start` to set up the lambda project under the default name on AWS 5 | 3. Check out the API ID in `claudia.json` (the previous step creates the file) 6 | 4. Open https://API_ID.execute-api.us-east-1.amazonaws.com/latest/hard-coded-headers in a browser (replace API_ID with the API ID from `claudia.json`) and inspect the headers. You should see an `X-Version` header with the value `101`, and `Content-Type` of `text/plain` 7 | 8 | Check out [web.js](web.js) to see how the paths are set up. For more information, see the [Claudia Api Builder](https://github.com/claudiajs/claudia-api-builder) documentation. 9 | 10 | -------------------------------------------------------------------------------- /web-api-custom-headers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-api-custom-headers", 3 | "description": "An example for returning custom headers from API Gateway responses", 4 | "version": "1.0.0", 5 | "private": true, 6 | "files": [ 7 | "*.js" 8 | ], 9 | "scripts": { 10 | "start": "claudia create --name web-api-custom-headers --region us-east-1 --api-module web", 11 | "deploy": "claudia update" 12 | }, 13 | "devDependencies": { 14 | "claudia": "^4" 15 | }, 16 | "dependencies": { 17 | "claudia-api-builder": "^4" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /web-api-custom-headers/web.js: -------------------------------------------------------------------------------- 1 | /*global require, module*/ 2 | var ApiBuilder = require('claudia-api-builder'), 3 | api = new ApiBuilder(); 4 | module.exports = api; 5 | 6 | // set headers as key-value pairs using the success.headers property */ 7 | api.get('/hard-coded-headers', function () { 8 | 'use strict'; 9 | return {a: 'b'}; 10 | }, {success: {headers: {'X-Version': '101', 'Content-Type': 'text/plain'}}}); 11 | 12 | api.get('/error-headers', function () { 13 | 'use strict'; 14 | throw 'Abort'; 15 | }, {error: {headers: {'X-Version': '404'}}}); 16 | 17 | // or use dynamic headers by returning new api.ApiResponse(content, headers) 18 | api.get('/programmatic-headers', function () { 19 | 'use strict'; 20 | return new api.ApiResponse('OK', {'X-Version': '202', 'Content-Type': 'text/plain'}); 21 | }); 22 | 23 | // dynamic headers also work with promises, just resolve with new api.ApiResponse 24 | 25 | api.get('/programmatic-headers-promise', function () { 26 | 'use strict'; 27 | return Promise.resolve().then(function () { 28 | return new api.ApiResponse('OK', {'X-Version': '303', 'Content-Type': 'text/plain'}); 29 | }); 30 | }); 31 | 32 | // dynamic headers overwrite default headers, 33 | 34 | api.get('/dynamic-over-static', function () { 35 | 'use strict'; 36 | return new api.ApiResponse('OK', {'X-Version': '303', 'Content-Type': 'text/plain'}); 37 | }, {success: {headers: {'X-Version': 101, 'Y-Version': 202}}}); 38 | 39 | -------------------------------------------------------------------------------- /web-api-custom-status-code/README.md: -------------------------------------------------------------------------------- 1 | A simple example demonstrating how to use custom response codes in API responses. 2 | 3 | 1. run `npm install` to grab the dependencies 4 | 2. run `npm start` to set up the lambda project under the default name on AWS 5 | 3. Check out the API ID in `claudia.json` (the previous step creates the file) 6 | 4. Open https://API_ID.execute-api.us-east-1.amazonaws.com/latest/status-code in a browser (replace API_ID with the API ID from `claudia.json`) and inspect the headers. You should see the status code of 201 7 | 8 | Check out [web.js](web.js) to see how the paths are set up. For more information, see the [Claudia Api Builder](https://github.com/claudiajs/claudia-api-builder) documentation. 9 | 10 | -------------------------------------------------------------------------------- /web-api-custom-status-code/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-api-custom-headers", 3 | "description": "An example for returning custom headers from API Gateway responses", 4 | "version": "1.0.0", 5 | "private": true, 6 | "files": [ 7 | "*.js" 8 | ], 9 | "scripts": { 10 | "start": "claudia create --name web-api-custom-headers --region us-east-1 --api-module web", 11 | "deploy": "claudia update" 12 | }, 13 | "devDependencies": { 14 | "claudia": "^4" 15 | }, 16 | "dependencies": { 17 | "claudia-api-builder": "^4" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /web-api-custom-status-code/web.js: -------------------------------------------------------------------------------- 1 | /*global require, module*/ 2 | var ApiBuilder = require('claudia-api-builder'), 3 | api = new ApiBuilder(); 4 | module.exports = api; 5 | 6 | // set headers as key-value pairs using the success.headers property */ 7 | api.get('/status-code', function () { 8 | 'use strict'; 9 | return 'OK'; 10 | }, {success: 201 }); 11 | 12 | api.get('/error-code', function () { 13 | 'use strict'; 14 | throw 'Abort'; 15 | }, {error: 404}); 16 | 17 | // or use dynamic codes returning new api.ApiResponse(content, headers) 18 | api.get('/programmatic-codes', function () { 19 | 'use strict'; 20 | return new api.ApiResponse('OK', {'Content-Type': 'text/plain'}, 202); 21 | }); 22 | 23 | // dynamic headers also work with promises, just resolve with new api.ApiResponse 24 | 25 | api.get('/programmatic-codes-promise', function () { 26 | 'use strict'; 27 | return Promise.resolve().then(function () { 28 | return new api.ApiResponse('OK', {'X-Version': '303', 'Content-Type': 'text/plain'}, 400); 29 | }); 30 | }); 31 | 32 | -------------------------------------------------------------------------------- /web-api-generic-handlers/README.md: -------------------------------------------------------------------------------- 1 | A simple example demonstrating how to create generic handlers with Claudia API Builder. Since September 2016, API Gateway allows you to set up a wildcard path matcher using `{proxy+}`, and set up a single handler for all supported HTTP methods using the `ANY` method. With Claudia API Builder, since version 2.0, you can use both those features directly. 2 | 3 | 1. run `npm install` to grab the dependencies 4 | 2. run `npm start` to set up the lambda project under the default name on AWS. It will print a URL. 5 | 3. Open the `URL + '/echo'` in a browser to see the generic response, post to it, or invoke it with some other HTTP method using curl. 6 | 4. Open the `URL + '/dynamic/' + any path` to see how dynamic paths are parsed 7 | 8 | Check out [web.js](web.js) to see how the paths are set up. For more information, see the [Claudia Api Builder](https://github.com/claudiajs/claudia-api-builder) documentation. 9 | 10 | -------------------------------------------------------------------------------- /web-api-generic-handlers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-api-generic-handlers", 3 | "description": "Demo project for generic handlers", 4 | "version": "1.0.0", 5 | "private": true, 6 | "files": [ 7 | "*.js" 8 | ], 9 | "scripts": { 10 | "start": "claudia create --region us-east-1 --api-module web", 11 | "deploy": "claudia update" 12 | }, 13 | "devDependencies": { 14 | "claudia": "^4" 15 | }, 16 | "dependencies": { 17 | "claudia-api-builder": "^4" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /web-api-generic-handlers/web.js: -------------------------------------------------------------------------------- 1 | /*global require, module*/ 2 | var ApiBuilder = require('claudia-api-builder'), 3 | api = new ApiBuilder(); 4 | module.exports = api; 5 | 6 | 7 | api.any('/echo', function (request) { 8 | 'use strict'; 9 | return request; 10 | }); 11 | 12 | api.get('/dynamic/{proxy+}', function (request) { 13 | 'use strict'; 14 | return 'You called ' + request.pathParams.proxy; 15 | }, { success: { contentType: 'text/plain'}}); 16 | -------------------------------------------------------------------------------- /web-api-lambda-context/README.md: -------------------------------------------------------------------------------- 1 | # Accessing Lambda Context from Api Builder Requests 2 | 3 | This example shows how to access the [Lambda Context Object](http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html) from a web API built using [`claudia-api-builder'`](https://github.com/claudiajs/claudia-api-builder). 4 | 5 | This might be useful if you want to access the internal Lambda request ID, log groups or streams automatically created for your Lambda function, or terminate the execution using some other mechanism, instead of the default API Builder workflow. 6 | 7 | The context object is also useful if you want to detect which version of your function got called, for example if you're running multiple versions for development, testing and production. Check out the [Detecting Context Example](../detecting-context/) to see how you can use this to detect the version. 8 | 9 | ## Try it out 10 | 11 | Install the dependencies: 12 | 13 | ```bash 14 | npm install 15 | ``` 16 | 17 | Deploy the API to AWS: 18 | 19 | ```bash 20 | npm start 21 | ``` 22 | 23 | The deployer will print out the web API URL when it completes. Paste that in your browser or execute using CURL, and you'll see the Lambda context details 24 | 25 | ## How it works 26 | 27 | The Lambda context object gets assigned to `request.lambdaContext` (since `claudia-api-builder` 1.3.0), so you can just access all the context fields and methods directly. Check out [web.js](web.js) to see how it is actually used in the code. 28 | 29 | -------------------------------------------------------------------------------- /web-api-lambda-context/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-api-lambda-context", 3 | "version": "1.0.0", 4 | "description": "simple example showing how to access the lambda context when using claudia-api-builder", 5 | "main": "web.js", 6 | "scripts": { 7 | "start": "claudia create --region us-east-1 --api-module web" 8 | }, 9 | "author": "", 10 | "license": "MIT", 11 | "dependencies": { 12 | "claudia-api-builder": "^4" 13 | }, 14 | "devDependencies": { 15 | "claudia": "^4" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /web-api-lambda-context/web.js: -------------------------------------------------------------------------------- 1 | /*global require, console, module */ 2 | var ApiBuilder = require('claudia-api-builder'), 3 | api = new ApiBuilder(); 4 | 5 | module.exports = api; 6 | 7 | api.get('/', function (request) { 8 | 'use strict'; 9 | console.log(request); 10 | return request.lambdaContext; 11 | }); 12 | -------------------------------------------------------------------------------- /web-api-postdeploy-configuration/README.md: -------------------------------------------------------------------------------- 1 | A simple example demonstrating how to use post-deploy configuration hooks: 2 | 3 | 1. run `npm install` to grab the dependencies 4 | 2. run `npm start` to set up the lambda project; it will ask you to configure the message from the console during deployment 5 | 3. load the URL that the create command prints out -- you should see the message you configured 6 | 4. run `npm run deploy` to update the lambda without changing the configuration 7 | 5. run `npm run configure` to update the lambda with a configuration value set in package.json 8 | 6. run `npm run configure-interactive` to update the configuration from the console during deployment 9 | 10 | Check out [package.json](package.json) to see how the configuration parameter `custom-message` is passed to these steps, and check out [web.js](web.js), especially the line starting with `api.addPostDeployConfiguration` to see how the argument gets used to initialise stage variables. 11 | 12 | -------------------------------------------------------------------------------- /web-api-postdeploy-configuration/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-api-postdeploy-config", 3 | "description": "An example of how to add post deploy variable configuration to web APIs", 4 | "version": "1.0.0", 5 | "private": true, 6 | "scripts": { 7 | "start": "claudia create --region us-east-1 --api-module web --custom-message", 8 | "configure": "claudia update --custom-message 'from-update'", 9 | "configure-interactive": "claudia update --custom-message", 10 | "deploy": "claudia update" 11 | }, 12 | "devDependencies": { 13 | "claudia": "^4" 14 | }, 15 | "dependencies": { 16 | "claudia-api-builder": "^4" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /web-api-postdeploy-configuration/web.js: -------------------------------------------------------------------------------- 1 | /*global require, module*/ 2 | var ApiBuilder = require('claudia-api-builder'), 3 | api = new ApiBuilder(); 4 | 5 | module.exports = api; 6 | 7 | api.get('/', function (request) { 8 | 'use strict'; 9 | if (!request.env.message) { 10 | return 'message is not configred. Run "npm run configure" to set it up'; 11 | } else { 12 | return 'you configured ' + request.env.message; 13 | } 14 | 15 | }); 16 | 17 | api.addPostDeployConfig('message', 'Enter a message:', 'custom-message'); 18 | -------------------------------------------------------------------------------- /web-api-postdeploy-private-gateway/README.md: -------------------------------------------------------------------------------- 1 | An example of a private API gateway configured to work on an AWS VPC: 2 | 3 | 1. run `npm install` to grab the dependencies 4 | 2. run `npm start` to set up the lambda project under the default name on AWS 5 | 3. load the URL that the create command prints out -- you should see `Hello from your Private API` 6 | 7 | 8 | Check out [package.json](package.json) to see the configuration for the security groups and VPCEs. 9 | 10 | `Troubleshooting:` 11 | If after deploying your gateway you can't appear to access or resolve it, ensure that you are able to resolve URL's on the private DNS. 12 | 13 | **[Private API Considerations](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-private-apis.html#apigateway-private-api-design-considerations)** 14 | 15 | 16 | -------------------------------------------------------------------------------- /web-api-postdeploy-private-gateway/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-api-private-gateway", 3 | "description": "An example of how to create a private API gateway deployment", 4 | "version": "1.0.0", 5 | "private": true, 6 | "scripts": { 7 | "start": "claudia create --region eu-west-1 --api-module web --vpce $npm_package_security_vpce --account $npm_package_security_account --security-group-ids $npm_package_security_group --subnet-ids $npm_package_security_subnets" 8 | }, 9 | "security": { 10 | "account": "Your account number goes here", 11 | "group": "Your security group ID goes here", 12 | "vpce": "Your VPCE ID goes here", 13 | "subnets": "Your subnet ID's go here" 14 | 15 | }, 16 | "devDependencies": { 17 | "claudia": "^4" 18 | }, 19 | "dependencies": { 20 | "claudia-api-builder": "^4" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /web-api-postdeploy-private-gateway/web.js: -------------------------------------------------------------------------------- 1 | /*global require, module*/ 2 | var ApiBuilder = require('claudia-api-builder'), 3 | api = new ApiBuilder(); 4 | 5 | module.exports = api; 6 | 7 | api.get('/', function () { 8 | 'use strict'; 9 | return 'Hello from your Private API'; 10 | }); 11 | 12 | api.addPostDeployStep('Update to Private', async function (options, lambdaDetails, utils) { 13 | 'use strict'; 14 | 15 | let policy = '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":"*","Action":"execute-api:Invoke","Resource":"arn:aws:execute-api:REGIONTEMPLATE:ACCOUNTTEMPLATE:APITEMPLATE/*","Condition":{"StringEquals":{"aws:sourceVpce":"VPCETEMPLATE"}}}]}'; 16 | policy = policy.replace("ACCOUNTTEMPLATE",options.account); 17 | policy = policy.replace("APITEMPLATE",lambdaDetails.apiId); 18 | policy = policy.replace("REGIONTEMPLATE",lambdaDetails.region); 19 | policy = policy.replace("VPCETEMPLATE",options.vpce); 20 | 21 | let params = { 22 | restApiId: lambdaDetails.apiId, 23 | patchOperations: [ 24 | //This patch will replace the existing policy with your policy above 25 | { 26 | op: 'replace', 27 | path: '/policy', 28 | value: policy 29 | }, 30 | //This patch will update the API endpoint type from EDGE -> PRIVATE 31 | { 32 | op: 'replace', 33 | path: '/endpointConfiguration/types/EDGE', 34 | value :'PRIVATE' 35 | } 36 | ] 37 | }; 38 | await utils.apiGatewayPromise.updateRestApiPromise(params); 39 | 40 | 41 | await utils.apiGatewayPromise.createDeploymentPromise({restApiId: lambdaDetails.apiId, stageName: lambdaDetails.alias}); 42 | }); 43 | 44 | -------------------------------------------------------------------------------- /web-api-postdeploy/README.md: -------------------------------------------------------------------------------- 1 | A simple example demonstrating how to use post-deploy hooks: 2 | 3 | 1. run `npm install` to grab the dependencies 4 | 2. run `npm start` to set up the lambda project under the default name on AWS 5 | 3. load the URL that the create command prints out -- you should see `from-create` 6 | 4. run `npm run deploy` to update the lambda with a different configuration in a post-deploy step 7 | 5. load the URL again, you should see `from-update` 8 | 9 | 10 | Check out [package.json](package.json) to see how the configuration parameter `custom-message` is passed to these steps, and check out [web.js](web.js), especially the like starting with `api.addPostDeployStep` to see how the argument gets used to initialise stage variables. 11 | 12 | -------------------------------------------------------------------------------- /web-api-postdeploy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-api-postinstall1", 3 | "description": "An example of how to add post install hooks to web APIs", 4 | "version": "1.0.0", 5 | "private": true, 6 | "scripts": { 7 | "start": "claudia create --region us-east-1 --api-module web --custom-message 'from-create'", 8 | "deploy": "claudia update --custom-message 'from-update'" 9 | }, 10 | "devDependencies": { 11 | "claudia": "^4" 12 | }, 13 | "dependencies": { 14 | "claudia-api-builder": "^4" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /web-api-postdeploy/web.js: -------------------------------------------------------------------------------- 1 | /*global require, module*/ 2 | var ApiBuilder = require('claudia-api-builder'), 3 | api = new ApiBuilder(); 4 | 5 | module.exports = api; 6 | 7 | api.get('/', function (request) { 8 | 'use strict'; 9 | return request.env.message || 'hello world'; 10 | }); 11 | 12 | api.addPostDeployStep('message', function (options, lambdaDetails, utils) { 13 | 'use strict'; 14 | var customMessage = options['custom-message']; 15 | if (!customMessage) { 16 | return; 17 | } 18 | return utils.apiGatewayPromise.createDeploymentPromise({ 19 | restApiId: lambdaDetails.apiId, 20 | stageName: lambdaDetails.alias, 21 | variables: { 'message': customMessage } 22 | }).then(function () { 23 | return customMessage; 24 | }); 25 | }); 26 | 27 | -------------------------------------------------------------------------------- /web-api/README.md: -------------------------------------------------------------------------------- 1 | A simple example demonstrating how to create and configure a Web REST API connected to a Lambda function with Node.js and Claudia.js. To try it out, first [set up the credentials](https://github.com/claudiajs/claudia/blob/master/getting_started.md#configuring-access-credentials), then: 2 | 3 | 1. run `npm install` to grab the dependencies 4 | 2. run `npm start` to set up the lambda project under the default name on AWS 5 | 3. Check out the API ID in `claudia.json` (the previous step creates the file) 6 | 4. Open https://API_ID.execute-api.us-east-1.amazonaws.com/latest/greet?name=Mike in a browser (replace API_ID with the API ID from `claudia.json`) 7 | 8 | Check out [web.js](web.js) to see how the paths are set up. For more information, see the [Claudia Api Builder](https://github.com/claudiajs/claudia-api-builder) documentation. 9 | 10 | When a version is not provided for the create or update command (as in this case, check out the [package.json](package.json) scripts section), Claudia will automatically create a stage called `latest` for the API, and deploy that using the `$LATEST` Lambda function version. That's where the `/latest/` part of the URL comes from. If a version is provided with the deployment, then an API gateway stage will be created for that name, and connected to the appropriate Lambda version alias. This makes it easy to run several Lambda and API versions at the same time, for development, testing, production etc. 11 | 12 | Claudia assigns a generic input processing template to all requests, that just dumps all the available parameters (headers, query string and stage variables) into a JSON object, available to your request handler. You can see the entire request object passed from Api Gateway to Lambda using the /echo path (https://API_ID.execute-api.us-east-1.amazonaws.com/latest/echo?name=Mike). 13 | 14 | 15 | -------------------------------------------------------------------------------- /web-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-api-test", 3 | "description": "Simple example that shows how to create WEB apis", 4 | "private": true, 5 | "files": [ 6 | "*.js" 7 | ], 8 | "scripts": { 9 | "start": "claudia create --name web-api-test --region us-east-1 --api-module web", 10 | "deploy": "claudia update" 11 | }, 12 | "devDependencies": { 13 | "claudia": "^5" 14 | }, 15 | "dependencies": { 16 | "claudia-api-builder": "^4", 17 | "superb": "^3.0.0" 18 | }, 19 | "version": "1.0.0", 20 | "main": "web.js", 21 | "license": "MIT" 22 | } 23 | -------------------------------------------------------------------------------- /web-api/web.js: -------------------------------------------------------------------------------- 1 | /*global require, module*/ 2 | var ApiBuilder = require('claudia-api-builder'), 3 | api = new ApiBuilder(), 4 | fs = require('fs'), 5 | superb = require('superb'), 6 | util = require('util'); 7 | module.exports = api; 8 | 9 | // just return the result value for synchronous processing 10 | api.get('/hello', function () { 11 | 'use strict'; 12 | return 'hello world'; 13 | }); 14 | 15 | // pass some arguments using the query string or headers to this 16 | // method and see that they're all in the request object 17 | api.get('/echo', function (request) { 18 | 'use strict'; 19 | return request; 20 | }); 21 | 22 | // use request.queryString for query arguments 23 | api.get('/greet', function (request) { 24 | 'use strict'; 25 | return request.queryString.name + ' is ' + superb.random(); 26 | }); 27 | 28 | // use {} for dynamic path parameters 29 | api.get('/people/{name}', function (request) { 30 | 'use strict'; 31 | return request.pathParams.name + ' is ' + superb.random(); 32 | }); 33 | 34 | // Return a promise for async processing 35 | api.get('/packagejson', function () { 36 | 'use strict'; 37 | var read = util.promisify(fs.readFile); 38 | return read('./package.json') 39 | .then(JSON.parse) 40 | .then(function (val) { 41 | return val; 42 | }); 43 | }); 44 | 45 | // use .post to handle a post; or .delete, .patch, .head, .put 46 | api.post('/echo', function (request) { 47 | 'use strict'; 48 | return request; 49 | }); 50 | -------------------------------------------------------------------------------- /web-serving-html/README.md: -------------------------------------------------------------------------------- 1 | An example demonstrating how to customise success/error response codes and content types in a Web API connected to a Lambda function with Node.js and 2 | Claudia.js. This example will serve HTML pages instead of the default `application/json` response type, and it changes error code to 403, instead of the default 500. Finally, the search box redirects to Github using a 302 response code. 3 | 4 | To try it out, first [set up the credentials](https://github.com/claudiajs/claudia/blob/master/getting_started.md#configuring-access-credentials), then: 5 | 6 | 1. run `npm install` to grab the dependencies 7 | 2. run `npm start` to set up the lambda project under the default name on AWS 8 | 3. Check out the API ID in `claudia.json` (the previous step creates the file) 9 | 4. Open https://API_ID.execute-api.us-east-1.amazonaws.com/latest/start.html in a browser (replace API_ID with the API ID from `claudia.json`) 10 | 11 | Check out [web.js](web.js) to see how the paths are set up. For more information, see the [Claudia Api Builder](https://github.com/claudiajs/claudia-api-builder) documentation. 12 | -------------------------------------------------------------------------------- /web-serving-html/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-serving-html-test", 3 | "description": "An example of how to customise HTTP response codes and content types", 4 | "version": "1.0.0", 5 | "private": true, 6 | "files": [ 7 | "*.js" 8 | ], 9 | "scripts": { 10 | "start": "claudia create --name web-serving-html-test --region us-east-1 --api-module web", 11 | "deploy": "claudia update" 12 | }, 13 | "devDependencies": { 14 | "claudia": "^5" 15 | }, 16 | "dependencies": { 17 | "claudia-api-builder": "^4", 18 | "superb": "^3.0.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /web-serving-html/web.js: -------------------------------------------------------------------------------- 1 | /*global require, module*/ 2 | var ApiBuilder = require('claudia-api-builder'), 3 | api = module.exports = new ApiBuilder(), 4 | superb = require('superb'), 5 | renderPage = function (body) { 6 | 'use strict'; 7 | return ' ' + 8 | '' + 9 | '

Hello from Claudia.js

' + 10 | body + 11 | '' + 12 | ''; 13 | }; 14 | 15 | // this should show up as a normal web page in the browser, the response type is text/html 16 | api.get('/start.html', function () { 17 | 'use strict'; 18 | return renderPage( 19 | '' 24 | ); 25 | }, { success: { contentType: 'text/html'}}); 26 | 27 | // because the success code is 3xx, the content will be used as the redirect location 28 | api.get('/redirect', function () { 29 | 'use strict'; 30 | return 'https://github.com/claudiajs/claudia'; 31 | }, { success: 302 }); 32 | 33 | // both the success and the error show as web pages, but the error is 403, not the default 500 34 | api.get('/search', function (request) { 35 | 'use strict'; 36 | if (request.queryString.name) { 37 | return renderPage('

' + request.queryString.name + ' is ' + superb.random() + '

'); 38 | } else { 39 | throw renderPage('
Please provide a name'); 40 | } 41 | }, {error: {code: 403, contentType: 'text/html'}, success: {contentType: 'text/html'}}); 42 | 43 | -------------------------------------------------------------------------------- /web-serving-universal-react/build/client/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "main.css": "production/static/css/main.cacbacc7.css", 3 | "main.css.map": "production/static/css/main.cacbacc7.css.map", 4 | "main.js": "production/static/js/main.fb8951f8.js", 5 | "main.js.map": "production/static/js/main.fb8951f8.js.map", 6 | "static/media/logo.svg": "production/static/media/logo.5d5d9eef.svg" 7 | } -------------------------------------------------------------------------------- /web-serving-universal-react/build/client/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudiajs/example-projects/32d07bea4a59023557ddef88894425c36e340ab2/web-serving-universal-react/build/client/favicon.ico -------------------------------------------------------------------------------- /web-serving-universal-react/build/client/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /web-serving-universal-react/build/client/service-worker.js: -------------------------------------------------------------------------------- 1 | "use strict";function setOfCachedUrls(e){return e.keys().then(function(e){return e.map(function(e){return e.url})}).then(function(e){return new Set(e)})}var precacheConfig=[["static/css/main.cacbacc7.css","214eac7d9453fa4dbe07c4ea75d9e557"],["static/js/main.fb8951f8.js","de5ed7633dc0f041ea9cfe96186ff72e"],["static/media/logo.5d5d9eef.svg","5d5d9eefa31e5e13a6610d9fa7a283bb"]],cacheName="sw-precache-v3-sw-precache-webpack-plugin-"+(self.registration?self.registration.scope:""),ignoreUrlParametersMatching=[/^utm_/],addDirectoryIndex=function(e,t){var n=new URL(e);return"/"===n.pathname.slice(-1)&&(n.pathname+=t),n.toString()},cleanResponse=function(e){return e.redirected?("body"in e?Promise.resolve(e.body):e.blob()).then(function(t){return new Response(t,{headers:e.headers,status:e.status,statusText:e.statusText})}):Promise.resolve(e)},createCacheKey=function(e,t,n,r){var a=new URL(e);return r&&a.pathname.match(r)||(a.search+=(a.search?"&":"")+encodeURIComponent(t)+"="+encodeURIComponent(n)),a.toString()},isPathWhitelisted=function(e,t){if(0===e.length)return!0;var n=new URL(t).pathname;return e.some(function(e){return n.match(e)})},stripIgnoredUrlParameters=function(e,t){var n=new URL(e);return n.hash="",n.search=n.search.slice(1).split("&").map(function(e){return e.split("=")}).filter(function(e){return t.every(function(t){return!t.test(e[0])})}).map(function(e){return e.join("=")}).join("&"),n.toString()},hashParamName="_sw-precache",urlsToCacheKeys=new Map(precacheConfig.map(function(e){var t=e[0],n=e[1],r=new URL(t,self.location),a=createCacheKey(r,hashParamName,n,/\.\w{8}\./);return[r.toString(),a]}));self.addEventListener("install",function(e){e.waitUntil(caches.open(cacheName).then(function(e){return setOfCachedUrls(e).then(function(t){return Promise.all(Array.from(urlsToCacheKeys.values()).map(function(n){if(!t.has(n)){var r=new Request(n,{credentials:"same-origin"});return fetch(r).then(function(t){if(!t.ok)throw new Error("Request for "+n+" returned a response with status "+t.status);return cleanResponse(t).then(function(t){return e.put(n,t)})})}}))})}).then(function(){return self.skipWaiting()}))}),self.addEventListener("activate",function(e){var t=new Set(urlsToCacheKeys.values());e.waitUntil(caches.open(cacheName).then(function(e){return e.keys().then(function(n){return Promise.all(n.map(function(n){if(!t.has(n.url))return e.delete(n)}))})}).then(function(){return self.clients.claim()}))}),self.addEventListener("fetch",function(e){if("GET"===e.request.method){var t,n=stripIgnoredUrlParameters(e.request.url,ignoreUrlParametersMatching);t=urlsToCacheKeys.has(n);t||(n=addDirectoryIndex(n,"index.html"),t=urlsToCacheKeys.has(n));!t&&"navigate"===e.request.mode&&isPathWhitelisted(["^(?!\\/__).*"],e.request.url)&&(n=new URL("/index.html",self.location).toString(),t=urlsToCacheKeys.has(n)),t&&e.respondWith(caches.open(cacheName).then(function(e){return e.match(urlsToCacheKeys.get(n)).then(function(e){if(e)return e;throw Error("The cached response that was expected is missing.")})}).catch(function(t){return console.warn('Couldn\'t serve response for "%s" from cache: %O',e.request.url,t),fetch(e.request)}))}}); -------------------------------------------------------------------------------- /web-serving-universal-react/build/client/static/css/main.cacbacc7.css: -------------------------------------------------------------------------------- 1 | body{margin:0;padding:0;font-family:sans-serif}.App{text-align:center}.App-logo{-webkit-animation:App-logo-spin infinite 20s linear;animation:App-logo-spin infinite 20s linear;height:80px}.App-header{background-color:#222;height:150px;padding:20px;color:#fff}.App-intro{font-size:large}@-webkit-keyframes App-logo-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes App-logo-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}} 2 | /*# sourceMappingURL=main.cacbacc7.css.map*/ -------------------------------------------------------------------------------- /web-serving-universal-react/build/client/static/css/main.cacbacc7.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["client/index.css"],"names":[],"mappings":"AAAA,KACE,SACA,UACA,sBAAwB,CAG1B,KACE,iBAAmB,CAGrB,UACE,oDACQ,4CACR,WAAa,CAGf,YACE,sBACA,aACA,aACA,UAAa,CAGf,WACE,eAAiB,CAGnB,iCACE,GAAO,+BAAiC,sBAAwB,CAChE,GAAK,gCAAmC,uBAA0B,CAAE,CAGtE,yBACE,GAAO,+BAAiC,sBAAwB,CAChE,GAAK,gCAAmC,uBAA0B,CAAE","file":"static/css/main.cacbacc7.css","sourcesContent":["body {\n margin: 0;\n padding: 0;\n font-family: sans-serif;\n}\n\n.App {\n text-align: center;\n}\n\n.App-logo {\n -webkit-animation: App-logo-spin infinite 20s linear;\n animation: App-logo-spin infinite 20s linear;\n height: 80px;\n}\n\n.App-header {\n background-color: #222;\n height: 150px;\n padding: 20px;\n color: white;\n}\n\n.App-intro {\n font-size: large;\n}\n\n@-webkit-keyframes App-logo-spin {\n from { -webkit-transform: rotate(0deg); transform: rotate(0deg); }\n to { -webkit-transform: rotate(360deg); transform: rotate(360deg); }\n}\n\n@keyframes App-logo-spin {\n from { -webkit-transform: rotate(0deg); transform: rotate(0deg); }\n to { -webkit-transform: rotate(360deg); transform: rotate(360deg); }\n}\n\n\n\n// WEBPACK FOOTER //\n// ./src/client/index.css"],"sourceRoot":""} -------------------------------------------------------------------------------- /web-serving-universal-react/build/client/static/media/logo.5d5d9eef.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /web-serving-universal-react/build/server/static/media/logo.5d5d9eef.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /web-serving-universal-react/config/env.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | 7 | // Make sure that including paths.js after env.js will read .env variables. 8 | delete require.cache[require.resolve('./paths')]; 9 | 10 | const NODE_ENV = process.env.NODE_ENV; 11 | if (!NODE_ENV) { 12 | throw new Error( 13 | 'The NODE_ENV environment variable is required but was not specified.' 14 | ); 15 | } 16 | 17 | // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use 18 | var dotenvFiles = [ 19 | `${paths.dotenv}.${NODE_ENV}.local`, 20 | `${paths.dotenv}.${NODE_ENV}`, 21 | // Don't include `.env.local` for `test` environment 22 | // since normally you expect tests to produce the same 23 | // results for everyone 24 | NODE_ENV !== 'test' && `${paths.dotenv}.local`, 25 | paths.dotenv, 26 | ].filter(Boolean); 27 | 28 | // Load environment variables from .env* files. Suppress warnings using silent 29 | // if this file is missing. dotenv will never modify any environment variables 30 | // that have already been set. 31 | // https://github.com/motdotla/dotenv 32 | dotenvFiles.forEach(dotenvFile => { 33 | if (fs.existsSync(dotenvFile)) { 34 | require('dotenv').config({ 35 | path: dotenvFile, 36 | }); 37 | } 38 | }); 39 | 40 | // We support resolving modules according to `NODE_PATH`. 41 | // This lets you use absolute paths in imports inside large monorepos: 42 | // https://github.com/facebookincubator/create-react-app/issues/253. 43 | // It works similar to `NODE_PATH` in Node itself: 44 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 45 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 46 | // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. 47 | // https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421 48 | // We also resolve them to make sure all tools using them work consistently. 49 | const appDirectory = fs.realpathSync(process.cwd()); 50 | process.env.NODE_PATH = (process.env.NODE_PATH || '') 51 | .split(path.delimiter) 52 | .filter(folder => folder && !path.isAbsolute(folder)) 53 | .map(folder => path.resolve(appDirectory, folder)) 54 | .join(path.delimiter); 55 | 56 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 57 | // injected into the application via DefinePlugin in Webpack configuration. 58 | const REACT_APP = /^REACT_APP_/i; 59 | 60 | function getClientEnvironment(publicUrl) { 61 | const raw = Object.keys(process.env) 62 | .filter(key => REACT_APP.test(key)) 63 | .reduce( 64 | (env, key) => { 65 | env[key] = process.env[key]; 66 | return env; 67 | }, 68 | { 69 | // Useful for determining whether we’re running in production mode. 70 | // Most importantly, it switches React into the correct mode. 71 | NODE_ENV: process.env.NODE_ENV || 'development', 72 | // Useful for resolving the correct path to static assets in `public`. 73 | // For example, . 74 | // This should only be used as an escape hatch. Normally you would put 75 | // images into the `src` and `import` them in code to get their paths. 76 | PUBLIC_URL: publicUrl, 77 | } 78 | ); 79 | // Stringify all values so we can feed into Webpack DefinePlugin 80 | const stringified = { 81 | 'process.env': Object.keys(raw).reduce( 82 | (env, key) => { 83 | env[key] = JSON.stringify(raw[key]); 84 | return env; 85 | }, 86 | {} 87 | ), 88 | }; 89 | 90 | return { raw, stringified }; 91 | } 92 | 93 | module.exports = getClientEnvironment; 94 | -------------------------------------------------------------------------------- /web-serving-universal-react/config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /web-serving-universal-react/config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /web-serving-universal-react/config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebookincubator/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(path, needsSlash) { 15 | const hasSlash = path.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return path.substr(path, path.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${path}/`; 20 | } else { 21 | return path; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => 26 | envPublicUrl || require(appPackageJson).homepage; 27 | 28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 29 | // "public path" at which the app is served. 30 | // Webpack needs to know it to put the right 27 | 28 | 29 | ` 30 | -------------------------------------------------------------------------------- /web-serving-universal-react/src/shared/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import { 5 | Route, 6 | Switch, 7 | Link 8 | } from 'react-router-dom'; 9 | 10 | import logo from './logo.svg'; 11 | 12 | const Header = () => ( 13 |
14 | logo 15 |

Welcome to Universal React

16 |
17 | ) 18 | 19 | export const PageNotFound = (props, context = {}) => { 20 | if (context.setStatus) { 21 | context.setStatus(404) 22 | } 23 | 24 | return ( 25 |
26 |

27 | Page not found 28 |

29 | 30 | Go home 31 | 32 |
33 | ) 34 | } 35 | PageNotFound.contextTypes = { 36 | setStatus: PropTypes.func.isRequired 37 | } 38 | 39 | const TestRouterPage = ({ match }) => ( 40 |
41 |

42 | Test page {match.params.id} 43 |

44 |

45 | 46 | Home 47 | 48 |

49 |

50 | 51 | Go to non-existent page 52 | 53 |

54 |
55 | ) 56 | 57 | const Home = () => ( 58 |
59 |

To get started, edit src/shared/App.js and save to reload.

60 | 61 | Test the router 62 | 63 |
64 | ) 65 | 66 | const App = () => ( 67 |
68 | ( 69 |
70 |
71 | 72 | 73 | 74 | 75 | 76 |
77 | )}/> 78 |
79 | ) 80 | 81 | export default App 82 | -------------------------------------------------------------------------------- /web-serving-universal-react/src/shared/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | }); 9 | -------------------------------------------------------------------------------- /web-serving-universal-react/src/shared/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /website-email-alert/README.md: -------------------------------------------------------------------------------- 1 | # Website Email Alert 2 | 3 | This Lambda service checks if a URL is available, and if not, sends an e-mail. Kind of like pingdom. 4 | 5 | The project demonstrates how to integrate Lambda with [AWS Simple E-mail Service](https://aws.amazon.com/ses/). 6 | 7 | ## Prerequisites 8 | 9 | If you have never used AWS SES before, you'll need to first verify e-mails that will be used for sending and receiving. Do so from your command line by executing 10 | 11 | ```` 12 | aws ses verify-email-identity --email-address EMAIL@DOMAIN 13 | 14 | ```` 15 | Verifying two e-mail addresses should allow you to see this example in action, but if you want to send e-mails without having to verify every single recipient in the future, you will also need to get out of the SES Sandbox. For more info, check [Requesting Production Access to SES](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/request-production-access.html). 16 | 17 | SES service is specific to a region, so you might want to change the active region in the NPM `start` script section of `package.json`. 18 | 19 | ## Try it out 20 | 21 | 1. run `npm install` to grab the dependencies 22 | 2. run `npm start` to set up the lambda project under the default name on AWS 23 | 3. edit the [test.json](test.json) file and set up the e-mail accounts, as well as the URL you want to check 24 | 4. run `npm test` to execute the function manually using the test event 25 | 5. Try it out for some existing or non-existing URLs, to see the effect. 26 | 6. Then set it up as a scheduled event on AWS so it runs automatically, by executing `npm run schedule`. This will run the event from `test.json` every five minutes. 27 | 28 | ## How it works 29 | 30 | Check out the [package.json](package.json) scripts section to see how Claudia gets invoked for the `start`, `test` and `schedule` scripts. You can modify 31 | the execution frequency in `package.json` easily and re-create a different event. See the [Schedule Expression Syntax](http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/ScheduledEvents.html) for more information on the syntax. 32 | 33 | You can see all scheduled events in the [CloudWatch Rules Console](https://console.aws.amazon.com/cloudwatch/), and disable the event there if you don't want to receive any more e-mail notifications. 34 | 35 | Check out the [main.js](main.js) file to see how the Lambda function works. 36 | -------------------------------------------------------------------------------- /website-email-alert/main.js: -------------------------------------------------------------------------------- 1 | /*global exports, require */ 2 | var aws = require('aws-sdk'), 3 | got = require('got'); 4 | exports.handler = function (event, context) { 5 | 'use strict'; 6 | var SES = new aws.SES(), 7 | emailConfig = function (err) { 8 | return { 9 | Source: event.email.from, 10 | Destination: { 11 | ToAddresses: [event.email.to] 12 | }, 13 | Message: { 14 | Subject: { Data: '[monitoring] ' + event.url}, 15 | Body: { Text: { Data: 'Problem: ' + JSON.stringify(err)} } 16 | } 17 | }; 18 | }, 19 | start = Date.now(); 20 | 21 | if (!event.email || !event.email.from || !event.email.to) { 22 | return context.fail('email not set up'); 23 | } 24 | if (!event.url) { 25 | return context.fail('url not set up'); 26 | } 27 | got(event.url, {retries: 0, timeout: (event.timeout || 2000)}).then(function () { 28 | context.succeed({success: event.url, time: (Date.now() - start)}); 29 | }, function (e) { 30 | SES.sendEmail(emailConfig({code: e.statusCode || e.code, message: e.statusMessage}), context.done); 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /website-email-alert/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website-email-alert", 3 | "description": "Send email if a web site is unreachable", 4 | "version": "1.0.0", 5 | "private": true, 6 | "files": [ 7 | "main.js" 8 | ], 9 | "scripts": { 10 | "start": "claudia create --name website-email-alert --region us-east-1 --handler main.handler --policies policies", 11 | "test": "claudia test-lambda --event test.json", 12 | "schedule": "claudia add-scheduled-event --event test.json --name website-alerts --rate '5 minutes'", 13 | "deploy": "claudia update" 14 | }, 15 | "devDependencies": { 16 | "claudia": "^4" 17 | }, 18 | "dependencies": { 19 | "aws-sdk": "^2.2.37", 20 | "got": "^5.4.1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /website-email-alert/policies/send-email.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": [ 7 | "ses:SendEmail" 8 | ], 9 | "Resource": [ 10 | "*" 11 | ] 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /website-email-alert/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "email": { 3 | "from":"email@domain", 4 | "to": "email@domain" 5 | }, 6 | "url": "https://some-url" 7 | } 8 | --------------------------------------------------------------------------------