{"id":13787190,"url":"https://github.com/rhwilr/adonis-bumblebee","last_synced_at":"2025-05-12T00:30:46.882Z","repository":{"id":28917156,"uuid":"119245820","full_name":"rhwilr/adonis-bumblebee","owner":"rhwilr","description":"Api Transformer for AdonisJs Framework","archived":false,"fork":false,"pushed_at":"2023-01-05T03:48:14.000Z","size":741,"stargazers_count":138,"open_issues_count":16,"forks_count":10,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-04-29T01:20:27.053Z","etag":null,"topics":["adonis","adonisjs","adonisjs-framework","api","api-transformer","serializer"],"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/rhwilr.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2018-01-28T09:45:09.000Z","updated_at":"2024-11-16T12:41:18.000Z","dependencies_parsed_at":"2023-01-14T13:45:37.731Z","dependency_job_id":null,"html_url":"https://github.com/rhwilr/adonis-bumblebee","commit_stats":null,"previous_names":[],"tags_count":30,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rhwilr%2Fadonis-bumblebee","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rhwilr%2Fadonis-bumblebee/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rhwilr%2Fadonis-bumblebee/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rhwilr%2Fadonis-bumblebee/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rhwilr","download_url":"https://codeload.github.com/rhwilr/adonis-bumblebee/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253655778,"owners_count":21943068,"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":["adonis","adonisjs","adonisjs-framework","api","api-transformer","serializer"],"created_at":"2024-08-03T20:00:31.102Z","updated_at":"2025-05-12T00:30:46.282Z","avatar_url":"https://github.com/rhwilr.png","language":"JavaScript","funding_links":[],"categories":["APIs"],"sub_categories":[],"readme":"# adonis-bumblebee\n\nAPI Transformer Provider for the AdonisJs framework. This library provides a\npresentation and transformation layer for complex data output.\n\n\u003cbr /\u003e\n\u003chr /\u003e\n\u003cbr /\u003e\n\n[![NPM Version][npm-image]][npm-url]\n[![Build Status][travis-image]][travis-url]\n[![Codecov][codecov-image]][codecov-url]\n\n[npm-image]: https://img.shields.io/npm/v/adonis-bumblebee.svg?style=flat-square\n[npm-url]: https://www.npmjs.com/package/adonis-bumblebee\n\n[travis-image]: https://img.shields.io/travis/rhwilr/adonis-bumblebee/master.svg?style=flat-square\n[travis-url]: https://travis-ci.org/rhwilr/adonis-bumblebee\n\n[codecov-image]: https://img.shields.io/codecov/c/github/rhwilr/adonis-bumblebee.svg?style=flat-square\n[codecov-url]: https://codecov.io/gh/rhwilr/adonis-bumblebee\n\n\n\n## Goals\n - Create a “barrier” between source data and output, so changes to your models\n   do not affect API response\n - Include relationships for complex data structures\n - Manipulate and typecast data in one place only\n\n\n\n## Table of contents\n\n  * [Installation](#installation)\n  * [Simple Example](#simple-example)\n  * [Resources](#resources)\n  * [Transformers](#transformers)\n    * [Transformer Classes](#transformer-classes)\n    * [Including Data](#including-data)\n        * [Default Include](#default-include)\n        * [Available Include](#available-include)\n        * [Parse available includes automatically](#parse-available-includes-automatically)\n    * [Transformer Variants](#transformer-variants)\n  * [EagerLoading](#eagerloading)\n  * [Serializers](#serializers)\n    * [PlainSerializer](#plainserializer)\n    * [DataSerializer](#dataserializer)\n    * [SLDataSerializer](#sldataserializer)\n  * [Pagination](#pagination)    \n  * [Fluent Interface](#fluent-interface)\n  * [Credits](#credits)\n\n\n\n## Installation\n\nRun this command to install the package and follow the instructions in\n[instructions.md](instructions.md).\n\n```sh\nadonis install adonis-bumblebee\n```\n\n\n\n## Simple Example\n\nFor the sake of simplicity, this example has been put together as one simple\nroute function. In reality, you would create dedicated Transformer classes for\neach model. But we will get there, let's first have a look at this:\n\n```js\nRoute.get('/', async ({ response, transform }) =\u003e {\n  const users = await User.all()\n\n  return transform.collection(users, user =\u003e ({\n    firstname: user.first_name,\n    lastname: user.last_name\n  }))\n})\n```\n\nYou may notice a few things here: First, we can import `transform` from the\ncontext, and then call a method `collection` on it. This method is called a\n[resources](#resources) and we will cover it in the next section. We pass our\ndata to this method along with a [transformer](#transformers). In return, we get\nthe transformed data back.\n\n\n\n## Resources\n\nResources are objects that represent data and have knowledge of a “Transformer”.\nThere are two types of resources:\n\n- **Item** - A singular resource, probably one entry in a data store\n- **Collection** - A collection of resources\n\nThe resource accepts an object or an array as the first argument, representing\nthe data that should be transformed. The second argument is the transformer used\nfor this resource.\n\n\n\n## Transformers\n\nThe simplest transformer you can write is a callback transformer. Just return an\nobject that maps your data.\n\n```js\nconst users = await User.all()\n\nreturn transform.collection(users, user =\u003e ({\n  firstname: user.first_name,\n  lastname: user.last_name\n}))\n```\n\nBut let's be honest, this is not what you want. And we would agree with you, so\nlet's have a look at transformer classes.\n\n\n### Transformer Classes\n\nThe recommended way to use transformers is to create a transformer class. This\nallows the transformer to be easily reused in multiple places.\n\n#### Creating a Transformer\n\nYou can let bumblebee generate the transformer for you by running:\n\n```sh\nadonis make:transformer User\n```\n\nThe command will create a new classfile in `app/Transformers/`. You can also\ncreate the class yourself, you just have to make sure that the class extends\n`Bumblebee/Transformer` and implements at least a `transform` method.\n\n```js\nconst BumblebeeTransformer = use('Bumblebee/Transformer')\n\nclass UserTransformer extends BumblebeeTransformer {\n  transform (model) {\n    return {\n      id: model.id,\n\n      firstname: model.first_name,\n      lastname: model.last_name\n    }\n  }\n}\n\nmodule.exports = UserTransformer\n```\n\n*Note:* You also get the `context` as the second argument in the `transform`\nmethod. Through this, you can access the current request or the authenticated\nuser.\n\n*Note:* A transformer can also return a primitive type, like a string or a\nnumber, instead of an object. But keep in mind that including additional data,\nas covered in the next section, only work when an object is returned.\n\n#### Using the Transformer\n\nOnce the transformer class is defined, it can be passed to the resource as the\nsecond argument.\n\n```js\nconst users = await User.all()\n\nreturn transform.collection(users, 'UserTransformer')\n```\n\nIf the transformer was placed in the default location `App/Transformers`, you\ncan reference it by just passing the name of the transformer. If you placed the\ntransformer class somewhere else or use a different path for your transformers,\nyou may have to pass the full namespace or change the default namespace in the\nconfig file. Lastly, you can also pass a reference to the transformer class\ndirectly.\n\n*Note:* Passing the transformer as the second argument will terminate the fluent\ninterface. If you want to chain more methods after the call to `collection` or\n`item` you should only pass the first argument and then use the `transformWith`\nmethod to define the transformer. See [Fluent Interface](#fluent-interface)\n\n\n### Including Data\n\nWhen transforming a model, you may want to include some additional data. For\nexample, you may have a book model and want to include the author for the book\nin the same resource. Include methods let you do just that. \n\n\n#### Default Include\n\nIncludes defined in the `defaultInclude` getter will always be included in the\nreturned data.\n\nYou have to specify the name of the include by returning an array of all\nincludes from the `defaultInclude` getter. Then you create an additional method\nfor each include, named like in the example: `include{Name}`.\n\nThe include method returns a new resource, that can either be an `item` or a\n`collection`.  See [Resources](#resources).\n\n```js\nclass BookTransformer extends BumblebeeTransformer {\n  static get defaultInclude () {\n    return [\n      'author'\n    ]\n  }\n\n  transform (book) {\n    return {\n      id: book.id,\n      title: book.title,\n      year: book.yr\n    }\n  }\n\n  includeAuthor (book) {\n    return this.item(book.getRelated('author'), AuthorTransformer)\n  }\n}\n\nmodule.exports = BookTransformer\n```\n\n*Note:* Just like in the transform method, you can also access to the `context`\nthrough the second argument.\n\n*Note:* If you want to use snake_case property names, you would still name the\ninclude function in camelCase, but list it under `defaultInclude` in snake_case.\n\n\n#### Available Include\n\nAn `availableInclude` is almost the same as a `defaultInclude`, except it is not\nincluded by default.\n\n```js\nclass BookTransformer extends BumblebeeTransformer {\n  static get availableInclude () {\n    return [\n      'author'\n    ]\n  }\n\n  transform (book) {\n    return {\n      id: book.id,\n      title: book.title,\n      year: book.yr\n    }\n  }\n\n  includeAuthor (book) {\n    return this.item(book.getRelated('author'), AuthorTransformer)\n  }\n}\n\nmodule.exports = BookTransformer\n```\n\nTo include this resource you call the `include()` method before transforming.\n\n```js\nreturn transform.include('author').item(book, BookTransformer)\n```\n\nThese includes can be nested with dot notation too, to include resources within\nother resources.\n\n```js\nreturn transform.include('author,publisher.something').item(book, BookTransformer)\n```\n\n\n#### Parse available includes automatically\n\nIn addition to the previously mentioned `include` method, you can also enable\n`parseRequest` in the config file. Now bumblebee will automatically parse the\n`?include=` GET parameter and include the requested resources.\n\n\n### Transformer Variants\n\nSometimes you may want to transform some model in a slitely different way while\nsill utilizing existing include methods. To use out book example, you may have\nan api endpoint that returns a list of all books, but you don't want to include\nthe summary of the book in this response to save on data. However, when\nrequesting a single book you want the summary to be included.\n\nYou could define a separate transformer for this, but it would be much easier if\nyou could reuse the existing book transformer. This is where transform variants\ncome in play.\n\n```js\nclass BookTransformer extends BumblebeeTransformer {\n  transform (book) {\n    return {\n      id: book.id,\n      title: book.title,\n      year: book.yr\n    }\n  }\n\n  transformWithSummary (book) {\n    return {\n      ...this.transform(book),\n      summary: book.summary\n    }\n  }\n}\n\nmodule.exports = BookTransformer\n```\n\nWe define a `transformWithSummary` method that calls our existing `transform`\nmethod and adds the book summary to the result.\n\nNow we can use this variant by specifing it as follows:\n\n```js\nreturn transform.collection(books, 'BookTransformer.withSummary')\n```\n\n\n\n## EagerLoading\n\nWhen you include additional models in your transformer be sure to eager load\nthese relations as this can quickly turn into n+1 database queries. If you have\ndefaultIncludes you should load them with your initial query. In addition,\nbumblebee will try to load related data if the include method is named the same\nas the relation.\n\nTo ensure the eager-loaded data is used, you should always use the\n`.getRelated()` method on the model.\n\n\n\n## Metadata\n\nSometimes you need to add just a little bit of extra information about your\nmodel or response. For these situations, we have the `meta` method. \n\n```js\nconst User = use('App/Models/User')\n\nconst users = await User.all()\n\nreturn transform\n  .meta({ \n    access: 'limited'\n  })\n  .collection(users, UserTransformer)\n```\n\nHow this data is added to the response is dependent on the\n[Serializer](#serializers).\n\n\n\n## Pagination\n\nWhen dealing with large amounts of data, it can be useful to paginate your API\nresponses to keep them lean. Adonis provides the `paginate` method on the query\nbuilder to do this on the database level. You can then pass the paginated models\nto the `paginate` method of bumblebee and your response will be transformed\naccordingly. The pagination information will be included under the `pagination`\nnamespace.\n\n```js\nconst User = use('App/Models/User')\nconst page = 1\n\nconst users = await User.query().paginate(page)\n\nreturn transform.paginate(users, UserTransformer)\n```\n\n\n\n## Serializers\n\nAfter your data passed through the transformer, it has to pass through one more\nlayer. The `Serializer` will form your data into its final structure. The\ndefault serializer is the `PlainSerializer` but you can change this in the\nsettings. For smaller APIs, the PlainSerializer works fine, but for larger\nprojects, you should consider the `DataSerializer`.\n\n\n### PlainSerializer\n\nThis is the simplest serializer. It does not add any namespaces to the\ndata. It is also compatible with the default structure that you get when you\nreturn a lucid model from a route.\n\n```js\n// Item\n{\n  foo: 'bar'\n}\n\n// Collection\n[\n  {\n    foo: bar\n  },\n  {...}\n]\n```\n\nThere is one major drawback to this serializer. It does not play nicely with metadata:\n\n```js\n// Item with meta\n{\n  foo: 'bar',\n  meta: {\n    ...\n  }\n}\n\n// Collection\n{\n  data: [\n    {...}\n  ],\n  meta: {\n    ...\n  }\n}\n```\n\nSince you cannot mix an Array and Objects in JSON, the serializer has to add a\n`data` property if you use metadata on a collection. The same is true if you use\npagination. This is why we do not recommend using `PlainSerializer` when using\nthese features. But other than that, this serializer works great for small and\nsimple APIs.\n\n\n### DataSerializer\n\nThis serializer adds the `data` namespace to all of its items:\n\n```js\n// Item\n{\n  data: {\n    foo: 'bar',\n    included: {\n      data: {\n        name: 'test'\n      }\n    }\n  }\n}\n\n// Collection\n{\n  data: [\n    {\n      foo: bar\n    },\n    {...}\n  ]\n}\n```\n\nThe advantage over the `PlainSerializer` is that it does not conflict with meta\nand pagination:\n\n```js\n// Item with meta\n{\n  data: {\n    foo: 'bar'\n  },\n  meta: {\n    ...\n  }\n}\n\n// Collection\n{\n  data: [\n    {...}\n  ],\n  meta: {...},\n  pagination: {...}\n}\n```\n\n\n### SLDataSerializer\n\nThis serializer works similarly to the DataSerializer, but it only adds the\n`data` namespace on the first level.\n\n```js\n// Item\n{\n  data: {\n    foo: 'bar',\n    included: {\n      name: 'test'\n    }\n  }\n}\n```\n\n\n\n## Fluent Interface\n\nBumblebee has a fluent interface for all the setter methods. This means you can\nchain method calls which makes the API more readable. The following methods are\navailable on the `transform` object in the context and from `Bumblebee.create()`\n(see below).\n\n**Chainable methods:**\n- `collection(data)`\n- `item(data)`\n- `null(data)`\n- `paginate(data)`\n- `meta(metadata)`\n- `transformWith(transformer)`\n- `usingVariant(variant)`\n- `withContext(ctx)`\n- `include(include)`\n- `setSerializer(serializer)`\n- `serializeWith(serializer)` (alias for `setSerializer`)\n\n**Terminating methods:**\n- `collection(data, transformer)`\n- `item(data, transformer)`\n- `paginate(data, transformer)`\n- `toJSON()`\n\n\nYou may want to use the transformer somewhere other than in a controller. You\ncan import bumblebee directly by the following method:\n\n```js\nconst Bumblebee = use('Adonis/Addons/Bumblebee')\n\nlet transformed = await Bumblebee.create()\n    .collection(data)\n    .transformWith(BookTransformer)\n    .withContext(ctx)\n    .toJSON()\n```\n\nYou can use the same methods as in a controller. With one difference: If you\nneed the `context` inside the transformer, you have to set it with the\n`.withContext(ctx)` method since it is not automatically injected.\n\n\n\n## Credits\n\nSpecial thanks to the creator(s) of [Fractal], a PHP API transformer that was\nthe main inspiration for this package. Also, a huge thank goes to the creator(s)\nof [AdonisJS] for creating such an awesome framework.\n\n[Fractal]: https://fractal.thephpleague.com\n[AdonisJS]: http://adonisjs.com\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frhwilr%2Fadonis-bumblebee","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frhwilr%2Fadonis-bumblebee","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frhwilr%2Fadonis-bumblebee/lists"}