All Projects → gajus → Graphql Deduplicator

gajus / Graphql Deduplicator

Licence: other
A GraphQL response deduplicator. Removes duplicate entities from the GraphQL response.

Programming Languages

javascript
184084 projects - #8 most used programming language

Projects that are alternatives of or similar to Graphql Deduplicator

Pup
The Ultimate Boilerplate for Products.
Stars: ✭ 563 (+118.22%)
Mutual labels:  graphql, apollo, graphql-client
Villus
🏎 A tiny and fast GraphQL client for Vue.js
Stars: ✭ 378 (+46.51%)
Mutual labels:  graphql, apollo, graphql-client
Apollo Fetch
🐶 Lightweight GraphQL client that supports middleware and afterware
Stars: ✭ 581 (+125.19%)
Mutual labels:  graphql, apollo, graphql-client
Example Storefront
Example Storefront is Reaction Commerce’s headless ecommerce storefront - Next.js, GraphQL, React. Built using Apollo Client and the commerce-focused React UI components provided in the Storefront Component Library (reactioncommerce/reaction-component-library). It connects with Reaction backend with the GraphQL API.
Stars: ✭ 471 (+82.56%)
Mutual labels:  graphql, apollo, graphql-client
Apollo Link
🔗 Interface for fetching and modifying control flow of GraphQL requests
Stars: ✭ 1,434 (+455.81%)
Mutual labels:  graphql, apollo, graphql-client
Next React Graphql Apollo Hooks
React, Apollo, Next.js, GraphQL, Node.js, TypeScript high performance boilerplate with React hooks GraphQL implementation and automatic static type generation
Stars: ✭ 186 (-27.91%)
Mutual labels:  graphql, apollo
Graphql.js
A Simple and Isomorphic GraphQL Client for JavaScript
Stars: ✭ 2,206 (+755.04%)
Mutual labels:  graphql, graphql-client
Reason Urql
Reason bindings for Formidable's Universal React Query Library, urql.
Stars: ✭ 203 (-21.32%)
Mutual labels:  graphql, graphql-client
Modern Graphql Tutorial
📖 A simple and easy GraphQL tutorial to get started with GraphQL.
Stars: ✭ 219 (-15.12%)
Mutual labels:  graphql, apollo
React Auth App Example
An app example with authentication using Create React App, React, React Router, Apollo, GraphQL, Redux and Redux Form.
Stars: ✭ 179 (-30.62%)
Mutual labels:  graphql, apollo
React Native Fullstack Graphql
🚀 Starter projects for mobile applications based on React Native & GraphQL.
Stars: ✭ 208 (-19.38%)
Mutual labels:  graphql, apollo
Canner
⚡️[NOT MAINTAINED] Content Management Framework creates custom CMS fast and easy. Support data sources such as Firebase/Firestore, GraphQL and Restful APIs.
Stars: ✭ 2,472 (+858.14%)
Mutual labels:  graphql, apollo
Hotchocolate
Welcome to the home of the Hot Chocolate GraphQL server for .NET, the Strawberry Shake GraphQL client for .NET and Banana Cake Pop the awesome Monaco based GraphQL IDE.
Stars: ✭ 3,009 (+1066.28%)
Mutual labels:  graphql, graphql-client
Gqlify
[NOT MAINTAINED]An API integration framework using GraphQL
Stars: ✭ 182 (-29.46%)
Mutual labels:  graphql, graphql-client
Next Ecommerce
⚡️ Quantum Ecommerce. Made with Next.js | GraphQL | Apollo Server | Apollo Client | SSR
Stars: ✭ 186 (-27.91%)
Mutual labels:  graphql, apollo
Fullstack Boilerplate
Fullstack boilerplate using Typescript, React, GraphQL
Stars: ✭ 181 (-29.84%)
Mutual labels:  graphql, apollo
36 Graphql Concepts
📜 36 concepts every GraphQL developer should know.
Stars: ✭ 209 (-18.99%)
Mutual labels:  graphql, graphql-client
Neuron
A GraphQL client for Elixir
Stars: ✭ 244 (-5.43%)
Mutual labels:  graphql, graphql-client
Kit
ReactQL starter kit (use the CLI)
Stars: ✭ 232 (-10.08%)
Mutual labels:  graphql, apollo
Graphql Shield
🛡 A GraphQL tool to ease the creation of permission layer.
Stars: ✭ 3,121 (+1109.69%)
Mutual labels:  graphql, apollo

