GithubHelp home page GithubHelp logo

raphiniert-com / ra-data-postgrest Goto Github PK

View Code? Open in Web Editor NEW
100.0 9.0 31.0 830 KB

react admin client for postgREST

License: MIT License

TypeScript 99.00% JavaScript 1.00%
api postgrest react rest postgresql react-admin

ra-data-postgrest's Introduction

PostgREST Data Provider For React-Admin

npm npm downloads Coverage Status License: MIT

PostgREST Data Provider for react-admin, the frontend framework for building admin applications on top of REST/GraphQL services.

Installation

npm install --save @raphiniert/ra-data-postgrest

NOTE: When using RA 3.x.x, use the data-provider 1.x.x. For RA >= 4.1.x use the data-provider starting from 1.2.x.

REST Dialect

This Data Provider fits REST APIs using simple GET parameters for filters and sorting. This is the dialect used for instance in PostgREST.

Method API calls
getList GET http://my.api.url/posts?order=title.asc&offset=0&limit=24&filterField=eq.value
getOne GET http://my.api.url/posts?id=eq.123
getMany GET http://my.api.url/posts?id=in.(123,456,789)
getManyReference GET http://my.api.url/posts?author_id=eq.345
create POST http://my.api.url/posts
update PATCH http://my.api.url/posts?id=eq.123
updateMany PATCH http://my.api.url/posts?id=in.(123,456,789)
delete DELETE http://my.api.url/posts?id=eq.123
deleteMany DELETE http://my.api.url/posts?id=in.(123,456,789)

Note: The PostgREST data provider expects the API to include a Content-Range header in the response to getList calls. The value must be the total number of resources in the collection. This allows react-admin to know how many pages of resources there are in total, and build the pagination controls.

Content-Range: posts 0-24/319

If your API is on another domain as the JS code, you'll need to whitelist this header with an Access-Control-Expose-Headers CORS header.

Access-Control-Expose-Headers: Content-Range

Usage

// in src/App.js
import * as React from 'react';
import { Admin, Resource, fetchUtils } from 'react-admin';
import postgrestRestProvider,
     { IDataProviderConfig,
       defaultPrimaryKeys,
       defaultSchema } from '@raphiniert/ra-data-postgrest';

import { PostList } from './posts';

const config: IDataProviderConfig = {
    apiUrl: 'http://path.to.my.api/',
    httpClient: fetchUtils.fetchJson,
    defaultListOp: 'eq',
    primaryKeys: defaultPrimaryKeys,
    schema: defaultSchema
}

const App = () => (
    <Admin dataProvider={postgrestRestProvider(config)}>
        <Resource name="posts" list={PostList} />
    </Admin>
);

export default App;

Adding Custom Headers

The provider function accepts an HTTP client function as second argument. By default, they use react-admin's fetchUtils.fetchJson() as HTTP client. It's similar to HTML5 fetch(), except it handles JSON decoding and HTTP error codes automatically.

That means that if you need to add custom headers to your requests, you just need to wrap the fetchJson() call inside your own function:

import { fetchUtils, Admin, Resource } from 'react-admin';
import postgrestRestProvider from '@raphiniert/ra-data-postgrest';

const httpClient = (url, options = {}) => {
    if (!options.headers) {
        options.headers = new Headers({ Accept: 'application/json' });
    }
    // add your own headers here
    options.headers.set('X-Custom-Header', 'foobar');
    return fetchUtils.fetchJson(url, options);
};

const config: IDataProviderConfig = {
    ...
    httpClient: httpClient,
    ...
}

const dataProvider = postgrestRestProvider(config);

render(
    <Admin dataProvider={dataProvider} title="Example Admin">
        ...
    </Admin>,
    document.getElementById('root')
);

Now all the requests to the REST API will contain the X-Custom-Header: foobar header.

Tip: The most common usage of custom headers is for authentication. fetchJson has built-on support for the Authorization token header:

const httpClient = (url, options = {}) => {
    options.user = {
        authenticated: true,
        token: 'SRTRDFVESGNJYTUKTYTHRG',
    };
    return fetchUtils.fetchJson(url, options);
};

Now all the requests to the REST API will contain the Authorization: SRTRDFVESGNJYTUKTYTHRG header.

