{"id":13393412,"url":"https://github.com/monojack/graphql-normalizr","last_synced_at":"2025-04-05T09:08:41.606Z","repository":{"id":55939515,"uuid":"119448517","full_name":"monojack/graphql-normalizr","owner":"monojack","description":"Normalize GraphQL responses for persisting in the client cache/state","archived":false,"fork":false,"pushed_at":"2024-02-15T07:37:14.000Z","size":664,"stargazers_count":194,"open_issues_count":0,"forks_count":14,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-28T19:06:48.874Z","etag":null,"topics":["application-cache","graphql","javascript","normalizer","state-management"],"latest_commit_sha":null,"homepage":null,"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/monojack.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-01-29T22:10:44.000Z","updated_at":"2025-02-11T21:57:47.000Z","dependencies_parsed_at":"2024-02-15T08:33:29.431Z","dependency_job_id":"da786f17-a5b0-470e-992f-7a2083d5f1d5","html_url":"https://github.com/monojack/graphql-normalizr","commit_stats":{"total_commits":94,"total_committers":9,"mean_commits":"10.444444444444445","dds":"0.25531914893617025","last_synced_commit":"693268df08248bb0b2718fa00357c7b959c36698"},"previous_names":[],"tags_count":26,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monojack%2Fgraphql-normalizr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monojack%2Fgraphql-normalizr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monojack%2Fgraphql-normalizr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monojack%2Fgraphql-normalizr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/monojack","download_url":"https://codeload.github.com/monojack/graphql-normalizr/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247312081,"owners_count":20918344,"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":["application-cache","graphql","javascript","normalizer","state-management"],"created_at":"2024-07-30T17:00:52.154Z","updated_at":"2025-04-05T09:08:41.574Z","avatar_url":"https://github.com/monojack.png","language":"JavaScript","funding_links":[],"categories":["JavaScript","Libraries","Implementations"],"sub_categories":["JavaScript Libraries","JavaScript/TypeScript"],"readme":"# **graphql-normalizr**\n\n![publish](https://github.com/monojack/graphql-normalizr/workflows/Publish/badge.svg)\n[![npm version](https://img.shields.io/npm/v/graphql-normalizr.svg)](https://www.npmjs.com/package/graphql-normalizr)\n[![npm downloads](https://img.shields.io/npm/dm/graphql-normalizr.svg)](https://www.npmjs.com/package/graphql-normalizr)\n[![minified size](https://badgen.net/bundlephobia/min/graphql-normalizr)](https://bundlephobia.com/result?p=graphql-normalizr@latest)\n\nNormalize GraphQL responses for persisting in the client cache/state.\n\n\u003e Not related, in any way, to [normalizr](https://github.com/paularmstrong/normalizr), just shamelessly piggybacking on its popularity. Also, \"normaliz**E**r\" is taken...\n\n**TL;DR**: Transforms:\n\n```json\n{\n  \"data\": {\n    \"findUser\": [\n      {\n        \"__typename\": \"User\",\n        \"id\": \"5a6efb94b0e8c36f99fba013\",\n        \"email\": \"Lloyd.Nikolaus@yahoo.com\",\n        \"posts\": [\n          {\n            \"__typename\": \"BlogPost\",\n            \"id\": \"5a6efb94b0e8c36f99fba016\",\n            \"title\": \"Dolorem voluptatem molestiae\",\n            \"comments\": [\n              {\n                \"__typename\": \"Comment\",\n                \"id\": \"5a6efb94b0e8c36f99fba019\",\n                \"message\": \"Alias quod est voluptatibus aut quis sunt aut numquam.\"\n              },\n              {\n                \"__typename\": \"Comment\",\n                \"id\": \"5a6efb94b0e8c36f99fba01b\",\n                \"message\": \"Harum quia asperiores nemo.\"\n              },\n              {\n                \"__typename\": \"Comment\",\n                \"id\": \"5a6efb94b0e8c36f99fba01c\",\n                \"message\": \"Vel veniam consectetur laborum.\"\n              },\n              {\n                \"__typename\": \"Comment\",\n                \"id\": \"5a6efb94b0e8c36f99fba01e\",\n                \"message\": \"Possimus beatae vero recusandae beatae quas ut commodi laboriosam.\"\n              }\n            ]\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\ninto:\n\n```json\n{\n  \"comments\": {\n    \"5a6efb94b0e8c36f99fba019\": {\n      \"id\": \"5a6efb94b0e8c36f99fba019\",\n      \"message\": \"Alias quod est voluptatibus aut quis sunt aut numquam.\"\n    },\n    \"5a6efb94b0e8c36f99fba01b\": {\n      \"id\": \"5a6efb94b0e8c36f99fba01b\",\n      \"message\": \"Harum quia asperiores nemo.\"\n    },\n    \"5a6efb94b0e8c36f99fba01c\": {\n      \"id\": \"5a6efb94b0e8c36f99fba01c\",\n      \"message\": \"Vel veniam consectetur laborum.\"\n    },\n    \"5a6efb94b0e8c36f99fba01e\": {\n      \"id\": \"5a6efb94b0e8c36f99fba01e\",\n      \"message\": \"Possimus beatae vero recusandae beatae quas ut commodi laboriosam.\"\n    }\n  },\n  \"blogPosts\": {\n    \"5a6efb94b0e8c36f99fba016\": {\n      \"id\": \"5a6efb94b0e8c36f99fba016\",\n      \"title\": \"Dolorem voluptatem molestiae\",\n      \"comments\": [\n        \"5a6efb94b0e8c36f99fba019\",\n        \"5a6efb94b0e8c36f99fba01b\",\n        \"5a6efb94b0e8c36f99fba01c\",\n        \"5a6efb94b0e8c36f99fba01e\"\n      ]\n    }\n  },\n  \"users\": {\n    \"5a6efb94b0e8c36f99fba013\": {\n      \"id\": \"5a6efb94b0e8c36f99fba013\",\n      \"email\": \"Lloyd.Nikolaus@yahoo.com\",\n      \"posts\": [\"5a6efb94b0e8c36f99fba016\"]\n    }\n  }\n}\n```\n\n## Motivation\n\nWe all love **GraphQL** and we want to use it. There are tons of libraries and clients out there that help us do that with ease, but there is still one problem... How do you persist that data?\n\nYes, everything is all great when the response mirrors the exact structure we asked for, but we don't want to cache it that way, do we? We probably want a normalized version of that data which we can persist to our store and read/modify it efficiently. Flux or Redux stores work best with normalized data and there are also GraphQL clients you can use to execute queries on the local cache/state ([blips](https://github.com/monojack/blips) or [apollo-link-state](https://github.com/apollographql/apollo-link-state)), in which case, we definitely need to persist normalized data.\n\n**GraphQLNormalizr** is simple, fast, light-weight and it provides all the tools needed to do just that, the only requirement is that you include the `id` and `__typename` fields for all the nodes _(but it can do that for you if you're too lazy or you want to keep your sources thin)_.\n\n## Table of contents\n\n- [Installation](#installation)\n- [API by example](#api-by-example)\n  - [`GraphQLNormalizr`](#graphqlnormalizr)\n  - [`parse`](#parse)\n  - [`addRequiredFields`](#addrequiredfields)\n  - [`normalize`](#normalize)\n- [Migrating from 1.x to 2.x](#migrating)\n\n## Installation\n\n```sh\nnpm install graphql-normalizr\n```\n\n## API by example\n\nThe **GraphQLNormalizr** constructor function returns an object containing 3 methods:\n\n1.  [parse](#parse)\n2.  [addRequiredFields](#addrequiredfields)\n3.  [normalize](#normalize)\n\nDepending on how you write your queries, you may or may not use `parse` or `addRequiredFields`, but `normalize` is the method that you will transform the GraphQL response. As you've probably seen from the **TL;DR**, all response nodes must contain the `__typename` and `id` fields. `__typename` is a [GraphQL meta field](http://graphql.org/learn/queries/#meta-fields) and the `id` key may be customized when creating the GraphQLNormalizr client.\n\nIf your queries already ask for `id` and `__typename` there's no need to use **parse** or **addRequiredFields**. Otherwise, **parse** will take care of transforming your `GraphQL source` into a `Document` and add the `__typename` and `id` fields where needed. In case you already use a different parser, or only have access to the `Document` you may use **addRequiredFields** on the `Document` to add the `__typename` and `id` fields\n\n### GraphQLNormalizr\n\n```js\nimport { GraphQLNormalizr } from 'graphql-normalizr'\n\n// const config = ...\nconst normalizer = new GraphQLNormalizr(config)\n```\n\n**config**: optional - the configuration object containing information for instantiating the client. it takes the following props:\n\n- [idKey](#idkey)\n- [useConnections](#useconnections)\n- [typeMap](#typemap)\n- [ignore](#ignore)\n- [lists](#lists)\n- [typenames](#typenames)\n- [typePointers](#typepointers)\n- [caching](#caching)\n- [plural](#plural)\n- [casing](#casing)\n\n##### idKey\n\n\u003e String\n\nDefault is **\"id\"**. Configures a custom `id` key for the client. Use this if your resource identifiers are found under a different key name _('\\_id', 'key', 'uid' etc)_.\n\nConsider the following GraphQL response:\n\n```js\nconst response = {\n  data: {\n    findUser: {\n      __typename: 'User',\n      uid: '5a6efb94b0e8c36f99fba013',\n      email: 'Lloyd.Nikolaus@yahoo.com',\n    },\n  },\n}\n```\n\nNormalize the data with our custom `id` key:\n\n```js\n// using destructuring to get the `normalize` method of the client\nconst { normalize } = new GraphQLNormalizr({ idKey: 'uid' })\nnormalize(response)\n// =\u003e\n// {\n//  users: {\n//    '5a6efb94b0e8c36f99fba013' : {\n//      uid: '5a6efb94b0e8c36f99fba013',\n//      email: 'Lloyd.Nikolaus@yahoo.com'\n//    }\n//  }\n// }\n```\n\n##### useConnections\n\n\u003e Boolean\n\nDefault is `false`. If you are using GraphQL connections with `edges` and `nodes`, set this flag to **`true`** otherwise you'll get a warning and the normalization won't work.\n\n**NOTE**: _The connections implementation needs to be according to the [specification](https://facebook.github.io/relay/graphql/connections.htm)_\n\n```js\nconst response = {\n  data: {\n    findUser: {\n      __typename: 'User',\n      id: '5a6efb94b0e8c36f99fba013',\n      email: 'Lloyd.Nikolaus@yahoo.com',\n      friends: {\n        __typename: 'FriendsConnection',\n        totalCount: 3,\n        edges: [\n          {\n            node: {\n              __typename: 'User',\n              id: '5a6cf127c2b20834f6551481',\n              email: 'Madisen_Braun@hotmail.com',\n            },\n            cursor: 'Y3Vyc29yMg==',\n          },\n          {\n            node: {\n              __typename: 'User',\n              id: '5a6cf127c2b20834f6551482',\n              email: 'Robel.Ansel@yahoo.com',\n            },\n            cursor: 'Y3Vyc29yMw==',\n          },\n        ],\n        pageInfo: {\n          endCursor: 'Y3Vyc29yMw==',\n          hasNextPage: false,\n        },\n      },\n    },\n  },\n}\n\nconst { normalize } = new GraphQLNormalizr({\n  useConnections: true,\n})\n\nnormalize(response)\n// =\u003e\n// {\n//   users: {\n//     '5a6efb94b0e8c36f99fba013': {\n//       id: '5a6efb94b0e8c36f99fba013',\n//       email: 'Lloyd.Nikolaus@yahoo.com',\n//       friends: ['5a6cf127c2b20834f6551481', '5a6cf127c2b20834f6551482'],\n//     },\n//     '5a6cf127c2b20834f6551481': {\n//       id: '5a6cf127c2b20834f6551481',\n//       email: 'Madisen_Braun@hotmail.com',\n//     },\n//     '5a6cf127c2b20834f6551482': {\n//       id: '5a6cf127c2b20834f6551482',\n//       email: 'Robel.Ansel@yahoo.com',\n//     },\n//   },\n// }\n```\n\n##### typeMap\n\n\u003e Object\n\nBy default **the entity name will be the plural form of the type name, converted to camel case**, _(`PrimaryAddress` type will be stored under the `primaryAddresses` key)_. Use this option to provide specific **entity** names for some/all **Types**, or try the [plural](#plural) and [casing](#casing) options to derive the entity names.\n\n```js\nconst response = {\n  data: {\n    findUser: {\n      __typename: 'User',\n      id: '5a6efb94b0e8c36f99fba013',\n      email: 'Lloyd.Nikolaus@yahoo.com',\n    },\n  },\n}\n\nconst { normalize } = new GraphQLNormalizr({\n  typeMap: { User: 'accounts' },\n})\nnormalize(response)\n// =\u003e\n// {\n//  accounts: {\n//    '5a6efb94b0e8c36f99fba013' : {\n//      id: '5a6efb94b0e8c36f99fba013',\n//      email: 'Lloyd.Nikolaus@yahoo.com'\n//    }\n//  }\n// }\n```\n\n##### ignore\n\n\u003e Object\n\nPrevent normalization of specified fields\n\n```js\nconst response = {\n  data: {\n    allUsers: [\n      {\n        __typename: 'User',\n        id: '5a6efb94b0e8c36f99fba013',\n        email: 'Lloyd.Nikolaus@yahoo.com',\n        preferences: null\n        posts: [\n          {\n            __typename: 'BlogPost',\n            id: '5a6cf127c2b20834f6551484',\n            likes: 10,\n            title: 'Sunt ut aut',\n            tags: {},\n          }\n        ]\n      },\n      {\n        __typename: 'User',\n        id: '5a6efb94b0e8c36f99fba013',\n        email: 'Anna.Klaus@gmail.com',\n        preferences: { foo: 'apple', bar: 1,  baz: { a: 'b' }, quux: null, }\n        posts: [\n          {\n            __typename: 'BlogPost',\n            id: '5a6cf127c2b20834f6551485',\n            likes: 23,\n            title: 'Nesciunt esse',\n            tags: [],\n          }\n        ]\n      },\n    ],\n  },\n}\n```\n\nNormalize the data excluding the `preferences` field on `users` and the `tags` field on `blogPosts`:\n\n```js\n// using destructuring to get the `normalize` method of the client\nconst { normalize } = new GraphQLNormalizr({ ignore: { users: [ 'preferences' ], blogPosts: [ 'tags' ] } })\nnormalize(response)\n// =\u003e\n// {\n//   users: {\n//     '5a6efb94b0e8c36f99fba013': {,\n//       id: '5a6efb94b0e8c36f99fba013',\n//       email: 'Lloyd.Nikolaus@yahoo.com',\n//       preferences: null\n//     },\n//     '5a6efb94b0e8c36f99fba013': {\n//       id: '5a6efb94b0e8c36f99fba013',\n//       email: 'Anna.Klaus@gmail.com',\n//       preferences: { foo: 'apple', bar: 1,  baz: { a: 'b' }, quux: null, }\n//     },\n//   },\n//   blogPosts: {\n//     '5a6cf127c2b20834f6551484': {\n//       id: '5a6cf127c2b20834f6551484',\n//       likes: 10,\n//       title: 'Sunt ut aut',\n//       tags: {},\n//     },\n//     '5a6cf127c2b20834f6551485': {\n//       id: '5a6cf127c2b20834f6551485',\n//       likes: 23,\n//       title: 'Nesciunt esse',\n//       tags: [],\n//     },\n//   }\n// }\n```\n\n##### plural\n\n\u003e Boolean\n\nDefault is `true`. Set this to `false` if you don't want to [pluralize](https://github.com/blakeembrey/pluralize) entity names. Considering the previous response example:\n\n```js\nconst { normalize } = new GraphQLNormalizr({\n  plural: false,\n})\nnormalize(response)\n// =\u003e\n// {\n//  user: {\n//    '5a6efb94b0e8c36f99fba013' : {\n//      id: '5a6efb94b0e8c36f99fba013',\n//      email: 'Lloyd.Nikolaus@yahoo.com'\n//    }\n//  }\n// }\n```\n\n##### casing\n\n\u003e 'lower'|'upper'|'camel'|'pascal'|'snake'|'kebab'\n\nYou can also specify the preferred casing for entity names. Again, consider the above response example.\n\n```js\n// casing: 'lower'\n// User =\u003e user\n\n// casing: 'upper'\n// User =\u003e USER\n\n// casing: 'camel'\n// PrimaryAddress =\u003e primaryAddress\n\n// casing: 'pascal'\n// PrimaryAddress =\u003e PrimaryAddress\n\n// casing: 'snake'\n// PrimaryAddress =\u003e primary_address\n\n// casing: 'kebab'\n// PrimaryAddress =\u003e primary-address\n```\n\nCombine `plural` and `casing` options to get the desired entity names\n\n##### lists\n\n\u003e Boolean\n\nDefault is `false`. All the data is stored in key/value pairs, for easy access. If you want to use arrays, for whatever reason, set this to `true`\n\nFor the same response object in our previous example:\n\n```js\nconst { normalize } = new GraphQLNormalizr({\n  lists: true,\n})\nnormalize(response)\n// =\u003e\n// {\n//  users: [\n//    {\n//      id: '5a6efb94b0e8c36f99fba013',\n//      email: 'Lloyd.Nikolaus@yahoo.com'\n//    }\n//  ]\n// }\n```\n\n##### typenames\n\n\u003e Boolean\n\nDefault is `false`. The normalized data will not contain the `__typename` field. Set this to `true` if you need to persist them.\n\n```js\nconst { normalize } = new GraphQLNormalizr({\n  typenames: true,\n})\n\nnormalize(response)\n// =\u003e\n// {\n//  users: {\n//    '5a6efb94b0e8c36f99fba013' : {\n//      __typename: 'User',\n//      id: '5a6efb94b0e8c36f99fba013',\n//      email: 'Lloyd.Nikolaus@yahoo.com'\n//    }\n//  }\n// }\n```\n\n##### typePointers\n\n\u003e Boolean\n\nDefault is `false`. Enables explicit type pointers - instead of an array of only identifiers and having to figure out which collection they point to, it will return objects containing the identifier as well as the collection name. Works especially well with Union types and Interfaces.\n\n```js\n\nconst { normalize } = new GraphQLNormalizr({\n  typePointers: true,\n})\n\n// ['5a6cf127c2b20834f655148a', '5a6cf127c2b20834f655148b', '5a6cf127c2b20834f655148c']\nusers: [\n  {\n    _id: '5a6cf127c2b20834f655148a', // '_id' or the specified key\n    collection: 'members', // type Member\n  },\n  {\n    _id: '5a6cf127c2b20834f655148b', // '_id' or the specified key\n    collection: 'authors', // type Author\n  },\n  {\n    _id: '5a6cf127c2b20834f655148c', // '_id' or the specified key\n    collection: 'members', // type Member\n  },\n],\n```\n\n##### caching\n\n\u003e Boolean\n\nDefault is `false`. The **normalize** method is pretty fast by itself, it does a single iteration and associates the values only for each response node and not for all the fields. Enable this if you think you'd be normalizing the same response multiple times, like when you're polling for data and it may not have changed.\n\n```js\nconst { normalize } = new GraphQLNormalizr({\n  caching: true,\n})\n\nconst normalized = normalize(response)\nconst cached = normalize(response)\n\ncached === normalized // =\u003e true\n```\n\n### `parse`\n\nTurns a **GraphQL source** into a **Document** and adds the required fields where necessary.\n\n```js\n// ...\nimport { GraphQLNormalizr } from 'graphql-normalizr'\n\nconst source = `{\n  allUsers {\n    email\n    posts {\n      title\n      comments {\n        message\n      }\n    }\n  }\n}`\n\nconst { parse } = new GraphQLNormalizr()\n\nconst query = parse(source) // will add `id` and `__typename` fields to all the nodes\n\n// We can use the print method from `graphql` to see/use the updated source\nconst { print } = require('graphql')\nprint(query)\n// =\u003e\n// `{\n//  allUsers {\n//    __typename\n//    id\n//    email\n//    posts {\n//      __typename\n//      id\n//      comments {\n//        __typename\n//        id\n//        message\n//      }\n//    }\n//  }\n// }`\n\n// ...\n```\n\n### `addRequiredFields`\n\nIf you only have access to the **Document**, you can use the **print** method from `graphql` to get the **source** and parse it. But that may be expensive and you shouldn't have to print a document just to parse it again. `addRequiredFields` will add the `id` and `__typename` fields to that document, without the need of extracting its source.\n\n```js\n// ...\nimport { GraphQLNormalizr } from 'graphql-normalizr'\nimport { allUsersQuery } from './queries'\n\nconst { addRequiredFields } = new GraphQLNormalizr()\n\nconst query = addRequiredFields(allUsersQuery)\n\n// ...\n```\n\n### `normalize`\n\nThe following is a full example where we use [apollo-fetch](https://github.com/apollographql/apollo-fetch/tree/master/packages/apollo-fetch) to execute a query and then normalize it with **GraphQLNormalizr**\n\n```js\nconst { GraphQLNormalizr } = require('graphql-normalizr')\nconst { createApolloFetch } = require('apollo-fetch')\n\nconst uri = 'http://localhost:8080/graphql'\nconst fetch = createApolloFetch({ uri })\n\nconst source = `\n  query {\n    allUsers {\n      ...userFields\n    }\n  }\n  fragment userFields on User {\n    email\n    posts {\n      title\n      comments {\n        message\n      }\n    }\n  }\n`\n\nconst { normalize, parse } = new GraphQLNormalizr()\nconst query = parse(source)\n\nfetch({ query }).then(response =\u003e {\n  const normalized = normalize(response)\n  // persist the normalized data to our app state.\n}).catch(...)\n```\n\n## Migrating\n\n**from _v1.x_ to _v2.x_**\n\nThere aren't many breaking changes between v1.x and v2.x. In fact, there's only one and it's about how **Type** names get converted into **entity** names.\n\nWith the default configuration, v1.x will transfrom a `PrimaryAddress` type name into `primaryaddresses` entity name. With 2.x, the default configuiration will transform `PrimaryAddress` to `primaryAddresses`. The only difference is that now, it changes to _camelcase_ instead of _lowercase_\n\nIf you don't want to change your code everywhere you are accessing entities, you can configure the way **GraphQLNormalizr** makes the transformation with the [plural](#plural) and [casing](#casing) options:\n\n```js\nconst { normalize } = new GraphQLNormalizr({\n  plural: true, // true is the default value, so you can omit this\n  casing: 'lower',\n})\n\n// this above configuration will change `PrimaryAddress` to `primaryaddresses`\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmonojack%2Fgraphql-normalizr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmonojack%2Fgraphql-normalizr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmonojack%2Fgraphql-normalizr/lists"}