{"id":50993991,"url":"https://github.com/beyonk/hapi-view-models","last_synced_at":"2026-06-20T06:39:32.207Z","repository":{"id":66169674,"uuid":"114241089","full_name":"beyonk/hapi-view-models","owner":"beyonk","description":"View model support for hapi","archived":false,"fork":false,"pushed_at":"2017-10-05T11:36:08.000Z","size":31,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-03-07T15:51:39.036Z","etag":null,"topics":["hapi","hapi-api","hapi-plugin","hapijs","view-model"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/beyonk.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2017-12-14T11:14:10.000Z","updated_at":"2025-07-09T01:24:58.000Z","dependencies_parsed_at":null,"dependency_job_id":"df966b27-4a26-4fa4-a306-f30d3388cb6d","html_url":"https://github.com/beyonk/hapi-view-models","commit_stats":null,"previous_names":["beyonk-group/hapi-view-models","beyonk/hapi-view-models"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/beyonk/hapi-view-models","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beyonk%2Fhapi-view-models","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beyonk%2Fhapi-view-models/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beyonk%2Fhapi-view-models/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beyonk%2Fhapi-view-models/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/beyonk","download_url":"https://codeload.github.com/beyonk/hapi-view-models/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beyonk%2Fhapi-view-models/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34560266,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-20T02:00:06.407Z","response_time":98,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["hapi","hapi-api","hapi-plugin","hapijs","view-model"],"created_at":"2026-06-20T06:39:31.161Z","updated_at":"2026-06-20T06:39:32.198Z","avatar_url":"https://github.com/beyonk.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## hapi View Models\n\n[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)\n[![Build Status](https://circleci.com/gh/vendigo-group/hapi-view-models.png)](https://circleci.com)\n[![Vendigo Open Source](https://img.shields.io/badge/vendigo-oss-brightgreen.svg)](http://github.com/vendigo-group)\n\nA plugin to provide a concept of 'view-models' to [hapi](https://hapijs.com).\n\n### Purpose\n\nWhen rendering payloads from an API, we often want to provide different subsets (or supersets) of data to users with different *roles*, or *scopes*. By leveraging `lodash.omit` and `server.decorate`, we can contain the complexity of doing this as a fairly neat abstraction, reducing the overall complexity of payload modification and filtering, and keeping our controllers clean.\n\nWith `hapi-view-models` it is possible to render a variety of different versions of a single entity (payload) from a single endpoint.\n\n### Installing\n\nInstalling is done in the usual way\n\n```bash\nnpm install hapi-view-models\n```\n\n### Usage\n\nThe `vm` reply helper allows you to render views of your data:\n\n```javascript\nconst KeyPairViewModel = require('...')\nserver.route({\n  ...\n  handler (request, reply) {\n    reply.vm(KeyPairViewModel, response)\n  }\n})\n```\n\nWhere vm takes the KeyPairViewModel and filters the data inside response according to the user's scope. Response can be a single entity or an array of entities.\n\nIf you're returning a response envelope you can provide a path to the entity as a third argument to the vm reply helper.\n\n```javascript\nconst KeyPairViewModel = require('...')\nserver.route({\n  ...\n  handler (request, reply) {\n    reply.vm(KeyPairViewModel, {\n      nested: {\n        data: [...]\n      }\n    }, 'nested.data')\n  }\n})\n```\n\n### Real World Example\n\nSuppose we owned a cryptocurrency exchange. That's very 'of the moment', isn't it?\n\nLets say our wallet owner has a role of 'owner', and a visitor doesn't have this role.\n\nWe'd want to build a view model to render a wallet's data in a secure way. We declare a set of properties that are included in the payload for each role. Including a property in a role automatically **excludes that data from all other roles**.\n\nRoles are defined by hapi in `request.auth.credentials.scope` and are controlled by your auth mechanism.\n\nOur view model extends ViewModel and looks like this:\n\n```javascript\nconst { ViewModel } = require('hapi-view-models')\n\nclass KeyPairViewModel extends ViewModel {\n    get includes () {\n      return {\n        owner: ['private']\n      }\n    }\n}\n```\n\nAnd our handler would look something like this:\n\n```javascript\nconst { plugin } = require('hapi-view-models')\nconst KeyPairViewModel = require('path/to/key-pair-view-model')\n\n// Register the plugin with hapi\nserver.register(plugin, err =\u003e {\n  assert.ifError(err)\n})\n\nfunction getWalletKeys (address) {\n  // Fetched from a database, more than likley.\n  const keyPair = {\n    private: '0x000',\n    public: '0xaaa'\n  }\n}\n\nserver.route({\n  method: 'GET',\n  path: '/wallet/{address}',\n  handler: function (request, reply) {\n    const keyPair = getWalletKeys(request.params.address)\n    reply.vm(KeyPairViewModel, keyPair)\n  }\n})\n```\n\nThe rendered data would look something like this:\n\n```javascript\n// If the owner requests the wallet\n{\n  private: '0x000',\n  public: '0xaaa'\n}\n\n// But if a visitor requests the wallet\n{\n  public: '0xaaa'\n}\n```\n\n### Understanding `get includes()`\n\nThe getter method `get includes()` or just `.includes` as a property on your view model is a mapping of `scope` to an array of properties that are (deep) filtered from the resultant payload. This means we also support nesting!\n\nThis means you can do:\n\n```javascript\nconst data = {\n  a: {\n    foo: 'bar'\n  },\n  b: {\n    e: 'stuff here',\n    c: {\n      d: 'some-data'\n    }\n  }\n}\n\nclass SomeViewModel extends ViewModel {\n    get includes () {\n      return {\n        role1: ['a'], // Role 1 can see property 'a'\n        role2: ['a', 'b'] // Role 2 can see property 'a' and 'b'\n        role3: ['b.c.d'] // Without this role, you can see b and b.e, but the contents of b.c will be '{}' as 'b.c.d' is hidden.\n      }\n    }\n}\n```\n\nYou'll notice something going on here with includes. Includes is an 'exclusive' mapping. This means that if you declare a property visible by a role, **it is hidden for all other roles**. This means that you can minimally hide role-sensitive data, without having to re-iterate yourself or risk the leak of private properties leaking through.\n\nTL;DR: Once a property is declared as *visible* to a role, it is automatically *invisible* to all other roles.\n\nIdeally your user should have all the roles it needs to see all the data it needs, but if you like, you can 're-declare' visibility as we have done in the ase of `role2` above. A user wanting to see `a` can have roles `role1`, `role2`, or both. `a` (and as a result `a.foo`) isn't visible to any other roles.\n\n### Structure\n\nThe plugin exports two modules:\n\n  * `plugin` which is the `hapi` plugin providing `reply.vm()`\n  * `ViewModel` which is the base class your view models should extend.\n\nThe plugin uses a slightly stricter extension of [standard-style](https://standardjs.com/)\n\n### Contributing\n\nTo contribute to the plugin, fork it to your own github account, create a branch, make your changes, and submit a PR.\n\nNote that Vendigo Finance Ltd requires that you include tests to cover your new code, along with your PR in order to get it merged.\n\nTo run our test suite:\n\n```bash\nnpm install\nnpm test\nnpm run lint\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbeyonk%2Fhapi-view-models","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbeyonk%2Fhapi-view-models","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbeyonk%2Fhapi-view-models/lists"}