Special Filter Feature

As postgRest allows several comparators, e.g. ilike, like, eq... The dataProvider is designed to enable you to specify the comparator in your react filter component:

<Filter {...props}>
    <TextInput label="Search" source="post_title@ilike" alwaysOn />
    <TextInput label="Search" source="post_author" alwaysOn />
    // some more filters
</Filter>

One can simply append the comparator with an @ to the source. In this example the field post_title would be filtered with ilike whereas post_author would be filtered using eq which is the default if no special comparator is specified.

RPC Functions

Given a RPC call as GET /rpc/add_them?post_author=Herbert HTTP/1.1, the dataProvider allows you to filter such endpoints. As they are no view, but a SQL procedure, several postgREST features do not apply. I.e. no comparators such as ilike, like, eq are applicable. Only the raw value without comparator needs to be send to the API. In order to realize this behavior, just add an "empty" comparator to the field, i.e. end source with an @ as in the example:

<Filter {...props}>
    <TextInput label="Search" source="post_author@" alwaysOn />
    // some more filters
</Filter>

Compound primary keys

If one has data resources without primary keys named id, one will have to define this specifically. Also, if there is a primary key, which is defined over multiple columns:

const config: IDataProviderConfig = {
    ...
    primaryKeys: new Map([
        ['some_table', ['custom_id']],
        ['another_table', ['first_column', 'second_column']],
    ]),
    ...
}

const dataProvider = postgrestRestProvider(config);

Custom schema

PostgREST allows to select and switch the database schema by setting a custom header. Thus, one way to use this function would be adding the custom header as a string while using react-admin hooks within meta.schema (compare to next section) or to set it up as function of () => (string) while using the data provider just component based. The latter can be done as follows and gives the opportunity to use some central storage (e.g. localStorage) which can be changed at multiple points of the application:

const config: IDataProviderConfig = {
    ...
    schema: () => localStorage.getItem("schema") || "api",
    ...
}

const dataProvider = postgrestRestProvider(config);

Passing extra headers via meta

Postgrest supports calling functions with a single JSON parameter by sending the header Prefer: params=single-object with your request according to its docs.

Within the data provider one can add any kind of header to the request while calling react-admin hooks, e.g.:

const [create, { isLoading, error }] = useCreate(
    'rpc/my-function',
    {
        data: { ... },
        meta: { headers: { Prefer: 'params=single-object' } },
    }
);

Null sort order

Postgrest supports specifying the position of nulls in sort ordering. This can be configured via an optional data provider parameter:

const config: IDataProviderConfig = {
    ...
    sortOrder: PostgRestSortOrder.AscendingNullsLastDescendingNullsLast
    ...
}

const dataProvider = postgrestRestProvider(config);

It is important to note that null positioning in sort will impact index utilization so in some cases you'll want to add corresponding index on the database side.

Vertical filtering

Postgrest supports a feature of Vertical Filtering (Columns). Within the react-admin hooks this feature can be used as in the following example:

const { data, total, isLoading, error } = useGetList(
    'posts',
    {
        pagination: { page: 1, perPage: 10 },
        sort: { field: 'published_at', order: 'DESC' }
        meta: { columns: ['id', 'title'] }
    }
);

Further, one should be able to leverage this feature to rename columns:

columns: ['id', 'somealias:title']

, to cast columns:

columns: ['id::text', 'title']

and even get bits from a json or jsonb column"

columns: ['id', 'json_data->>blood_type', 'json_data->phones']

Note: not working for create and updateMany.

Developers notes

The current development of this library was done with node v19.10 and npm 8.19.3. In this version the unit tests and the development environment should work.

License

This data provider is licensed under the MIT License and sponsored by raphiniert.com.

ra-data-postgrest's People

Contributors

andymed-jlp avatar anthony-bernardo avatar christiaanwesterbeek avatar dependabot[bot] avatar jordiyapz avatar jpagex avatar kav avatar mkkorp avatar olivierdalang avatar programmarchy avatar promitheus7 avatar ruslantalpa avatar scheiblr avatar seclace avatar slax57 avatar yannbeauxis avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ra-data-postgrest's Issues

Any plans to add full text search?