graphql-deduplicator

GitSpo Mentions Travis build status Coveralls NPM version Canonical Code Style

A GraphQL response deduplicator.

Removes duplicate entities from the GraphQL response.

Client support

graphql-deduplicator works with any GraphQL client that appends __typename and id fields to every resource. If your client automatically does not request __typename and id fields, these fields can be specified in your GraphQL query.

graphql-deduplicator has been tested with apollo-client.

How does it work?

__typename and an id values are used to construct a resource identifier. The resource identifier is used to normalize data. As a result, when GraphQL API response contains a resource with a repeating identifier, the apollo-client is going to read only the first instance of the resource and ignore duplicate entities. graphql-deduplicator strips body (fields other than __datatype and id) from all the duplicate entities.

Motivation

graphql-deduplicator is designed to reduce the GraphQL response size by removing body of duplicate entities. This allows to make queries that return large datasets of repeated data without worrying about the cost of the response body size, time it takes to parse the response or the memory the reconstructed object will consume.

Real-life example

Consider the following schema:

interface Node {
  id: ID!
}

type Movie implements Node {
  id: ID!
  name: String!
  synopsis: String!
}

type Event implements Node {
  id: ID!
  movie: Movie!
  date: String!
  time: String!
}

type Query {
  events (
    date: String
  ): [Event!]!
}

Using this schema, you can query events for a particular date, e.g.

{
  events (date: "2017-05-19") {
    __typename
    id
    date
    time
    movie {
      __typename
      id
      name
      synopsis
    }
  }
}

Note: If you are using apollo-client, then you do not need to include __typename when constructing the query.

The result of the above query will contain a lot of duplicate information.

{
  "data": {
    "events": [
      {
        "__typename": "Event",
        "id": "1669971",
        "date": "2017-05-19",
        "time": "17:25",
        "movie": {
          "__typename": "Movie",
          "id": "1198359",
          "name": "King Arthur: Legend of the Sword",
          "synopsis": "When the child Arthur’s father is murdered, Vortigern, Arthur’s uncle, seizes the crown. Robbed of his birthright and with no idea who he truly is, Arthur comes up the hard way in the back alleys of the city. But once he pulls the sword Excalibur from the stone, his life is turned upside down and he is forced to acknowledge his true legacy... whether he likes it or not."
        }
      },
      {
        "__typename": "Event",
        "id": "1669972",
        "date": "2017-05-19",
        "time": "20:30",
        "movie": {
          "__typename": "Movie",
          "id": "1198359",
          "name": "King Arthur: Legend of the Sword",
          "synopsis": "When the child Arthur’s father is murdered, Vortigern, Arthur’s uncle, seizes the crown. Robbed of his birthright and with no idea who he truly is, Arthur comes up the hard way in the back alleys of the city. But once he pulls the sword Excalibur from the stone, his life is turned upside down and he is forced to acknowledge his true legacy... whether he likes it or not."
        }
      },
      // ...
    ]
  }
}

I've run into this situation when building https://applaudience.co.uk. A query retrieving 300 events produced a response of 1.5MB. When gziped, that number dropped to 100KB. However, the problem is that upon receiving the response, the browser needs to parse the entire JSON document. Parsing 1.5MB JSON string is (a) time consuming and (b) memory expensive.

The good news is that we do not need to return body of duplicate records (see How does it work?). For all duplicate records we only need to return __typename and id. This information is enough for apollo-client to identify the resource as duplicate and skip it. In case when a response includes large and often repeated fragments, this will reduce the response size 10x, 100x or more times.

In case of the earlier example, the response becomes:

