• Stars
    star
    335
  • Rank 121,707 (Top 3 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created over 4 years ago
  • Updated about 1 month ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

react-admin data provider for Hasura GraphQL Engine

ra-data-hasura

A GraphQL data provider for react-admin v4 tailored to target Hasura GraphQL endpoints. For React Admin v3 use v0.4.2 of this library.

Example applications demonstrating usage:

Benefits and Motivation

This utility is built on top of ra-data-graphql and is a custom data provider for the current Hasura GraphQL API format.

The existing ra-data-graphql-simple provider, requires that your GraphQL endpoint implement a specific grammar for the objects and methods exposed, which is different with Hasura because the exposed objects and methods are generated differently.

This utility auto generates valid GraphQL queries based on the properties exposed by the Hasura API such as object_bool_exp and object_set_input.

Installation

Install with:

npm install --save graphql ra-data-hasura

Usage

The ra-data-hasura package exposes a single function with the following signature:

buildHasuraProvider(
  options?: Object,
  buildGqlQueryOverrides?: Object,
  customBuildVariables?: Function,
  customGetResponseParser?: Function,
) => Function

See the Options and Customizing queries sections below for more details on these arguments.

This function acts as a constructor for a dataProvider based on a Hasura GraphQL endpoint. When executed, this function calls the endpoint, running an introspection query to learn about the specific data models exposed by your Hasura endpoint. It uses the result of this query (the GraphQL schema) to automatically configure the dataProvider accordingly.

// Initialize the dataProvider before rendering react-admin resources.
import React, { useState, useEffect } from 'react';
import buildHasuraProvider from 'ra-data-hasura';
import { Admin, Resource } from 'react-admin';

import { PostCreate, PostEdit, PostList } from './posts';

const App = () => {
  const [dataProvider, setDataProvider] = useState(null);

  useEffect(() => {
    const buildDataProvider = async () => {
      const dataProvider = await buildHasuraProvider({
        clientOptions: { uri: 'http://localhost:8080/v1/graphql' },
      });
      setDataProvider(() => dataProvider);
    };
    buildDataProvider();
  }, []);

  if (!dataProvider) return <p>Loading...</p>;

  return (
    <Admin dataProvider={dataProvider}>
      <Resource
        name="Post"
        list={PostList}
        edit={PostEdit}
        create={PostCreate}
      />
    </Admin>
  );
};

export default App;

How It Works

The data provider converts React Admin queries into the form expected by Hasura's GraphQL API. For example, a React Admin GET_LIST request for a person resource with the parameters :

{
  "pagination": { "page": 1, "perPage": 5 },
  "sort": { "field": "name", "order": "DESC" },
  "filter": {
    "ids": [101, 102]
  }
}

will generate the following GraphQL request for Hasura :

query person($limit: Int, $offset: Int, $order_by: [person_order_by!]!, $where: person_bool_exp) {
  items: person(limit: $limit, offset: $offset, order_by: $order_by, where: $where) {
    id
    name
    address_id
  }
  total: person_aggregate(limit: $limit, offset: $offset, order_by: $order_by, where: $where) {
    aggregate {
      count
    }
  }
}

With the following variables to be passed alongside the query:

{
  limit: 5,
  offset: 0,
  order_by: { name: 'desc' },
  where: {
    _and: [
      {
        id: {
          _in: [101, 102]
        }
      }
    ]
  }
}

React Admin sort and filter objects will be converted appropriately, for example sorting with dot notation:

export const PostList = (props) => (
  <List {...props} sort={{ field: 'user.email', order: 'DESC' }}>
    ...
  </List>
);

will generate the following GraphQL query variables:

{
  limit: 25,
  offset: 0,
  order_by: { user: { email: 'desc' } }
}

and

export const AddressList = () => (
  <List
    sort={{ field: 'city', order: 'DESC' }}
    filter={{ distinct_on: 'city' }}
  >
    ...
  </List>
);

will generate the following GraphQL query variables:

{
  // ...
  "order_by": {
    "city": "desc"
  },
  "distinct_on": "city"
}

Keep in mind that distinct_on must be used in conjunction with order_by, otherwise a "distinct_on" columns must match initial "order_by" columns" error will result. See more here.

Options

Customize the Apollo client

You can either supply just the client options:

buildGraphQLProvider({
  clientOptions: {
    uri: 'http://localhost:8080/v1/graphql',
    ...otherApolloOptions,
  },
});

or supply the client instance directly:

buildGraphQLProvider({ client: myClient });

Adding Authentication Headers

To send authentication headers, you'll need to supply the client instance directly with headers defined:

import { ApolloClient, InMemoryCache } from '@apollo/client';

const myClientWithAuth = new ApolloClient({
  uri: 'http://localhost:8080/v1/graphql',
  cache: new InMemoryCache(),
  headers: {
    'x-hasura-admin-secret': 'hasuraAdminSecret',
    // 'Authorization': `Bearer xxxx`,
  },
});

buildHasuraProvider({ client: myClientWithAuth });
Adding headers using just client options

You can also add headers using only client options rather than the client itself:

import { createHttpLink } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';

const authLink = setContext((_, { headers }) => ({
  headers: {
    ...headers,
    'x-hasura-admin-secret': 'hasuraAdminSecret',
    // 'Authorization': `Bearer xxxx`,
  },
}));

const httpLink = createHttpLink({
  uri: 'http://localhost:8080/v1/graphql',
});

const clientOptionsWithAuth = {
  link: authLink.concat(httpLink),
};

buildHasuraProvider({ client: clientOptionsWithAuth });

Customize the introspection

These are the default options for introspection:

const introspectionOptions = {
  include: [], // Either an array of types to include or a function which will be called for every type discovered through introspection
  exclude: [], // Either an array of types to exclude or a function which will be called for every type discovered through introspection
};

// Including types
const introspectionOptions = {
  include: ['Post', 'Comment'],
};

// Excluding types
const introspectionOptions = {
  exclude: ['CommandItem'],
};

// Including types with a function
const introspectionOptions = {
  include: (type) => ['Post', 'Comment'].includes(type.name),
};

// Including types with a function
const introspectionOptions = {
  exclude: (type) => !['Post', 'Comment'].includes(type.name),
};

Note: exclude and include are mutually exclusives and include will take precendance.

Note: When using functions, the type argument will be a type returned by the introspection query. Refer to the introspection documentation for more information.

Pass the introspection options to the buildApolloProvider function:

buildApolloProvider({ introspection: introspectionOptions });

Customize the Data Return

Once the data is returned back from the provider, you can customize it by implementing the DataProvider interface. An example is changing the ID key.

const [dataProvider, setDataProvider] = React.useState<DataProvider | null>(
  null
);

React.useEffect(() => {
  const buildDataProvider = async () => {
    const dataProviderHasura = await buildHasuraProvider({
      clientOptions: {
        uri: 'http://localhost:8080/v1/graphql',
      },
    });
    const modifiedProvider: DataProvider = {
      getList: async (resource, params) => {
        let { data, ...metadata } = await dataProviderHasura.getList(
          resource,
          params
        );

        if (resource === 'example_resource_name') {
          data = data.map(
            (val): Record => ({
              ...val,
              id: val.region_id,
            })
          );
        }

        return {
          data: data as any[],
          ...metadata,
        };
      },
      getOne: (resource, params) => dataProviderHasura.getOne(resource, params),
      getMany: (resource, params) =>
        dataProviderHasura.getMany(resource, params),
      getManyReference: (resource, params) =>
        dataProviderHasura.getManyReference(resource, params),
      update: (resource, params) => dataProviderHasura.update(resource, params),
      updateMany: (resource, params) =>
        dataProviderHasura.updateMany(resource, params),
      create: (resource, params) => dataProviderHasura.create(resource, params),
      delete: (resource, params) => dataProviderHasura.delete(resource, params),
      deleteMany: (resource, params) =>
        dataProviderHasura.deleteMany(resource, params),
    };
    setDataProvider(() => modifiedProvider);
  };
  buildDataProvider();
}, []);

Customizing queries

Queries built by this data provider are made up of 3 parts:

  1. The set of fields requested
  2. The variables defining the query constraints like where, order_by, limit, offset
  3. The response format e.g. { data: {...}, total: 100 }

Each of these can be customized - functions overriding numbers 2 and 3 can be passed to directly to buildDataProvider as shown in Usage, whilst number 1 can be customized in parts using the buildGqlQueryOverrides object argument:

{
  buildFields?: Function,
  buildMetaArgs?: Function,
  buildArgs?: Function,
  buildApolloArgs?: Function,
}

A likely scenario is that you want to override only the buildFields part so that you can customize your GraphQL queries - requesting fewer fields, more fields, nested fields etc.

This can be easily done, and importantly can be done using gql template literal tags, as shown in the examples below. Take a look at this demo application to see it in action.

Example: extending a query to include related entities

By default, the data provider will generate queries that include all fields on a resource, but without any relationships to nested entities. If you would like to keep these base fields but extend the query to also include related entities, then you can write a custom buildFields like this:

import buildDataProvider, { buildFields } from 'ra-data-hasura';
import type { BuildFields } from 'ra-data-hasura';
import gql from 'graphql-tag';

/**
 * Extracts just the fields from a GraphQL AST.
 * @param {GraphQL AST} queryAst
 */
const extractFieldsFromQuery = (queryAst) => {
  return queryAst.definitions[0].selectionSet.selections;
};

// Define the additional fields that we want.
const EXTENDED_GET_ONE_USER = gql`
  {
    todos_aggregate {
      aggregate {
        count
      }
    }
  }
`;

const customBuildFields: BuildFields = (type, fetchType) => {
  const resourceName = type.name;

  // First take the default fields (all, but no related or nested).
  const defaultFields = buildFields(type, fetchType);

  if (resourceName === 'users' && fetchType === 'GET_ONE') {
    const relatedEntities = extractFieldsFromQuery(EXTENDED_GET_ONE_USER);
    defaultFields.push(...relatedEntities);
  }

  // Extend other queries for other resources/fetchTypes here...

  return defaultFields;
};

buildDataProvider(options, { buildFields: customBuildFields });

Example: write a completely custom query

If you want full control over the GraphQL query, then you can define the entire set of fields like this:

import gql from 'graphql-tag';
import buildDataProvider, { buildFields } from 'ra-data-hasura';
import type { BuildFields } from 'ra-data-hasura';

/**
 * Extracts just the fields from a GraphQL AST.
 * @param {GraphQL AST} queryAst
 */
const extractFieldsFromQuery = (queryAst) => {
  return queryAst.definitions[0].selectionSet.selections;
};

const GET_ONE_USER = gql`
  {
    id
    name
    todos(
      where: { is_completed: { _eq: false } }
      order_by: { created_at: asc }
    ) {
      title
    }
    todos_aggregate {
      aggregate {
        count
      }
    }
  }
`;

const customBuildFields: BuildFields = (type, fetchType) => {
  const resourceName = type.name;

  if (resourceName === 'users' && fetchType === 'GET_ONE') {
    return extractFieldsFromQuery(GET_ONE_USER);
  }

  // No custom query defined, so use the default query fields (all, but no related/nested).
  return buildFields(type, fetchType);
};

buildDataProvider(options, { buildFields: customBuildFields });

Note that when using this approach in particular, it is possible that you will come across this issue.

Special Filter Feature

This adapter allows filtering several columns at a time with using specific comparators, e.g. ilike, like, eq, etc.

<Filter {...props}>
  <TextInput
    label="Search"
    source="email,first_name@_eq,last_name@_like"
    alwaysOn
  />
</Filter>

It will generate the following filter payload

{
  "variables": {
    "where": {
      "_and": [],
      "_or": [
        {
          "email": {
            "_ilike": "%edu%"
          }
        },
        {
          "first_name": {
            "_eq": "edu"
          }
        },
        {
          "last_name": {
            "_like": "%edu%"
          }
        }
      ]
    },
    "limit": 10,
    "offset": 0,
    "order_by": {
      "id": "asc"
    }
  }
}

The adapter assigns default comparator depends on the data type if it is not provided. For string data types, it assumes as text search and uses ilike otherwise it uses eq. For string data types that uses like or ilike it automatically transform the filter value as %value%.

Nested filtering

Nested filtering is supported using # as a field separator.

<TextInput
  label="Search by indication, drug, sponsor, nctid"
  source="indication#name@_ilike,drug#preferred_name@_ilike,sponsor#name@_ilike,trial#nctid@_ilike"
  alwaysOn
/>

Will produce the following payload:

{
  "where": {
    "_and": [],
    "_or": [
      {
        "indication": {
          "name": {
            "_ilike": "%TEXT%"
          }
        }
      },
      {
        "drug": {
          "name": {
            "_ilike": "%TEXT%"
          }
        }
      },
      {
        "sponsor": {
          "name": {
            "_ilike": "%TEXT%"
          }
        }
      }
    ]
  },
  "limit": 10,
  "offset": 0,
  "order_by": {
    "id": "asc"
  }
}

Jsonb filtering

<TextField label="Theme Color" source="users#preferences@_contains@ux#theme" />

Will produce payload:

{
  "where": {
    "_and": [
      {
        "users": {
          "preferences": {
            "_contains": {
              "ux": {
                "theme": "%TEXT"
              }
            }
          }
        }
      }
    ]
  },
  "limit": 10,
  "offset": 0,
  "order_by": {
    "id": "asc"
  }
}

Fetch data matching a jsonb _contains operation

<FunctionField render={(rec: {processor = "apple" | "google" | "stripe", ...})
  <ReferenceManyField
    reference="account_plans"
    target="payments#details@_contains@processor#${rec.processor}_id"
    source="payment_processor"
  >
    <Datagrid>
    ...
    </Datagrid>
  </ReferenceManyField>
} />

Will produce payload:

{
  "where": {
    "_and": [
      {
        "payments": {
          "details": {
            "_contains": {
              "processor": {
                "%{rec.processor}_id": "%{rec.id}"
              }
            }
          }
        }
      }
    ]
  }
}

Sorting lists by multiple columns

Hasura support sorting by multiple fields but React Admin itself doesn't allow the List component to receive an array as the sort prop. So to achieve sorting by multiple fields, separate the field and order values using a comma.

For example, a list like

const TodoList = (props) => (
  <List sort={{ field: 'title,is_completed', order: 'asc,desc' }} {...props}>
    <Datagrid rowClick="edit">...</Datagrid>
  </List>
);

will generate a query with an order_by variable like

order_by: [{ title: "asc" }, { is_completed: "desc" }]

Fields may contain dots to specify sorting by nested object properties similarly to React Admin source property.

Contributing

To modify, extend and test this package locally,

$ cd ra-data-hasura
$ npm link

Now use this local package in your react app for testing

$ cd my-react-app
$ npm link ra-data-hasura

Build the library by running npm run build and it will generate the transpiled version of the library under lib folder.

Credits

We would like to thank Steams and all the contributors to this library for porting this adapter to support GraphQL spec, since all the releases till v0.0.8 were based off the REST API spec.

More Repositories

1

graphql-engine

Blazing fast, instant realtime GraphQL APIs on your DB with fine grained access control, also trigger webhooks on database events.
TypeScript
30,844
star
2

gitkube

Build and deploy docker images to Kubernetes using git push
Go
3,758
star
3

graphqurl

curl for GraphQL with autocomplete, subscriptions and GraphiQL. Also a dead-simple universal javascript GraphQL client.
JavaScript
3,301
star
4

skor

Now part of Hasura GraphQL Engine. Listen to postgres events and forward them as JSON payloads to a webhook
C
1,247
star
5

learn-graphql

Real world GraphQL tutorials for frontend developers with deadlines!
JavaScript
1,177
star
6

gatsby-gitbook-starter

Generate GitBook style modern docs/tutorial websites using Gatsby + MDX
JavaScript
976
star
7

awesome-react-graphql

A curated collection of resources, clients and tools that make working with `GraphQL and React/React Native` awesome
736
star
8

eff

🚧 a work in progress effect system for Haskell 🚧
Haskell
547
star
9

react-check-auth

Add auth protection anywhere in your react/react-native app
JavaScript
530
star
10

3factor-example

Canonical example of building a 3factor app : a food ordering application
JavaScript
455
star
11

awesome-live-reloading

A curated collection of live-reloading / hot-reloading / watch-reloading tools for different languages and frameworks.
435
star
12

awesome-vue-graphql

A curated collection of resources, clients and tools that make working with `GraphQL and Vue.js` awesome
302
star
13

graphql-bench

A super simple tool to benchmark GraphQL queries
TSQL
256
star
14

hasura-ecommerce

TypeScript
245
star
15

pgdeltastream

Streaming Postgres logical replication changes atleast-once over websockets
Go
244
star
16

graphql-engine-heroku

Blazing fast, instant realtime GraphQL APIs on Postgres with fine grained access control, also trigger webhooks on database events.
Dockerfile
229
star
17

graphql2chartjs

graphql2chartjs reshapes your GraphQL data as per the ChartJS API.
JavaScript
222
star
18

hasura-aws-stack

A complete production ready 100% serverless stack on AWS with Hasura
JavaScript
212
star
19

json2graphql

From a JSON file to postgres-backed realtime GraphQL
JavaScript
199
star
20

3factor

3factor app is an architecture pattern for modern fullstack apps. 3factor apps are fast to build and are highly scalable.
SCSS
179
star
21

client-side-graphql

147
star
22

hasura-k8s-stack

A feature-complete Hasura stack on Kubernetes
JavaScript
138
star
23

hasura-actions-examples

Examples of handling custom business logic with Hasura Actions
JavaScript
135
star
24

awesome-angular-graphql

A curated collection of resources, clients and tools that make working with `GraphQL and Angular` awesome
132
star
25

gqless-movies-demo

A movies app using Hasura and gqless
TypeScript
127
star
26

awesome-fluent-graphql

Awesome list of fluent GraphQL clients & examples
TypeScript
103
star
27

graphiql-online

Explore your GraphQL APIs with headers
JavaScript
90
star
28

kubeformation

Create declarative cluster specifications for your managed Kubernetes vendor (GKE, AKS)
Go
86
star
29

data-dictionary

TypeScript
83
star
30

firebase2graphql

Move from Firebase realtime db to instant GraphQL APIs on Postgres
JavaScript
81
star
31

jwt-guide

TypeScript
79
star
32

nodejs-graphql-subscriptions-boilerplate

Boilerplate to setup GraphQL subscriptions in your nodejs code
JavaScript
78
star
33

graphql-serverless

Example boilerplates for GraphQL backends hosted on serverless platforms
Go
70
star
34

graphql-parser-hs

A GraphQL query parser for Haskell
Haskell
59
star
35

sphinx-graphiql

Sphinx plugin that adds a GraphiQL directive so that you can embed an interactive GraphQL query explorer in your docs
JavaScript
57
star
36

kriti-lang

A minimal JSON templating language
Haskell
53
star
37

schema-stitching-examples

JavaScript
44
star
38

gitkube-example

An example repo to be used with gitkube: git push to deploy on to Kubernetes
HTML
43
star
39

graphql-backend-benchmarks

GraphQL performance benchmarks across Hasura, Postgraphile and Prisma
Shell
42
star
40

comment-progress

Notify progress by commenting on GitHub issues, pull requests, and commits :octocat: πŸ’¬
JavaScript
38
star
41

local-development

[Deprecated] Run Hasura locally on your computer
37
star
42

rxdb-hasura-demo

An Offline first todo app
JavaScript
37
star
43

monad-validate

(NOTE: REPOSITORY MOVED TO NEW OWNER: https://github.com/lexi-lambda/monad-validate) A Haskell monad transformer library for data validation
Haskell
32
star
44

pod42

Python
31
star
45

gitlab-graphql

Install gitlab and expose the gitlab api's over GraphQL
JavaScript
29
star
46

codegen-assets

TypeScript
28
star
47

data-hub

Explore data sources from a native GraphQL API, database schemas to custom code contributed by the community.
PLpgSQL
27
star
48

pg-client-hs

A low level Haskell library to connect to postgres
Haskell
25
star
49

template-gallery

Repository containing schema sharing packages.
PLpgSQL
24
star
50

yelp-clone-react

A Yelp clone built using React + GraphQL + Hasura
JavaScript
24
star
51

authz-workshop

TSQL
23
star
52

ndc-hub

Shell
22
star
53

graphql-schema-stitching-demo

Schema Stitching Example with Hasura GraphQL + MetaWeather API
JavaScript
22
star
54

github-integration-starter

Try out Hasura's GitHub Integration on Cloud Projects using the examples in this repo.
22
star
55

ndc-typescript-deno

Instant Hasura Native Data Connector by writing Typescript Functions
TypeScript
22
star
56

hasura-cloud-preview-apps

TypeScript
21
star
57

issues

Dump and sync org wide issues into postgres and visualise with metabase.
Python
19
star
58

imad-app

Base repository for IMAD course application.
JavaScript
19
star
59

realm-pg-sync

The realm-pg-sync microservice
JavaScript
18
star
60

graphql-data-specification

A specification for Data APIs with GraphQL
Haskell
18
star
61

hasura-discord-docs-bot

PLpgSQL
18
star
62

react-apollo-todo

A todo app with react, apollo demonstrating graphql queries, mutations and subscriptions.
CSS
17
star
63

architect-graphql-workshop

JavaScript
16
star
64

continuous-backup

Postgres wal-e continuous backup system
Shell
16
star
65

preview-actions

Starter kit to try out actions
JavaScript
16
star
66

auth-ui-kit

Web UI Kit for Hasura Authentication
JavaScript
15
star
67

graphql-example-apps

PLpgSQL
14
star
68

js-sdk

JavaScript
14
star
69

ndc-spec

NDC Specification and Reference Implementation
Rust
14
star
70

cloud-functions-boilerplates

Boilerplates for cloud functions (AWS Lambda, Google Cloud Functions, Azure Cloud Functions, Zeit, etc.) that work in conjunction with Hasura GraphQL Engine's event triggers
JavaScript
14
star
71

graphql-subscriptions-benchmark

TypeScript
13
star
72

sample-apps

TypeScript
12
star
73

smooth-checkout-buildkite-plugin

All the things you need during a Buildkite checkout 🧈 πŸͺ
Shell
12
star
74

sqlite-dataconnector-agent

SQLite Data Connector Agent for Hasura GQL Engine. Please note that this repository is a mirror. We will still accept PRs, but will have to mirror them to our upstream repo.
TypeScript
11
star
75

github-bot

Hasura's own GitHub bot πŸ€–
JavaScript
11
star
76

generator-hasura-web

JavaScript
11
star
77

demo-apps

Config to deploy Hasura demo apps using Docker Compose
HTML
11
star
78

smooth-secrets-buildkite-plugin

A buildkite plugin to setup ssh keys and env secrets for your pipelines 🧈 πŸ”’
Shell
11
star
79

chat-app-android

Java
10
star
80

custom-resolvers-boilerplate

A boilerplate for writing custom resolvers with Hasura GraphQL Engine
JavaScript
10
star
81

open-data-domain-specification

Rust
10
star
82

android-sdk

The Android SDK for Hasura
Java
9
star
83

sample-auth-webhook

Sample auth webhooks for the Hasura GraphQL engine
JavaScript
9
star
84

generator-hasura-node

JavaScript
9
star
85

graphql-asia-workshop

JavaScript
9
star
86

reactathon-workshop

9
star
87

go-buildkite-dsl

Write Buildkite configs in Go πŸͺ πŸ“
Go
8
star
88

graphql-on-various-pg

Hasura's GraphQL engine on various Postgres systems/providers
Shell
8
star
89

cli-plugins-index

8
star
90

ndc-sdk-typescript

NDC SDK for TypeScript
TypeScript
8
star
91

graphql-weather-api

A simple GraphQL express weather api server boilerplate
JavaScript
8
star
92

supergraph-top-n-challenge

JavaScript
8
star
93

haskell-docker-builder

Package haskell binaries as docker images
Makefile
7
star
94

graphql-engine-install-manifests

Various installation manifests for Hasura's GraphQL Engine
Shell
7
star
95

laravel-todo-hge

A sample Laravel app with an auth webhook
PHP
7
star
96

awesome-react-fullstack

A review of the must know concepts & tools for going fullstack with react. Awesome list to the top tools and learning resources for each concept.
7
star
97

weaviate_gdc

POC: Weaviate data connector
TypeScript
7
star
98

ai-workshop-hasuracon23

Jupyter Notebook
6
star
99

ndc-postgres

Hasura v3 Data Connector for PostgreSQL
Rust
6
star
100

trigger-serverless-zeit-example

JavaScript
6
star