Thank you for the library, very useful.

As I understand, now filtering support only LIKE type of search. It would be great to have full-text search too (@@ operator in Postgres). Any plans for this?

support specifying nullsfirst or nullslast in sorting config.

I'm not sure the optimal path here as it may vary by field and table. Any thoughts on how granular to get here? I'm thinking something similar to defaultOp but we could get fancier with per table or field configuration. For my specific use case I'd like to always use nullslast which would work with a single bool but I suspect that's not enough for others.

Best way to use Compound key ?

It's not a issue, only a question :
I'm using Compound key with ShowView but I'm not very proud of my code

const RowClick = (id, basePath, record) => {
	return `/myresource/${JSON.stringify([record.name,record.category] )}/show`;
}

const MyResourceList = (props) => (
	<List {...props}>
	   <Datagrid rowClick={RowClick}>
	    <TextField source="name" />
	  </Datagrid>
  </List>
);

Is there a better way to do it ? Any opinions would be greatly appreciated... :)

use schema other than public?

Is there any way to use a different schema from public?

I didn't see an obvious way to do this; not sure if the limitation is in this package or supabase.

Are embedded resources supported?

When I try to filter something on my List using this filter:

filter={{ "name@ilike": "ticket"}}

Everything works fine and a URL is generated like so: &name=ilike.*ticket*

When I try to filter the embedded resources, like so:

filter={{ "prices.name@ilike": "ticket"}}

It results in the following URL, which is correct: &prices.name=ilike.*ticket*

However when I try to implement the same filter using a TextInput, everything breaks down:

<TextInput source="prices.name@ilike" label="search" />

Results in: prices=eq.%5Bobject+Object%5D

Adapt for non-subzero cases

Hi !

I've set up a postgrest API following their tutorial. It works surprisingly well, but is not fully compatible with the authProvider in this repo, as the subzero starterkit does things slightly differently than vanilla postgrest :

  • The auth endpoint is the same as the data endpoint (e.g. api.example.com/rpc/login instead of /rest/rpc/login).
  • The login endpoint response is different (/me dict with user information for subzero, {token: "xxx.yyy.zzz"} for vanilla postgrest). It is possible to get user information by decoding the token client side. So it's a matter of storing the token (for reuse in the restProvider) and decoding the token client side (for use in getPermissions).

In the readme, you say you're open to contribution from that. How would you see that ? An alternative authProvider, or some settings/callbacks to customize behaviour ?

eq missing from single id lookup

Hi.

Firstly - thanks so much for this! Makes working with postgres a breeze!

With version 1.1.3 the single query URL ends with an ?id=eq.abc123 whereas in 1.1.5 it ends with ?id=abc123, and this causes an issue with my postgrest v7.0.1 instance. Am I doing something wrong?

If it's not me doing something stupid - I'm happy to work out the issue and do a PR!

Many Thanks,
Andy

Versioning

Would it make more sense to release a version 3.x and 4.x of this repo s.t. the delineation to match versions with react-admin?

Enforce code formatting

