{"id":13752998,"url":"https://github.com/frangeris/pong","last_synced_at":"2025-04-14T02:31:05.637Z","repository":{"id":57248394,"uuid":"92560969","full_name":"frangeris/pong","owner":"frangeris","description":"🏓 Pong for RESTful APIs (microservices pattern) using Serverless Framework :zap:","archived":false,"fork":false,"pushed_at":"2018-10-19T14:39:52.000Z","size":263,"stargazers_count":29,"open_issues_count":12,"forks_count":4,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-27T16:21:59.002Z","etag":null,"topics":["api-gateway","api-generator","lambda-functions","microservice","restful-api","serverless-boilerplate","serverless-framework"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/frangeris.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-05-27T01:30:25.000Z","updated_at":"2024-11-08T15:04:37.000Z","dependencies_parsed_at":"2022-08-24T16:22:16.805Z","dependency_job_id":null,"html_url":"https://github.com/frangeris/pong","commit_stats":null,"previous_names":["frangeris/serverless-boilerplate"],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frangeris%2Fpong","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frangeris%2Fpong/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frangeris%2Fpong/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/frangeris%2Fpong/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/frangeris","download_url":"https://codeload.github.com/frangeris/pong/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248810883,"owners_count":21165195,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api-gateway","api-generator","lambda-functions","microservice","restful-api","serverless-boilerplate","serverless-framework"],"created_at":"2024-08-03T09:01:14.190Z","updated_at":"2025-04-14T02:31:05.357Z","avatar_url":"https://github.com/frangeris.png","language":"JavaScript","readme":"\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"https://cdn.rawgit.com/frangeris/pong/f389d356/boilerplate-logo.svg\" alt=\"Serverless RESTful Boilerplate\" width=\"500\" /\u003e\n\u003c/div\u003e\n\n# Pong for RESTful APIs (serverless microservices pattern)\n\n[![serverless](http://public.serverless.com/badges/v3.svg)](http://www.serverless.com)\n[![Build Status](https://travis-ci.org/frangeris/pong.svg?branch=master)](https://travis-ci.org/frangeris/pong)\n[![Dependency Status][daviddm-image]][daviddm-url]\n[![node](https://img.shields.io/badge/node-6.10-brightgreen.svg)](https://packagist.org/packages/frangeris/pong)\n[![Semantic Releases][semantic-release-badge]][semantic-release]\n[![PRs Welcome][prs-badge]][prs]\n\n[daviddm-image]: https://david-dm.org/frangeris/pong.svg?theme=shields.io\n[daviddm-url]: https://david-dm.org/frangeris/pong\n[semantic-release]: https://github.com/frangeris/pong/releases\n[semantic-release-badge]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg?style=flat\n[prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat\n[prs]: http://makeapullrequest.com\n\nBuild scalables RESTful apis under serverless arquitecture for AWS (serverless +v1.x), create a good codebase with scalability while the project grow up could require a lot of efford, time and dedication to know how the framework works, often this process of learning tends to be while we're building the product and this require agility and fast learning, customizing the code could be annoying a take more time than expected, that's the reason of this skeleton. \n\n## Why Pong?\nHave you play ping-pong? is a sport in which two players ([frontend and backend](https://en.wikipedia.org/wiki/Front_and_back_ends)) hit a lightweight ball ([HATEOAS](https://en.wikipedia.org/wiki/HATEOAS)) back and forth across a table ([Restful](https://en.wikipedia.org/wiki/Representational_state_transfer)) using small bats ([Vuejs](https://vuejs.org/) and [Serverless Framework](https://github.com/serverless/serverless)). \n\n## Requirements\n- [Serverless framework v1+](https://github.com/serverless/serverless)\n- [node.js](https://nodejs.org/)\n\n## Installation\n\nFirst, install [Yeoman](http://yeoman.io) and `generator-pong` using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/) and [serverless framework](https://github.com/serverless/serverless)).\n\n```bash\nnpm install -g yo\nnpm install -g generator-pong\n```\n\nThen generate your new project:\n\n```bash\nyo pong\n```\n\nGenerate new functions inside a project using CLI (recomended for overwrite `serverless.yml`):\n\n```bash\n$ yo pong:func\n? Path (can include parameters) /users/{uid}\n? Which HTTP method? GET\n? Your function description Get users by uid\n   create functions/users/id.js\n✔ GET /users/{uid} ► functions/users/id.handler (get-users-id)\n```\n\nFunctions also can be nested resources, just specify the url and `pong` will create the files and nested folders.\n\n```bash\n$ pwd\n/home/code/myproject\n\ndev @ ~/code/myproject\n$ yo pong:func\n? Path (can include parameters) /users/{uid}/orgs\n? Which HTTP method? GET\n? Your function description Get users orgs\n   create functions/users/orgs/get.js\n✔ GET /users/{uid}/orgs ► functions/users/orgs/get.handler (get-users-orgs)\n\ndev @ ~/code/myproject/functions/users\n$ ls\ntotal 16K\ntotal 16K\ndrwxr-xr-x 3 frang 4,0K feb 14 12:34 .\ndrwxr-xr-x 5 frang 4,0K feb 14 12:32 ..\n-rw-r--r-- 1 frang  268 feb 14 12:32 id.js\ndrwxr-xr-x 2 frang 4,0K feb 14 12:34 orgs \u003c-------------- Nested folder was created\n```\n\u003e The `func` subgenerator will save the path with parameters (if have)\n\n## New updates \u0026 new nice stuff here?\nThank's to [Yeoman](http://yeoman.io) :raised_hands: we have a [conflict handler](http://yeoman.io/generator/Conflicter.html) out-of-the-box.\n\n\u003e The Conflicter is a module that can be used to detect conflict between files. Each Generator file system helpers pass files through this module to make sure they don't break a user file.\n\nTo update a project with the latest features in the boilerplate just run `yo pong` inside the project, the generator must detect and automatically inform that an update will be made, the generator will only update global files, if you have modifed \"core\" files be careful while overwriting in updates.\n\n```bash\ndev @ ~/code/my-api\n$ yo pong\nProject detected, updating the boilerplate files instead...\n? Your project name (my-api)\n```\nResolve conflicts (in case that exists) and continue...\n\n*Note:* it will ask for some fields in case you want to update basic parameters in `serverless.yml`, in case nothing change, hit enter to use existing previous values.\n\n## Now, let's Rock n' roll\nThe project structure presented in this boilerplate is Microservices Pattern, where functionality is grouped primarily by function rather than resources. Each job or functionality is isolated within a separate Lambda function.\n\nIf you wish to read more about this pattern and comparation with others, please check out this awesome [writeup](https://serverless.com/blog/serverless-architecture-code-patterns/) by [Eslam Hefnawy](https://github.com/eahefnawy).\n\nThe basic project contains the following directory structure:\n```\n.\n├── __tests__\n├── .vscode\n│   ├── debug.js                    # Debugger for vscode \u003c3\n│   ├── event.json                  # Parameters for request when debugging\n│   └── launch.json\n├── functions\n│   └── aws-authorizer-jwt          # AWS authorizer for jwt tokens\n│       └── handler.js\n│   └── firebase-authorizer-jwt     # Firebase authorizer for jwt tokens\n│       └── handler.js\n│   └── myresource                  # Basic structure of a resource\n│       ├── id.js\n│       ├── get.js\n│       ├── post.js\n│       ├── put.js\n│       └── delete.js\n├── helpers\n│   ├── authpolicy.js\n│   ├── index.js\n│   └── response.js\n├── templates\n│   ├── request.vtl\n|   └── response.vtl\n├── tokens\n│   ├── aws.json                    # AWS jwks file\n│   └── firebase.json               # Firebase tokens\n├── .editorconfig\n├── .env.yml.example\n├── .gitignore\n├── LICENSE\n├── package.json\n├── README.md\n├── serverless.yml\n```\n\n## Concepts\n#### What is a service? (Api Gateway)\nDue to the current limitations where every service will create an individual API in API Gateway (WIP), we'll be working with a unique service with all the functions (resources) that will be exposed.\n\n#### How to use multiple environment stages (develop, prod, staging)?\nThe default stage is \"develop\", for create a new one, use the package `serverless-aws-alias` and change the value in `serverless.yml` or pass it as `--option` when deployment.\n\n#### Custom with Apache Velocity Templates\nTemplates are optionals, used **ONLY** when the integration is `lambda`, this method is more complicated and involves a lot more configuration of the http event syntax, [more info](https://serverless.com/framework/docs/providers/aws/events/apigateway/#lambda-integration).\n\nThe templates are defined as plain text, however you can also reference an external file with the help of the `${file(templates/response.vtl)}` syntax, use [Apache Velocity](http://velocity.apache.org/) syntax for custom. \n\n## Configuration files\n\n#### serverless.yml\nThe default provider is `aws`, see [documentation](https://serverless.com/framework/docs/providers/aws/guide/serverless.yml/) for complete list of options available.\n\n#### package.js (core required packages)\n- [yortus/asyncawait](https://github.com/yortus/asyncawait) for avoid [callback hell](http://callbackhell.com/) in validation helper.\n- [krachot/options-resolver](https://github.com/krachot/options-resolver) as port of Symfony component [OptionsResolver](http://symfony.com/doc/current/components/options_resolver.html)\n- [HyperBrain/serverless-aws-alias](https://github.com/HyperBrain/serverless-aws-alias) enables use of AWS aliases on Lambda functions.\n- [Brightspace/node-jwk-to-pem](https://github.com/Brightspace/node-jwk-to-pem) used to convert jwks to pem file.\n- [mzabriskie/axios](https://github.com/mzabriskie/axios) Awesome HTTP client for make request.\n\n#### .env.yml.example\nEnvironment variables used by your function, variables are grouped by stage, so this meas variables will only be available depending of the stage where you defined them, variables are loaded automatically, there is not need to \"require a file early as possible\", so copy the file **IN CASE IF NOT EXISTS (CREATED AUTOMATICALLY BY BOILERPLATE)** `.env.yml.example` to `.env.yml` and write the real values, depending the value for `stage` in `serverless.yml` file, values will be loaded, eg: \n\nCreate your final env vars file\n\n```sh\n$ cp .env.yml.example .env.yml\n```\n\nNow add the values per stage\n\n```yml\ndevelop:\n  AWS_SECRET_KEY: dontsavethiscredentialsstringsincode\n\nprod:\n  AWS_SECRET_KEY: dontsavethiscredentialsstringsincode\n```\n\nAnd access them natively in you code from `process.env`:\n\n```javascript\nmodule.exports.handler = (event, context, callback) =\u003e {\n    console.log(process.env.AWS_SECRET_KEY)\n}\n```\n\n\u003e `.env.yml.example` is added to VCS for keep reference of the variables, not values (good practice). `.env.yml` is not uploaded either aws when create the package or vcs.\n\n#### Some helpers...\nHelpers are just custom reusable functions for facilitate some repetitive tasks like validations, custom response, etc.\n\nHere the current availables:\n- `jwks-to-pem.js` if a helper file to convert AWS jwks json to pem file used in `aws-authorizer-jwt` function, eg: in root inside a project:\n```bash\n$ node ./helpers/jwks-to-pem.js \u003curl to jwks.json\u003e\n```\n\u003e `jwks.json` is usually located in `https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json`.\n\nThis will generate a json file with the pem keys in it, `aws-authorizer-jwt` use this file to authenticate using [JSON Web Tokens](https://jwt.io/) with [cognito integration](https://aws.amazon.com/blogs/mobile/integrating-amazon-cognito-user-pools-with-api-gateway/) for secure your API resources, [more info](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html).\n\nThe authorizer needs to khow the `iss` of the [token](https://tools.ietf.org/html/rfc7519#section-4.1.1), so add the value to `.env.yml` replacing the values of `region, userPoolId`, like this:\n```bash\ndevelop:\n  AWS_ISS: https://cognito-idp.{region}.amazonaws.com/{userPoolId}\n```\n**If a function needs to be secured using aws jwt authorizer**, remember to add it inside the function template in `serverless.yml` file, eg like this:\n```yml\n  get-users-orgs:\n    name: test-api-get-users-orgs\n    description: Get users orgs\n    handler: functions/users/orgs/get.handler\n    events:\n      - http:\n          path: '/users/{sub}/orgs'\n          method: GET\n          cors: true\n          authorizer: aws-authorizer-jwt\n```\nAnd that's it, API Gateway will run the [authorizer before the lambda execution](https://aws.amazon.com/blogs/compute/introducing-custom-authorizers-in-amazon-api-gateway/) automatically :dancer:\n\n- `validate()` this method return a `Promise` and throw an `Error` if the validation fails.\n- `response()` is a shorcut for the callback received in the lambda handler, but this add the json body for integration response in API Gateway at the same time implementing [JSON API](http://jsonapi.org) standard, eg:\n\nSamples of the `response` **using lambda-proxy integration**, [more info of integrations](https://serverless.com/framework/docs/providers/aws/events/apigateway).\n```javascript\nconst { response } = require('path/to/helpers')\n\nresponse(201)\n// {\"statusCode\": 201, \"body\": \"{\\\"data\\\":null}\",\"headers\": {}}\n\nresponse(403, new Error('my custom error message'))\n// {\"statusCode\": 403, \"body\": \"{\\\"error\\\":{\\\"title\\\":\\\"my custom error message\\\",\\\"meta\\\":{}}}\", \"headers\":{}}\n\nresponse(501, {key: 'value'})\n//{\"statusCode\": 501, \"body\": \"{\\\"data\\\":{\\\"key\\\":\\\"value\\\"}}\", \"headers\": {}}\n \nresponse(403)\n// {\"statusCode\": 403, \"body\": \"{\\\"data\\\":null}\", \"headers\": {}}\n\nresponse()\n// Error - Invalid arguments supplied for response\n```\n\n\u003e **[WIP]** Customization of header using the new response is not supported for now...\n\n- `resolver` just an `object` to interact with [krachot/options-resolver](https://github.com/krachot/options-resolver)\n**For use response helpers it's extremely required add this code at the very begining of the handler**, the reason is that `response` helper use the lambda `callback` function for finish the execution of the lambda and is not cool always send it by parameter...\n\n```javascript\nmodule.exports.handler = (event, context, callback) =\u003e {\n    // needed for response scope\n    global.cb = callback\n```\n\nThe other issue is related to request body, from [Serverless docs](https://serverless.com/framework/docs/providers/aws/events/apigateway/#simple-http-endpoint) and [AWS Developer Guide](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-set-up-simple-proxy.html#api-gateway-simple-proxy-for-lambda-input-format):\n\nNote: When the body is a JSON-Document, you must parse it yourself\n\n```json\n{\n    \"resource\": \"Resource path\",\n    \"path\": \"Path parameter\",\n    \"httpMethod\": \"Incoming request's method name\",\n    \"headers\": {},\n    \"queryStringParameters\": {},\n    \"pathParameters\":  {},\n    \"stageVariables\": {},\n    \"requestContext\": {},\n    \"body\": \"---------------A JSON STRING OF THE REQUEST PAYLOAD.-------------------\",\n    \"isBase64Encoded\": \"A boolean flag to indicate if the applicable request payload is Base64-encode\"\n}\n```\n\nThat means we must to parse the body received, in every functions, is not an object, is an string, so\n\n```javascript\nmodule.exports.handler = (event, context, callback) =\u003e {\n\n    // needed for response scope\n    global.cb = callback\n\n    // parse the body string to object\n    let body = JSON.parse(event.body)\n\n    ...\n```\n\nAfter adding the code bellow, just import the helper lib built-in and that's it... ^_^\n\n```javascript\nconst { validate, resolver, response } = require('../../helpers')\n\nmodule.exports.handler = (event, context, callback) =\u003e {\n    // needed for response scope\n    global.cb = callback\n\n    // parse the body string to object\n    let body = JSON.parse(event.body)\n\n    // marking as required some parameters\n    resolver.setRequired([\n        'email',\n        'password'\n    ])\n\n    // { email: 'tommy@powerrangers.com' }\n    validate(event)\n        // all good!\n        .then((body) =\u003e console.log('passed!'))\n        /*\n            400 Bad Request\n            {\"message\": \"The required options \\\"password\\\" are missing\"}\n        */\n        .catch((err) =\u003e response(err)) \n}\n```\n\n## Development\n\nInstall dependencies, Run test:\n\n``` bash\nnpm install\nnpm run test\n```\n\n## License\nThis boilerplate is open-sourced software licensed with **\u003c3** under the [MIT license](http://opensource.org/licenses/MIT) © [Frangeris Peguero](http://github.com/frangeris)\n","funding_links":[],"categories":["serverless-framework"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffrangeris%2Fpong","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffrangeris%2Fpong","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffrangeris%2Fpong/lists"}