{"id":13499255,"url":"https://github.com/relay-tools/react-relay-network-layer","last_synced_at":"2026-01-03T08:09:41.020Z","repository":{"id":57104539,"uuid":"56881165","full_name":"relay-tools/react-relay-network-layer","owner":"relay-tools","description":"ReactRelayNetworkLayer with middlewares and query batching for Relay Classic.","archived":false,"fork":false,"pushed_at":"2018-11-03T15:18:26.000Z","size":354,"stargazers_count":276,"open_issues_count":9,"forks_count":47,"subscribers_count":13,"default_branch":"master","last_synced_at":"2024-10-29T18:05:50.223Z","etag":null,"topics":["batch-request","graphql-client","relay","relay-network-layer"],"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/relay-tools.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-04-22T19:47:21.000Z","updated_at":"2024-08-30T02:31:15.000Z","dependencies_parsed_at":"2022-08-20T17:11:03.281Z","dependency_job_id":null,"html_url":"https://github.com/relay-tools/react-relay-network-layer","commit_stats":null,"previous_names":["nodkz/react-relay-network-layer"],"tags_count":30,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/relay-tools%2Freact-relay-network-layer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/relay-tools%2Freact-relay-network-layer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/relay-tools%2Freact-relay-network-layer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/relay-tools%2Freact-relay-network-layer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/relay-tools","download_url":"https://codeload.github.com/relay-tools/react-relay-network-layer/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248018060,"owners_count":21034048,"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":["batch-request","graphql-client","relay","relay-network-layer"],"created_at":"2024-07-31T22:00:31.520Z","updated_at":"2025-10-07T18:46:37.460Z","avatar_url":"https://github.com/relay-tools.png","language":"JavaScript","funding_links":[],"categories":["Libraries"],"sub_categories":["JavaScript Libraries"],"readme":"ReactRelayNetworkLayer (for Relay Classic)\n==========================================\n[![](https://img.shields.io/npm/v/react-relay-network-layer.svg)](https://www.npmjs.com/package/react-relay-network-layer)\n[![npm](https://img.shields.io/npm/dt/react-relay-network-layer.svg)](http://www.npmtrends.com/react-relay-network-layer)\n[![Travis](https://img.shields.io/travis/relay-tools/react-relay-network-layer.svg?maxAge=2592000)](https://travis-ci.org/relay-tools/react-relay-network-layer)\n[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)\n[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)\n![FlowType compatible](https://img.shields.io/badge/flowtype-compatible-brightgreen.svg)\n\n#### For **Relay Modern** please use [react-relay-network-modern](https://github.com/relay-tools/react-relay-network-modern) package.\n\nThe `ReactRelayNetworkLayer` is a [Relay Network Layer](https://facebook.github.io/relay/docs/guides-network-layer.html)\nwith various middlewares which can manipulate requests/responses on the fly (change auth headers, request url or perform some fallback if request fails), batch several relay request by timeout into one http request.\n\n`ReactRelayNetworkLayer` can be used in browser, react-native or node server for rendering. Under the hood this module uses global `fetch` method. So if your client is too old, please import explicitly proper polyfill to your code (eg. `whatwg-fetch`, `node-fetch` or `fetch-everywhere`).\n\n```\nyarn add react-relay-network-layer\nOR\nnpm install react-relay-network-layer --save\n```\n\n### Migrating from v1 to v2\nChanges in v2.0.0:\n- completely rewritten batch logic as middleware, added additional cool options to it `batchTimeout`, `maxBatchSize`\n- throw Error object on non-200 response (before thrown response)\n- much more tests\n\nAll other parts stay unaffected. So if you use `request batching`, you should change your config:\n```diff\nimport Relay from 'react-relay';\nimport {\n  RelayNetworkLayer,\n  urlMiddleware,\n+  batchMiddleware,\n} from 'react-relay-network-layer';\n\nRelay.injectNetworkLayer(new RelayNetworkLayer([\n+  batchMiddleware({\n+    batchUrl: (req) =\u003e '/graphql/batch',\n+  }),\n  urlMiddleware({\n    url: (req) =\u003e '/graphql',\n-    batchUrl: (req) =\u003e '/graphql/batch',\n  }),\n- ], { disableBatchQuery: false }));\n+ ]));\n```\n\nBig thanks to @brad-decker and @jeanregisser in helping to done this release.\n\nPrevious documentation for version 1.x.x can be found [here](https://github.com/relay-tools/react-relay-network-layer/blob/6eb361668cd81b760a8c99e8265f10416d802dd3/README.md).\n\nMiddlewares\n===========\n\n### Available middlewares:\n- **your custom inline middleware** - [see example](https://github.com/relay-tools/react-relay-network-layer#example-of-injecting-networklayer-with-middlewares-on-the-client-side) below where added `credentials` and `headers` to the `fetch` method.\n  - `next =\u003e req =\u003e { /* your modification of 'req' object */ return next(req); }`\n- **urlMiddleware** - for manipulating fetch `url` on fly via thunk.\n  - `url` - string or function(req) for single request (default: `/graphql`)  \n- **batchMiddleware** - gather some period of time relay-requests and sends it as one http-request. You server must support batch request, [how to setup your server](https://github.com/relay-tools/react-relay-network-layer#example-how-to-enable-batching)\n  - `batchUrl` - string or function(requestMap). Url of the server endpoint for batch request execution (default: `/graphql/batch`)\n  - `batchTimeout` - integer in milliseconds, period of time for gathering multiple requests before sending them to the server. Will delay sending of the requests on specified in this option period of time, so be careful and keep this value small. (default: `0`)\n  - `maxBatchSize` - integer representing maximum size of request to be sent in a single batch. Once a request hits the provided size in length a new batch request is ran. Actual for hardcoded limit in 100kb per request in [express-graphql](https://github.com/graphql/express-graphql/blob/master/src/parseBody.js#L112) module. (default: `102400` characters, roughly 100kb for 1-byte characters or 200kb for 2-byte characters)\n  - `allowMutations` - by default batching disabled for mutations, you may enable it passing `true` (default: `false`)\n- **retryMiddleware** - for request retry if the initial request fails.\n  - `fetchTimeout` - number in milliseconds that defines in how much time will request timeout after it has been sent to the server (default: `15000`).\n  - `retryDelays` - array of millisecond that defines the values on which retries are based on (default: `[1000, 3000]`).\n  - `statusCodes` - array of response status codes which will fire up retryMiddleware (default: `status \u003c 200 or status \u003e 300`).\n  - `allowMutations` - by default retries disabled for mutations, you may allow process retries for them passing `true` (default: `false`)\n  - `forceRetry` - function(cb, delay), when request is delayed for next retry, middleware will call this function and pass to it a callback and delay time. When you call this callback, middleware will proceed request immediately (default: `false`).\n- **authMiddleware** - for adding auth token, and refreshing it if gets 401 response from server.\n  - `token` - string or function(req) which returns token. If function is provided, then it will be called for every request (so you may change tokens on fly).\n  - `tokenRefreshPromise`: - function(req, err) which must return promise or regular value with a new token. This function is called when server returns 401 status code. After receiving a new token, middleware re-run query to the server with it seamlessly for Relay.\n  - `allowEmptyToken` - allow made a request without Authorization header if token is empty (default: `false`).\n  - `prefix` - prefix before token (default: `'Bearer '`).\n  - `header` - name of the HTTP header to pass the token in (default: `'Authorization'`).\n  - If you use `auth` middleware with `retry`, `retry` must be used before `auth`. Eg. if token expired when retries apply, then `retry` can call `auth` middleware again.\n- **loggerMiddleware** - for logging requests and responses.\n  - `logger` - log function (default: `console.log.bind(console, '[RELAY-NETWORK]')`)\n  - If you use `Relay@^0.9.0` you may turn on relay's internal [extended mutation debugger](https://twitter.com/steveluscher/status/738101549591732225). For this you should open browser console and type `__DEV__=true`. With webpack you may use `webpack.BannerPlugin('__DEV__=true;', {raw: true})` or `webpack.DefinePlugin({__DEV__: true})`.\n  - If you use `Relay@^0.8.0` you may turn on [internal Relay requests debugger](https://cloud.githubusercontent.com/assets/1946920/15735688/688ccabe-28bc-11e6-82e2-db644eb698b0.png): `import RelayNetworkDebug from 'react-relay/lib/RelayNetworkDebug';  RelayNetworkDebug.init();`\n- **perfMiddleware** - simple time measure for network request.\n  - `logger` - log function (default: `console.log.bind(console, '[RELAY-NETWORK]')`)\n- **gqErrorsMiddleware** - display `errors` data to console from graphql response. If you want see stackTrace for errors, you should provide `formatError` to `express-graphql` (see example below where `graphqlServer` accept `formatError` function).\n  - `logger` - log function (default: `console.error.bind(console)`)\n  - `prefix` - prefix message (default: `[RELAY-NETWORK] GRAPHQL SERVER ERROR:`)\n- **deferMiddleware** - _experimental_ Right now `deferMiddleware()` just set `defer` as supported option for Relay. So this middleware allow to community play with `defer()` in cases, which was [described by @wincent](https://github.com/facebook/relay/issues/288#issuecomment-199510058).\n\nAdvanced options (2nd argument after middlewares)\n===========\n\nRelayNetworkLayer may accept additional options:\n\n```js\nconst middlewares = []; // array of middlewares\nconst options = {}; // optional advanced options\nconst network = new RelayNetworkLayer(middlewares, options);\n```\n\nAvailable options:\n\n- **noThrow** - EXPERIMENTAL (May be deprecated in the future) set true to not throw when an error response is given by the server, and to instead handle errors in your app code.\n\n### Example of injecting NetworkLayer with middlewares on the **client side**.\n```js\nimport Relay from 'react-relay';\nimport {\n  RelayNetworkLayer,\n  urlMiddleware,\n  batchMiddleware,\n  loggerMiddleware,\n  gqErrorsMiddleware,\n  perfMiddleware,\n  retryMiddleware,\n  authMiddleware,\n} from 'react-relay-network-layer';\n\nRelay.injectNetworkLayer(new RelayNetworkLayer([\n  urlMiddleware({\n    url: (req) =\u003e '/graphql',\n  }),\n  batchMiddleware({\n    batchUrl: (reqestMap) =\u003e '/graphql/batch',\n    batchTimeout: 10,\n  }),\n  loggerMiddleware(),\n  gqErrorsMiddleware(),\n  perfMiddleware(),\n  retryMiddleware({\n    fetchTimeout: 15000,\n    retryDelays: (attempt) =\u003e Math.pow(2, attempt + 4) * 100, // or simple array [3200, 6400, 12800, 25600, 51200, 102400, 204800, 409600],\n    forceRetry: (cb, delay) =\u003e { window.forceRelayRetry = cb; console.log('call `forceRelayRetry()` for immediately retry! Or wait ' + delay + ' ms.'); },\n    statusCodes: [500, 503, 504]\n  }),\n  authMiddleware({\n    token: () =\u003e store.get('jwt'),\n    tokenRefreshPromise: (req) =\u003e {\n      console.log('[client.js] resolve token refresh', req);\n      return fetch('/jwt/refresh')\n        .then(res =\u003e res.json())\n        .then(json =\u003e {\n          const token = json.token;\n          store.set('jwt', token);\n          return token;\n        })\n        .catch(err =\u003e console.log('[client.js] ERROR can not refresh token', err));\n    },\n  }),\n\n  // example of the custom inline middleware\n  next =\u003e req =\u003e {\n    // `req` is an object with settings for `fetch` function. It's not an express request object.\n    // Internally works following code:\n    //    let { url, ...opts } = req;\n    //    fetch(url, opts)\n    // So `req` is a fetch options. And into this options, I added `url` prop, which will be extracted as shown above.\n    // You have fully control under `fetch` via `req` object.\n\n    req.method = 'GET'; // change default POST request method to GET\n    req.headers['X-Request-ID'] = uuid.v4(); // add `X-Request-ID` to request headers\n    req.credentials = 'same-origin'; // provide CORS policy to XHR request in fetch method\n    return next(req);\n  }\n]));\n```\n\n### How middlewares work internally\n\nMiddlewares on bottom layer use [fetch](https://github.com/github/fetch) method. So `req` is compliant with a `fetch()` options. And `res` can be obtained via `resPromise.then(res =\u003e ...)`, which returned by `fetch()`.\n\nMiddlewares have 3 phases:\n- `setup phase`, which runs only once, when middleware added to the NetworkLayer\n- `capturing phase`, when you may change request object, and pass it down via `next(req)`\n- `bubbling phase`, when you may change response promise, made re-request or pass it up unchanged\n\nBasic skeleton of middleware:\n```js\nexport default function skeletonMiddleware(opts = {}) {\n  // [SETUP PHASE]: here you can process `opts`, when you create Middleware\n\n  return next =\u003e req =\u003e {\n    // [CAPTURING PHASE]: here you can change `req` object, before it will pass to following middlewares.\n    // ...some code which modify `req`\n\n    const resPromise = next(req); // pass request to following middleware and get response promise from it\n\n    // [BUBBLING PHASE]: here you may change response of underlying middlewares, via promise syntax\n    // ...some code, which may add `then()` or `catch()` to response promise\n    //    resPromise.then(res =\u003e { console.log(res); return res; })\n\n    return resPromise; // return response promise to upper middleware\n  };\n}\n```\n\nMiddlewares use LIFO (last in, first out) stack. Or simply put - use `compose` function. So if you pass such middlewares [M1(opts), M2(opts)] to NetworkLayer it will be work such way:\n- call setup phase of `M1` with its opts\n- call setup phase of `M2` with its opts\n- for each request\n - call capture phase of `M1`\n - call capture phase of `M2`\n - call `fetch` method\n - call bubbling phase of `M2`\n - call bubbling phase of `M1`\n - chain to `resPromise.then(res =\u003e res.json())` and pass this promise for resolving/rejecting Relay requests.\n\n\nBatching several requests into one\n==================================\n\nJoseph Savona [wrote](https://github.com/facebook/relay/issues/1058#issuecomment-213592051): For legacy reasons, Relay splits \"plural\" root queries into individual queries. In general we want to diff each root value separately, since different fields may be missing for different root values.\n\nAlso if you use [react-relay-router](https://github.com/relay-tools/react-router-relay) and have multiple root queries in one route pass, you may notice that default network layer will produce several http requests.\n\nSo for avoiding multiple http-requests, the `ReactRelayNetworkLayer` is the right way to combine it in single http-request.\n\n### Example how to enable batching\n#### ...on server\nFirstly, you should prepare **server** to proceed the batch request:\n\n```js\nimport express from 'express';\nimport graphqlHTTP from 'express-graphql';\nimport { graphqlBatchHTTPWrapper } from 'react-relay-network-layer';\nimport bodyParser from 'body-parser';\nimport myGraphqlSchema from './graphqlSchema';\n\nconst port = 3000;\nconst server = express();\n\n// setup standart `graphqlHTTP` express-middleware\nconst graphqlServer = graphqlHTTP({\n  schema: myGraphqlSchema,\n  formatError: (error) =\u003e ({ // better errors for development. `stack` used in `gqErrors` middleware\n    message: error.message,\n    stack: process.env.NODE_ENV === 'development' ? error.stack.split('\\n') : null,\n  }),\n});\n\n// declare route for batch query\nserver.use('/graphql/batch',\n  bodyParser.json(),\n  graphqlBatchHTTPWrapper(graphqlServer)\n);\n\n// declare standard graphql route\nserver.use('/graphql',\n  graphqlServer\n);\n\nserver.listen(port, () =\u003e {\n  console.log(`The server is running at http://localhost:${port}/`);\n});\n```\n[More complex example](https://github.com/relay-tools/react-relay-network-layer/blob/master/examples/dataLoaderPerBatchRequest.js) of how you can use a single [DataLoader](https://github.com/facebook/dataloader) for all (batched) queries within a one HTTP-request.\n\nIf you are on Koa@2, [koa-graphql-batch](https://github.com/mattecapu/koa-graphql-batch) provides the same functionality as `graphqlBatchHTTPWrapper` (see its docs for usage example).\n\n#### ...on client\nAnd right after server side ready to accept batch queries, you may enable batching on the **client**:\n\n```js\nRelay.injectNetworkLayer(new RelayNetworkLayer([\n  batchMiddleware({\n    batchUrl: '/graphql/batch', // \u003c--- route for batch queries\n  }),\n]));\n```\n\n### How batching works internally\nInternally batching in NetworkLayer prepare list of queries `[ {id, query, variables}, ...]` sends it to server. And server returns list of responces `[ {id, payload}, ...]`, (where `id` is the same value as client requested for identifying which data goes with which query, and `payload` is standard response of GraphQL server: `{ data, error }`).\n\n\nRecommended modules\n==========\n- **[babel-plugin-transform-relay-hot](https://github.com/relay-tools/babel-plugin-transform-relay-hot)** - Babel 6 plugin for transforming `Relay.QL` tagged templates via the GraphQL json schema file. Each time when schema file was changed, the wrapper updates instance of standard `babelRelayPlugin` with new schema without completely restarting dev server.\n\n\nContribute\n==========\nI actively welcome pull requests with code and doc fixes.\nAlso if you made great middleware and want share it within this module, please feel free to open PR.\n\n[CHANGELOG](https://github.com/relay-tools/react-relay-network-layer/blob/master/CHANGELOG.md)\n\n\nLicense\n=======\n[MIT](https://github.com/relay-tools/react-relay-network-layer/blob/master/LICENSE.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frelay-tools%2Freact-relay-network-layer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frelay-tools%2Freact-relay-network-layer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frelay-tools%2Freact-relay-network-layer/lists"}