With my recent PR's I noticed code formatting is not enforced by something like prettier in this repo. I tried my best to format my changes to adhere to the current formatting, but it's not ideal. It makes for easier collaboration (and smaller PR's) if code formatting is enforced.

I propose to use prettier with the following .prettierrc config that adhere best to the current code formatting.

{
    "singleQuote": true,
    "tabWidth": 2,
    "trailingComma": "es5",
    "useTabs": false
}

Who's in for adding unit tests?

This repo is about querying data and is probably used in many productional apps. When adding PR's with new feature, like I've been doing today, we really need unit tests so that we can confidently merge such PR's.

custom port does not work

using postgrest

server-host = "!4"
server-port = 3001

and App.js

import postgrestRestProvider from 'ra-data-postgrest';
const dataProvider = postgrestRestProvider('http://localhost:3001');

gives index.js:1 Error: Network Error
at createError (createError.js:16)
at XMLHttpRequest.handleError (xhr.js:83)

when change posgrest to server-port = 80

it works

Multiple OR operators

Hi there,

I have the following scenario, using version 2.0.0 alpha 3, referring to the just implemented #82.

In many scenarios I need to filter out data by two columns which could be an integer or null. I could do so with an @or operator (just beginning, so maybe I just don't know the right approach for that), but if I try to add multiple OR operators to the filters it only adds one of them in the request url.
I verified that postgrest allows this by using a custom curl request.

I don't want you to spend time on something that could just be my fault, my question is simply: has it been tested already (in which case I'll investigate on my side) or should I post what I've tried and experienced so far?

My current workaround is fairly stupid, I add the two filters to the request url in the dataprovider.

If it helps, I'm happy to test that and try around - just to get rid of any unnecessary workarounds. ;)

Update Fails on resources without primary keys named id

Consider a list which displays all brands , having columns (brand_name,brand_id), while updating the data below is the sample request body is getting fired to PostgRest

{
"brand_id":"88ed8a29-88ed-422e-8755-88ed577fa8be",
"brand_name":"Nike",
"id":"88ed8a29-88ed-422e-8755-88ed577fa8be"
}

Ideally the id shouldn't be present since this id column wont be available in db.

May be we should perform delete params.data.id while sending an update request

Getting "Module not found" error

Hi, when trying to use the package I am getting the following error:

Failed to compile.

./src/App.js
Module not found: Can't resolve '@raphiniert/ra-data-postgrest' in '/home/ag/git/rexample/admin/src'

Followed these steps:

$ npx create-react-app admin
$ cd admin
$ npm install react-admin  ra-data-json-server prop-types
$ npm install --save @raphiniert/ra-data-postgrest

The package is present in the node_modules directory:

$ pwd
/home/ag/git/rexample/admin

$ ls node_modules/@raphiniert/ra-data-postgrest/
CHANGELOG.md  LICENSE  node_modules  package.json  README.md  src

Then added the postgrestRestProvider line to the ReactAdmin example in src/App.js:

import React from 'react';
import { Admin, Resource, ListGuesser } from 'react-admin';
import jsonServerProvider from 'ra-data-json-server';
import postgrestRestProvider from '@raphiniert/ra-data-postgrest';

const dataProvider = jsonServerProvider('https://jsonplaceholder.typicode.com');
const App = () => (
    <Admin dataProvider={dataProvider}>
        <Resource name="users" list={ListGuesser} />
    </Admin>
);

export default App

Nodejs version is v12.16.3

Any idea what I am missing? Sorry I have few experience in Nodejs and React.

Problem calling rpc-endpoints

There is a problem while calling rpc-endoints using compound key. it'll result in a and-clause.

Might require some refactor concerning the compound key functionality.

URL malformed when passing a set to "contains" ( cs / @> )

Example:

When defining a filter:


const AowSelectInput = (props: AutocompleteArrayInputProps) => {
  const { data, isLoading } = useGetList('areas_of_work', {
    pagination: { page: 1, perPage: Number.MAX_SAFE_INTEGER },
    sort: { field: 'name', order: 'asc' },
    meta: { columns: ['id', 'name'] },
  });
  return (
    <AutocompleteArrayInput
      choices={data}
      isLoading={isLoading}
      source="aow_ids"
      {...props}
    />
  );
};

const ProjectFilters = (props: any) => {
  return (
    <Filter {...props}>
      <AowSelectInput label="AoWs" source="aow_ids@cs" />
      ...
    </Filter>
  );
};

When applied to a single id, no problem but adding a second id the url is formed like:

'http://localhost:3000/projects?offset=0&limit=50&aow_ids=cs.17%2C29&order=name.asc' (note: aow_ids=cs.17,29)

It should be: aow_ids=cs.{17,29} (note: parenthesis)

updateMany returns Bad Request Error

I'm using ra-supabase which uses this library under the hood. Whenever i call updateMany function, Supabase server returns 400 http error with response {"code":"21000","details":null,"hint":null,"message":"UPDATE requires a WHERE clause"}. I guess it's because to bulk update in postgrest we should pass ids into url params like this id=in.(123,456,789) instead of body

The create method should return the data returned by Postgrest and not the posted data

Currently, the create method simply returns what the posted data is (including the id returned by Postgrest). However the Prefer: 'return=representation' is passed in the request which means that Postgrest is going to return the data of the new record. So the create method should simply return the json and not { ...params.data, id: encodeId(json, primaryKey) }

I know the example rest implementation indeed shows returning the posted data, but our dataProvider specifically adds this Prefer: 'return=representation' header, so we know that Postgrest is going to return the data and we should use that for the response.

Thoughts?

update method using incorrect source of id field

Hi there, thank you for this library.

I just noticed that there is a small bug in the update method of the data-provider. It is extracting the id from the params.data object -- but there is no guarantee this field will exist there. This is also inconsistent with the other methods which all use params.id / params.ids -- as specified in the react-admin docs.

I've created a PR with a fix: #20

Hope that's ok!

how to implement full text filter?

in react-admin document, using 'q' in searchinput component to implement full text filter, how to do that in ra-data-postgrest?
thanks

ReferenceField with UUID

I'm trying to use ReferenceField with UUID

<ReferenceField source="customer" reference="customers">
  <TextField source="id" />
</ReferenceField>

In the API, customer field is an UUID (fac9062f-2e20-41c6-a74a-d2d8871025a3) :

[
    {
        "id": "12a36bc8-85a7-46a5-9df5-15389eeffd96",
        "amount": 125.55,
        "customer": "fac9062f-2e20-41c6-a74a-d2d8871025a3"
    }
]

I looked in the console and instead of calling something like that :

http://localhost:3000/customers?id=in.(fac9062f-2e20-41c6-a74a-d2d8871025a3)

It's calling something like that :

http://localhost:3000/customers?id=in.(0=%5B&1=%22&10=-&11=2&12=e&13=2&14=0&15=-&16=4&17=1&18=c&19=6&2=f&20=-&21=a&22=7&23=4&24=a&25=-&26=d&27=2&28=d&29=8&3=a&30=8&31=7&32=1&33=0&34=2&35=5&36=a&37=3&38=%22&39=%5D&4=c&5=9&6=0&7=6&8=2&9=f)

I think this is this line that's causing the bug :

const url = `${apiUrl}/${resource}?id=in.(${stringify(query)})`;

PR

See my PR #4

React Admin v4 does not work with current ra-data-postgrest version

When using npm run build with react-admin v4 and current version of ra-data-postgrest there is a build issue due to differing ra-core version usages. As a result, trying to compile using npm run build results in needing to use react-admin v3.19 instead of the latest v4.x.

Two forks I've found that seem to have implemented this update - unsure about whether they work or not as my investigation has been quite superficial at this point. Hoping to spend some time this weekend testing version updates for myself to ensure functionality, and hopefully open a PR for updating dependencies to match react-admin.

https://github.com/Exact-Realty/ra-data-postgrest
https://github.com/milutinovici/ra-data-postgrest

EDIT: It seems there is currently a PR open as well with the version updates for react-admin compatibility when using npm run build: #30

Diff updates

We've got an issue where tables or views with computed columns don't update correctly as the update method is sending all the fields even ones that haven't changed (e.g. computed fields). I've solved this in a patch via diffing field values before update to only send changed fields. I can start a PR if you are interested.

Default behavior is trying to PATCH/update primary key?

I'm trying to update a single record, and seems like ra-d-pg is performing a PATCH to the endpoint, with the ID / primary key in the payload:

PATCH /rest/v1/table?id=eq.1
{ id: 1, name: "foo" }

Because the id column was created with something like:

id INT GENERATED BY DEFAULT AS IDENTITY

and the above seems to be trying to update the id column, I'm getting this error:

column "id" can only be updated to DEFAULT

Is this expected behavior?

No API key found in request

I would like to access to the Supabase tables but don't understand how to access with this plugin.
Actually, I get this :

const httpClient = (url, options = {}) => {
  options.user = {
      authenticated: true,
      token: process.env.REACT_APP_SUPABASE_ANON
  };
  return fetchUtils.fetchJson(url, options);
};

const dataProvider = postgrestRestProvider(process.env.REACT_APP_SUPABASE_URL, httpClient);
return (
    <Admin dataProvider={dataProvider}>
        <Resource name="posts" list={ListGuesser} />
        <Resource name="authors" list={ListGuesser} />
    </Admin>
)

Si when I try to access to the data, I have this error message : No API key found in request

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.