{
  "data": {
    "events": [
      {
        "__typename": "Event",
        "id": "1669971",
        "date": "2017-05-19",
        "time": "17:25",
        "movie": {
          "__typename": "Movie",
          "id": "1198359",
          "name": "King Arthur: Legend of the Sword",
          "synopsis": "When the child Arthur’s father is murdered, Vortigern, Arthur’s uncle, seizes the crown. Robbed of his birthright and with no idea who he truly is, Arthur comes up the hard way in the back alleys of the city. But once he pulls the sword Excalibur from the stone, his life is turned upside down and he is forced to acknowledge his true legacy... whether he likes it or not."
        }
      },
      {
        "__typename": "Event",
        "id": "1669972",
        "date": "2017-05-19",
        "time": "20:30",
        "movie": {
          "__typename": "Movie",
          "id": "1198359"
        }
      },
      // ...
    ]
  }
}

The synopsis and name fields have been removed from the duplicate Movie entity.

Usage

Server-side

You need to format the final result of the query. If you are using graphql-server, configure formatResponse, e.g.

import express from 'express';
import {
  graphqlExpress
} from 'graphql-server-express';
import {
  deflate
} from 'graphql-deduplicator';

const app = express();

app.use('/graphql', graphqlExpress(() => {
  return {
    formatResponse: (response) => {
      if (response.data && !response.data.__schema) {
        return deflate(response);
      }

      return response;
    }
  };
}));

app.listen(3000);

Client-side

Example usage with apollo-client

You need to modify the server response before it is processed by the GraphQL client. If you are using apollo-client, use link configuration to setup an afterware, e.g.

// @flow

import {
  ApolloClient
} from 'apollo-client';
import {
  ApolloLink,
  concat
} from 'apollo-link';
import {
  InMemoryCache
} from 'apollo-cache-inmemory';
import {
  HttpLink
} from 'apollo-link-http';
import {
  inflate
} from 'graphql-deduplicator';

const httpLink = new HttpLink({
  credentials: 'include',
  uri: '/api'
});

const inflateLink = new ApolloLink((operation, forward) => {
  return forward(operation)
    .map((response) => {
      return inflate(response);
    });
});

const apolloClient = new ApolloClient({
  cache: new InMemoryCache(),
  link: concat(inflateLink, httpLink)
});

export default apolloClient;

Example usage with apollo-boost

It is not possible to configure link with apollo-boost. Therefore, it is not possible to use graphql-deduplicator with apollo-boost. Use apollo-client setup.

Note: apollo-boost will be discontinued starting Apollo Client v3.

Best practices

Enable compression conditionally

Do not break integration of the standard GraphQL clients that are unaware of the graphql-deduplicator.

Use deflate only when client requests to use graphql-deduplicator, e.g.

// Server-side

app.use('/graphql', graphqlExpress((request) => {
  return {
    formatResponse: (response) => {
      if (request.query.deduplicate && response.data && !response.data.__schema) {
        return deflate(response);
      }

      return response;
    }
  };
}));

// Client-side

const httpLink = new HttpLink({
  credentials: 'include',
  uri: '/api?deduplicate=1'
});

Example using with Apollo Server

import { GraphQLExtension, GraphQLResponse } from 'apollo-server-core'
import { deflate } from 'graphql-deduplicator'
// [..]

const createContext = ({ req }): => {
  return {
    req,
    // [..]
  }
}
class DeduplicateResponseExtension extends GraphQLExtension {
  public willSendResponse(o) {
    const { context, graphqlResponse } = o
    // Ensures `?deduplicate=1` is used in the request
    if (context.req.query.deduplicate && graphqlResponse.data && !graphqlResponse.data.__schema) {
      const data = deflate(graphqlResponse.data)
      return {
        ...o,
        graphqlResponse: {
          ...graphqlResponse,
          data,
        },
      }
    }

    return o
  }
}

const apolloServer = new ApolloServer({
  // [..]
  context: createContext,
  extensions: [() => new DeduplicateResponseExtension()],
})
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].