├── .gitignore ├── README.md ├── Shortener-With-Lambdas ├── createShortURL.js └── getLongURL.js └── Shortener-Without-Lambdas ├── Get Mapping Templates ├── get-request.json └── get-response.json └── ShortURL-CUD-Mapping-Templates ├── post-request-mapping-template.json ├── post-response-mapping-templates ├── post-response-200.json └── post-response-400.json └── shortURL ├── delete-request.json ├── delete-response-400.json ├── put-request.json └── put-response.json /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Serverless URL Shortener 2 | 3 | A simple serverless URL shortener built using API Gateway, DynamoDB and optionally Lambda functions 4 | 5 | The beauty of serverless is the ability to build things fast, at the lowest price and the least efforts. AWS API Gateway can be leveraged to 6 | 7 | ### Shortener With Lambda Functions: 8 | 9 | ![Shortener With Lambda Functions](https://user-images.githubusercontent.com/23396903/108629071-32b28f00-7484-11eb-8b47-12ff50acaf83.PNG) 10 | 11 | ### Shortener Without Lambda Functions: 12 | 13 | ![Shortener Without Lambda Functions](https://user-images.githubusercontent.com/23396903/108629059-229aaf80-7484-11eb-9d38-e2095334370f.PNG) 14 | 15 | A repo inspired from Eric Johnson's [repo](https://github.com/aws-samples/amazon-api-gateway-url-shortener). 16 |
Slide deck for the presentation - [konf.me/presentation](konf.me/presentation) 17 | -------------------------------------------------------------------------------- /Shortener-With-Lambdas/createShortURL.js: -------------------------------------------------------------------------------- 1 | const AWS = require("aws-sdk"); 2 | const dynamodb = new AWS.DynamoDB({ apiVersion: "2012-08-10" }); 3 | 4 | exports.handler = (event, context, callback) => { 5 | console.log("Input to the lambda function", event); 6 | const { shortURL, longURL } = event; 7 | return dynamodb 8 | .putItem({ 9 | TableName: "URL-Shortener", 10 | Item: { 11 | shortId: { S: shortURL }, 12 | longURL: { S: longURL }, 13 | owner: { S: "owner" } 14 | } 15 | }) 16 | .promise() 17 | .then(data => { 18 | console.log("response post create", data); 19 | return "Successfully created shortURL"; 20 | }) 21 | .catch(err => { 22 | console.error("error", err); 23 | return err; 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /Shortener-With-Lambdas/getLongURL.js: -------------------------------------------------------------------------------- 1 | const AWS = require("aws-sdk"); 2 | const dynamodb = new AWS.DynamoDB({ apiVersion: "2012-08-10" }); 3 | 4 | exports.handler = async event => { 5 | console.log("Input to the lambda function", event); 6 | const { shortURL } = event; 7 | return dynamodb 8 | .getItem({ 9 | TableName: "shortener", 10 | Key: { 11 | shortId: { S: shortURL } 12 | } 13 | }) 14 | .promise() 15 | .then(response => { 16 | console.log("response from DDB", response); 17 | return { 18 | statusCode: 302, 19 | headers: { 20 | Location: response.Item.longURL.S 21 | } 22 | }; 23 | }) 24 | .catch(err => { 25 | console.error("error while fetching data from DDB", err); 26 | return err; 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /Shortener-Without-Lambdas/Get Mapping Templates/get-request.json: -------------------------------------------------------------------------------- 1 | { 2 | "Key": { 3 | "shortId": { 4 | "S": "$input.params().path.shortId" 5 | } 6 | }, 7 | "TableName": "URL-Shortener" 8 | } 9 | -------------------------------------------------------------------------------- /Shortener-Without-Lambdas/Get Mapping Templates/get-response.json: -------------------------------------------------------------------------------- 1 | #set($DDBResponse = $input.path('$')) 2 | #if ($DDBResponse.toString().contains("Item")) 3 | #set($context.responseOverride.header.Location = $DDBResponse.Item.longURL.S) 4 | #end -------------------------------------------------------------------------------- /Shortener-Without-Lambdas/ShortURL-CUD-Mapping-Templates/post-request-mapping-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "TableName": "URL-Shortener", 3 | "ConditionExpression": "attribute_not_exists(id)", 4 | "Key": { 5 | "shortId": { 6 | "S": $input.json('$.shortURL') 7 | } 8 | }, 9 | "ExpressionAttributeNames": { 10 | "#u": "longURL", 11 | "#o": "owner" 12 | }, 13 | "ExpressionAttributeValues": { 14 | ":u": { 15 | "S": $input.json('$.longURL') 16 | }, 17 | ":o": { 18 | "S": $input.json('$.owner') 19 | } 20 | }, 21 | "UpdateExpression": "SET #u = :u, #o = :o", 22 | "ReturnValues": "ALL_NEW" 23 | } -------------------------------------------------------------------------------- /Shortener-Without-Lambdas/ShortURL-CUD-Mapping-Templates/post-response-mapping-templates/post-response-200.json: -------------------------------------------------------------------------------- 1 | #set($DDBResponse = $input.path('$')) 2 | { 3 | "shortURL": "$DDBResponse.Attributes.shortId.S", 4 | "longURL": "$DDBResponse.Attributes.longURL.S", 5 | "owner": "$DDBResponse.Attributes.owner.S" 6 | } -------------------------------------------------------------------------------- /Shortener-Without-Lambdas/ShortURL-CUD-Mapping-Templates/post-response-mapping-templates/post-response-400.json: -------------------------------------------------------------------------------- 1 | #set($DDBResponse = $input.path('$')) 2 | #if($DDBResponse.toString().contains("ConditionalCheckFailedException")) 3 | #set($context.responseOverride.status = 200) 4 | { 5 | "error": true, 6 | "message": "URL link already exists" 7 | } 8 | #end -------------------------------------------------------------------------------- /Shortener-Without-Lambdas/ShortURL-CUD-Mapping-Templates/shortURL/delete-request.json: -------------------------------------------------------------------------------- 1 | { 2 | "Key": { 3 | "id": { 4 | "S": "$input.params().path.linkId" 5 | } 6 | }, 7 | "TableName": "ShotenerTest", 8 | "ConditionExpression": "#owner = :owner", 9 | "ExpressionAttributeValues": { 10 | ":owner": { 11 | "S": $input.json('$.owner') 12 | } 13 | }, 14 | "ExpressionAttributeNames": { 15 | "#owner": "owner" 16 | } 17 | } -------------------------------------------------------------------------------- /Shortener-Without-Lambdas/ShortURL-CUD-Mapping-Templates/shortURL/delete-response-400.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "message": "Either you do not have permission to do this operation or a parameter is missing" 4 | } 5 | } -------------------------------------------------------------------------------- /Shortener-Without-Lambdas/ShortURL-CUD-Mapping-Templates/shortURL/put-request.json: -------------------------------------------------------------------------------- 1 | { 2 | "TableName": "ShotenerTest", 3 | "Key": { 4 | "id": { 5 | "S": $input.json('$.id') 6 | } 7 | }, 8 | "ExpressionAttributeNames": { 9 | "#u": "url", 10 | "#owner": "owner", 11 | "#id": "id" 12 | }, 13 | "ExpressionAttributeValues": { 14 | ":u": { 15 | "S": $input.json('$.url') 16 | }, 17 | ":owner": { 18 | "S": $input.json('$.owner') 19 | }, 20 | ":linkId": { 21 | "S": "$input.params().path.linkId" 22 | } 23 | }, 24 | "UpdateExpression": "SET #u = :u", 25 | "ReturnValues": "ALL_NEW", 26 | "ConditionExpression": "#owner = :owner AND #id = :linkId" 27 | } -------------------------------------------------------------------------------- /Shortener-Without-Lambdas/ShortURL-CUD-Mapping-Templates/shortURL/put-response.json: -------------------------------------------------------------------------------- 1 | #set($inputRoot = $input.path('$')) 2 | { 3 | "id": "$inputRoot.Attributes.id.S", 4 | "url": "$inputRoot.Attributes.url.S", 5 | "owner": "$inputRoot.Attributes.owner.S" 6 | } --------------------------------------------------------------------------------