{"id":15650837,"url":"https://github.com/soyuka/rxrest","last_synced_at":"2025-08-20T08:20:28.497Z","repository":{"id":66040712,"uuid":"71991377","full_name":"soyuka/rxrest","owner":"soyuka","description":"Reactive rest library","archived":false,"fork":false,"pushed_at":"2018-06-08T07:56:55.000Z","size":2634,"stargazers_count":34,"open_issues_count":0,"forks_count":4,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-04-23T10:52:08.648Z","etag":null,"topics":["api","fetch","http","observable","reactive","request","rxjs"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/soyuka.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}},"created_at":"2016-10-26T10:13:15.000Z","updated_at":"2024-05-05T23:54:02.000Z","dependencies_parsed_at":null,"dependency_job_id":"9bac74de-6487-4d10-9582-880be16ec889","html_url":"https://github.com/soyuka/rxrest","commit_stats":{"total_commits":196,"total_committers":5,"mean_commits":39.2,"dds":0.1071428571428571,"last_synced_commit":"97030b0935ac0415bdc02277d512ed094ba05301"},"previous_names":[],"tags_count":72,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soyuka%2Frxrest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soyuka%2Frxrest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soyuka%2Frxrest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soyuka%2Frxrest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/soyuka","download_url":"https://codeload.github.com/soyuka/rxrest/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251752589,"owners_count":21638137,"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","fetch","http","observable","reactive","request","rxjs"],"created_at":"2024-10-03T12:36:01.722Z","updated_at":"2025-04-30T17:40:50.363Z","avatar_url":"https://github.com/soyuka.png","language":"JavaScript","readme":"RxRest [![Build Status](https://travis-ci.org/soyuka/rxrest.svg?branch=master)](https://travis-ci.org/soyuka/rxrest)\n======\n\n\u003e A reactive REST utility\n\nHighly inspirated by [Restangular](https://github.com/mgonto/restangular), this library implements a natural way to interact with a REST API.\n\n## Install\n\n```\nnpm install rxrest --save\n```\n\n## Example\n\n```javascript\nimport { RxRest, RxRestConfig } from 'rxrest'\n\nconst config = new RxRestConfig()\nconfig.baseURL = 'http://localhost/api'\n\nconst rxrest = new RxRest(config)\nrxrest.all('cars')\n.get()\n.subscribe((cars: Car[]) =\u003e {\n  /**\n   * `cars` is:\n   * RxRestCollection [\n   *   RxRestItem { name: 'Polo', id: 1, brand: 'Audi' },\n   *   RxRestItem { name: 'Golf', id: 2, brand: 'Volkswagen' }\n   * ]\n   */\n\n  cars[0].brand = 'Volkswagen'\n\n  cars[0].save()\n  .subscribe(result =\u003e {\n    console.log(result)\n    /**\n     * outputs: RxRestItem { name: 'Polo', id: 1, brand: 'Volkswagen' }\n     */\n  })\n})\n```\n\n## Menu\n\n-  [Technical concepts](#technical-concepts)\n-  [Promise compatibility](#promise-compatibility)\n-  [One-event Stream instead of multiple events](#one-event-stream-instead-of-multiple-events)\n-  [Object state (`$fromServer`, `$pristine`, `$uuid`)](#object-state-fromserver-pristine-uuid)\n-  [Configuration](#configuration)\n-  [Interceptors](#interceptors)\n-  [Handlers](#handlers)\n-  [API](#api)\n-  [Typings](#typings)\n-  [Angular 2 configuration example](#angular-2-configuration-example)\n\n## Technical concepts\n\nThis library uses a [`fetch`-like](https://developer.mozilla.org/en-US/docs/Web/API/GlobalFetch) library to perform HTTP requests. It has the same api as fetch but uses XMLHttpRequest so that requests have a cancellable ability! It also makes use of [`Proxy`](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Proxy) and implements an [`Iterator`](https://developer.mozilla.org/fr/docs/Web/JavaScript/Guide/iterateurs_et_generateurs) on `RxRestCollection`.\n\nBecause it uses fetch, the RxRest library uses it's core concepts. It will add an `Object` compatibility layer to [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/URLSearchParams) for query parameters and [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers).\nIt is also familiar with `Body`-like object, as `FormData`, `Response`, `Request` etc.\n\nThis script depends on `superagent` (for a easier XMLHttpRequest usage, compatible in both node and the browser) and `rxjs` for the reactive part.\n\n\u003csup\u003e[^ Back to menu](#menu)\u003c/sup\u003e\n\n## Promise compatibility\n\nJust use the `toPromise` utility:\n\n```javascript\n\nrxrest.one('foo')\n.get()\n.toPromise()\n.then(item =\u003e {\n  console.log(item)\n})\n```\n\n\u003csup\u003e[^ Back to menu](#menu)\u003c/sup\u003e\n## One-event Stream instead of multiple events\n\nSometimes, you may want RxRest to emit one event per item in the collection:\n\nTo do so, just call `asIterable(false)`:\n\n```javascript\nrxrest.all('cars')\n.asIterable(false)\n.get()\n// next() is called with every car available\n.subscribe((e) =\u003e {})\n```\n\nOr use the second argument of `.all` instead of `asIterable`:\n\n```javascript\nrxrest.all('cars', false)\n.get()\n// next() is called with every car available\n.subscribe((e) =\u003e {})\n```\n\n## Object state (`$fromServer`, `$pristine`, `$uuid`)\n\nThanks to the Proxy, we can get metadata informations about the current object and it's state.\n\nWhen you instantiate an object, it's `$pristine`. When it gets modified it's dirty:\n\n```javascript\nconst rxrest = new RxRest()\nconst car = rxrest.one('cars', 1)\n\nassert(car.$prisine === true)\n\ncar.brand = 'Ford'\n\nassert(car.$prisine === false)\n```\n\nYou can also check that the item comes from the server:\n\n```javascript\nconst rxrest = new RxRest()\nconst car = rxrest.one('cars', 1)\n\nassert(car.$fromServer === false) // we just instantiated it in the client\n\ncar.save()\n.subscribe((car) =\u003e {\n  assert(car.$fromServer === true) //now it's from the server\n  assert(car.$prisine === true) //it's also pristine!\n})\n```\n\n\u003csup\u003e[^ Back to menu](#menu)\u003c/sup\u003e\n## Configuration\n\nSetting up `RxRest` is done via `RxRestConfiguration`:\n\n```javascript\nconst config = new RxRestConfiguration()\n```\n\n#### `baseURL`\n\nIt is the base url prepending your routes. For example :\n\n```javascript\n//set the url\nconfig.baseURL = 'http://localhost/api'\n\nconst rxrest = new RxRest(config)\n//this will request GET http://localhost/api/cars/1\nrxrest.one('cars', 1)\n.get()\n```\n\n#### `identifier='id'`\n\nThis is the key storing your identifier in your api objects. It defaults to `id`.\n\n```javascript\nconfig.identifier = '@id'\n\nconst rxrest = new RxRest(config)\nrxrest.one('cars', 1)\n\n\u003e RxRestItem { '@id': 1 }\n```\n\n#### `headers`\n\nYou can set headers through the configuration, but also change them request-wise:\n\n```javascript\nconfig.headers\nconfig.headers.set('Authorization', 'foobar')\nconfig.headers.set('Content-Type', 'application/json')\n\nconst rxrest = new RxRest(config)\n\n// Performs a GET request on /cars/1 with Authorization and an `application/json` content type header\nrxrest.one('cars', 1).get()\n\n// Performs a POST request on /cars with Authorization and an `application/x-www-form-urlencoded` content type header\nrxrest.all('cars')\n.post(new FormData(), null, {'Content-Type': 'application/x-www-form-urlencoded'})\n```\n\n#### `queryParams`\n\nYou can set query parameters through the configuration, but also change them request-wise:\n\n```javascript\nconfig.queryParams.set('bearer', 'foobar')\n\nconst rxrest = new RxRest(config)\n\n// Performs a GET request on /cars/1?bearer=foobar\nrxrest.one('cars', 1).get()\n\n// Performs a GET request on /cars?bearer=barfoo\nrxrest.all('cars')\n.get({bearer: 'barfoo'})\n```\n\n#### `uuid`\n\nIt tells RxRest to add an uuid to every resource. This is great if you need a unique identifier that's not related to the data of a collection (useful in forms):\n\n```javascript\n//set the url\nconfig.uuid = true\n\nconst rxrest = new RxRest(config)\nrxrest.one('cars', 1)\n.get()\n.subscribe((car: Car) =\u003e {\n  console.log(car.$uuid)\n})\n```\n\nAlso works in a non-`$fromServer` resource:\n\n```\nconst car = rxrest.fromObject('cars')\nconsole.log(car.$uuid)\n```\n\n\u003csup\u003e[^ Back to menu](#menu)\u003c/sup\u003e\n\n## Interceptors\n\nYou can add custom behaviors on every state of the request. In order those are:\n  1. Request\n  2. Response\n  3. Error\n\nTo alter those states, you can add interceptors having the following signature:\n  1. `requestInterceptor(request: Request)`\n  2. `responseInterceptor(request: Body)`\n  3. `errorInterceptor(error: Response)`\n\nEach of those can return a Stream, a Promise, their initial altered value, or be void (ie: return nothing).\n\nFor example, let's alter the request and the response:\n\n```javascript\nconfig.requestInterceptors.push(function(request) {\n  request.headers.set('foo', 'bar')\n})\n\n// This alters the body (note that ResponseBodyHandler below is more appropriate to do so)\nconfig.responseInterceptors.push(function(response) {\n  return response.text(\n  .then(data =\u003e {\n    data = JSON.parse(data)\n    data.foo = 'bar'\n    //We can read the body only once (see Body.bodyUsed), here we return a new Response\n    return new Response(JSON.stringify(body), response)\n  })\n})\n\n// Performs a GET request with a 'foo' header having `bar` as value\nconst rxrest = new RxRest(config)\n\nrxrest.one('cars', 1)\n.get()\n\n\u003e RxRestItem\u003cCar\u003e {id: 1, brand: 'Volkswagen', name: 'Polo', foo: 1}\n```\n\n\u003csup\u003e[^ Back to menu](#menu)\u003c/sup\u003e\n\n## Handlers\n\nHandlers allow you to transform the Body before or after a request is issued.\n\nThose are the default values:\n\n```javascript\n/**\n * This method transforms the requested body to a json string\n */\nconfig.requestBodyHandler = function(body) {\n  if (!body) {\n    return undefined\n  }\n\n  if (body instanceof FormData || body instanceof URLSearchParams) {\n    return body\n  }\n\n  return body instanceof RxRestItem ? body.json() : JSON.stringify(body)\n}\n\n/**\n * This transforms the response in an Object (ie JSON.parse on the body text)\n * should return Promise\u003c{body: any, metadata: any}\u003e\n */\nconfig.responseBodyHandler = function(body) {\n  return body.text()\n  .then(text =\u003e {\n    return {body: text ? JSON.parse(text) : null, metadata: null}\n  })\n}\n```\n\nIn the `responseBodyHandler`, you can note that we're returning an object containing:\n\n1. `body` - the javascript Object or Array that will be transformed in a RxRestItem or RxRestCollection\n2. `metadata` - an API request sometimes gives us metadata (for example pagination metadata), add it here to be able to retrieve `item.$metadata` later\n\n\u003csup\u003e[^ Back to menu](#menu)\u003c/sup\u003e\n\n## API\n\nThere are two prototypes:\n  - RxRestItem\n  - RxRestCollection - an iterable collection of RxRestItem\n\n### Available on both RxRestItem and RxRestCollection\n\n#### `one(route: string, id: any): RxRestItem`\n\nCreates an RxRestItem on the requested route.\n\n#### `all(route: string, asIterable: boolean = false): RxRestCollection`\n\nCreates an RxRestCollection on the requested route\n\nNote that this allows url composition:\n\n```javascript\nrxrest.all('cars').one('audi', 1).URL\n\n\u003e cars/audi/1\n```\n\n#### `fromObject(route: string, element: Object|Object[]): RxRestItem|RxRestCollection`\n\nDepending on whether element is an `Object` or an `Array`, it returns an RxRestItem or an RxRestCollection.\n\nFor example:\n\n```javascript\nconst car = rxrest.fromObject('cars', {id: 1, brand: 'Volkswagen', name: 'Polo'})\n\n\u003e RxRestItem\u003cCar\u003e {id: 1, brand: 'Volkswagen', name: 'Polo'}\n\ncar.URL\n\n\u003e cars/1\n```\n\nRxRest automagically binds the id in the route, note that the identifier property is configurable.\n\n#### `get(queryParams?: Object|URLSearchParams, headers?: Object|Headers): Stream\u003cRxRestItem|RxRestCollection\u003e`\n\nPerforms a `GET` request, for example:\n\n```javascript\nrxrest.one('cars', 1).get({brand: 'Volkswagen'})\n.subscribe(e =\u003e console.log(e))\n\nGET /cars/1?brand=Volkswagen\n\n\u003e RxRestItem\u003cCar\u003e {id: 1, brand: 'Volkswagen', name: 'Polo'}\n```\n\n#### `post(body?: BodyParam, queryParams?: Object|URLSearchParams, headers?: Object|Headers): Stream\u003cRxRestItem|RxRestCollection\u003e`\n\nPerforms a `POST` request, for example:\n\n```javascript\nconst car = new Car({brand: 'Audi', name: 'A3'})\nrxrest.all('cars').post(car)\n.subscribe(e =\u003e console.log(e))\n\n\u003e RxRestItem\u003cCar\u003e {id: 3, brand: 'Audi', name: 'A3'}\n```\n\n#### `remove(queryParams?: Object|URLSearchParams, headers?: Object|Headers): Stream\u003cRxRestItem|RxRestCollection\u003e`\n\nPerforms a `DELETE` request\n\n#### `patch(body?: BodyParam, queryParams?: Object|URLSearchParams, headers?: Object|Headers): Stream\u003cRxRestItem|RxRestCollection\u003e`\n\nPerforms a `PATCH` request\n\n#### `head(queryParams?: Object|URLSearchParams, headers?: Object|Headers): Stream\u003cRxRestItem|RxRestCollection\u003e`\n\nPerforms a `HEAD` request\n\n#### `trace(queryParams?: Object|URLSearchParams, headers?: Object|Headers): Stream\u003cRxRestItem|RxRestCollection\u003e`\n\nPerforms a `TRACE` request\n\n#### `request(method: string, body?: BodyParam): Stream\u003cRxRestItem|RxRestCollection\u003e`\n\nThis is useful when you need to do a custom request, note that we're adding query parameters and headers\n\n```javascript\nrxrest.all('cars/1/audi')\n.setQueryParams({foo: 'bar'})\n.setHeaders({'Content-Type': 'application/x-www-form-urlencoded'})\n.request('GET')\n```\n\nThis will do a `GET` request on `cars/1/audi?foo=bar` with a `Content-Type` header having a `application/x-www-form-urlencoded` value.\n\n#### `json(): string`\n\nOutput a `JSON` string of your RxRest element.\n\n```javascript\nrxrest.one('cars', 1)\n.get()\n.subscribe((e: RxRestItem\u003cCar\u003e) =\u003e console.log(e.json()))\n\n\u003e {id: 1, brand: 'Volkswagen', name: 'Polo'}\n```\n\n#### `plain(): Object|Object[]`\n\nThis gives you the original object (ie: not an instance of RxRestItem or RxRestCollection):\n\n```javascript\nrxrest.one('cars', 1)\n.get()\n.subscribe((e: RxRestItem\u003cCar\u003e) =\u003e console.log(e.plain()))\n\n\u003e {id: 1, brand: 'Volkswagen', name: 'Polo'}\n```\n\n#### `clone(): RxRestItem|RxRestCollection`\n\nClones the current instance to a new one.\n\n### RxRestCollection\n\n#### `getList(): Stream\u003cRxRestCollection\u003e`\n\nJust a reference to Restangular ;). It's an alias to `get()`.\n\n### RxRestItem\n\n#### `save(): RxRestCollection`\n\nDo a `POST` or a `PUT` request according to whether the resource came from the server or not. This is due to an internal property `fromServer`, which is set when parsing the request result.\n\n\u003csup\u003e[^ Back to menu](#menu)\u003c/sup\u003e\n\n## Typings\n\nInterfaces:\n\n```typescript\nimport { RxRest, RxRestItem, RxRestConfig } from 'rxrest';\n\nconst config = new RxRestConfig()\nconfig.baseURL = 'http://localhost'\n\ninterface Car {\n  id: number;\n  name: string;\n  model: string;\n}\n\nconst rxrest = new RxRest(config)\n\nrxrest.one\u003cCar\u003e('/cars', 1)\n.get()\n.subscribe((item: Car) =\u003e {\n  console.log(item.model)\n  item.model = 'audi'\n\n  item.save()\n})\n```\n\nIf you work with [Hypermedia-Driven Web APIs (Hydra)](http://www.markus-lanthaler.com/hydra/), you can extend a default typing for you items to avoid repetitions:\n\n```typescript\ninterface HydraItem\u003cT\u003e {\n  '@id': string;\n  '@context': string;\n  '@type': string;\n}\n\ninterface Car extends HydraItem\u003cCar\u003e {\n  name: string;\n  model: Model;\n  color: string;\n}\n\ninterface Model extends HydraItem\u003cModel\u003e {\n  name: string;\n}\n```\n\nTo know more about typings and rxrest, please check out [the typings example](https://github.com/soyuka/rxrest/blob/master/test/typings.ts).\n\n\u003csup\u003e[^ Back to menu](#menu)\u003c/sup\u003e\n\n## Angular 2 configuration example\n\nFirst, let's declare our providers:\n\n```typescript\nimport { Injectable, NgModule, Component, OnInit } from '@angular/core'\nimport { RxRest, RxRestConfiguration } from 'rxrest'\n\n@Injectable()\nexport class AngularRxRestConfiguration extends RxRestConfiguration {\n  constructor() {\n    super()\n    this.baseURL = 'localhost/api'\n  }\n}\n\n@Injectable()\nexport class AngularRxRest extends RxRest {\n  constructor(config: RxRestConfiguration) {\n    super(config)\n  }\n}\n\n@NgModule({\n  providers: [\n    {provide: RxRest, useClass: AngularRxRest},\n    {provide: RxRestConfiguration, useClass: AngularRxRestConfiguration},\n  ]\n})\nexport class SomeModule {\n}\n\n```\n\nThen, just inject `RxRest`:\n\n```typescript\nexport interface Car {\n  name: string\n}\n\n@Component({\n  template: '\u003cul\u003e\u003cli *ngFor=\"let car of cars | async\"\u003e{{car.name}}\u003c/li\u003e\u003c/ul\u003e'\n})\nexport class FooComponent implements OnInit {\n  constructor(private rxrest: RxRest) {\n  }\n\n  ngOnInit() {\n    this.cars = this.rxrest.all\u003cCar\u003e('cars', true).get()\n  }\n}\n```\n\n[Full example featuring jwt authentication, errors handling, body parsers for JSON-LD](https://gist.github.com/soyuka/c2e89ebf3c7a33f8d059c567aefd471c)\n\n\u003csup\u003e[^ Back to menu](#menu)\u003c/sup\u003e\n\n## Test\n\nTesting can be done using [`rxrest-assert`](https://github.com/soyuka/rxrest-assert).\n\n## Licence\n\nMIT\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoyuka%2Frxrest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsoyuka%2Frxrest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoyuka%2Frxrest/lists"}