Pg_GraphQL: A GraphQL Extension for PostgreSQL
supabase.com
supabase.com
My understanding is that Postgrest is a separate project that began development in an unrelated way to Supabase, and Supabase is just an user. Is that correct?
But, Supabase is also a contributor to Postgrest, right? Does Supabase employs engineers dedicated to it?
Supabase uses PostgREST for its automatic/reflected REST API
> Supabase is just an user. Is that correct?
yep!
> Does Supabase employs engineers dedicated to it?
> Great to see AWS providing direct data coupling as a service. /s
> This service allows you to directly map a GraphQL endpoint to a database table. It’s like putting getters and setters on an object and claiming your encapsulating private variables. The end result is coupling between GraphQL clients and the underlying datasource.
> Information hiding is a key concept in independent change. Can I change the provider (of the GraphQL) endpoint independent of the clients? Directly exposing internal data structure makes this very difficult.
And [2]:
> So a few people have asked why I have this snarky response. What is my problem with this service? Well, to be clear, it’s not an issue with GraphQL, it’s an issue with direct coupling with underlying datasources #thread
> The service as advertised makes it simple to map a GraphQL definition against a database. Now, what’s the problem with this? Well, the devil here is in the detail. But fundamentally it comes down to how important information hiding is to you.
> ... see the thread for more ...
[1] https://twitter.com/samnewman/status/1346541251617828877 [2] https://twitter.com/samnewman/status/1346749556583780352
He's absolutely right that you shouldn't couple directly to the underlying representation. But Postgres lets you transparently define views that can be queried (and, with a little more elbow grease, updated) just like any other table. You can provide decoupling from within the database, and do so on-demand as your domain and your data model evolve.
I don't enjoy planting a separate bespoke API server on top of the database. Usually you end up lifting many of the same capabilities the database already has (auth, batching, ...) to your custom API, so a lot of the server is just boilerplate. Many API operations are natural consequences of your data model; there's little business or engineering value-add once you've settled on the latter, you're just writing glorified FFI bindings.
Lazy engineering will cause problems no matter what architectural stack you end up using. But a state-first architecture doesn't have to mean a complete loss of loose coupling -- it just means different techniques for achieving it.
I consider views and functions to be my database’s API, and when using a wrapping tool (I’ve used Hasura) I get it to use only the views in a dedicated API schema rather than the tables directly.
In addition to coupling, you want control over what tables and fields you publish.
A simple view is literally one line of SQL; it’s hardly a burden.
A lot of old systems have this tight coupling between databases and back-end code. I would advice against going down that path.
GraphQL was designed to solve the flaws in the utterly poor service design at Facebook. Do not forget that.
https://www.apollographql.com/docs/federation/v2/
Smack graphql on your postgres, and on your ldap, and your graphdb - and federated them as graphql?
The talk about memory constraints seems a bit odd though. The extension is still going to require memory and without profiling the alternatives it seems odd to say: "we won't use any more memory with an extension". Especially when you say "these established/stable alternatives fulfill all our feature requirements". In practice I always found Hasura to be reasonably lightweight (I can't speak for PostGraphile).
I think there's an advantage to running this API layer as an additional service in front of the DB though. Then it can act as an API gateway to more than just your database.
Also, the generation of a "Relay style" schema is off putting for me. I'm really not a fan of the style and was one of the biggest advantages for me in using Hasura.
[1] https://hasura.io/docs/latest/graphql/core/databases/postgre...
as samwillis mentioned, the memory footprint is tiny, which is a big perk for supabase's platform (or if you're self hosting) but its also fully language agnostic which opens up lots of options for extensibility:
For simple use-cases you can expose the graphql functionality over http using a PostgREST as described here https://supabase.github.io/pg_graphql/quickstart/
but, if you want more configuration like adding in middleware or wiring it up to an existing backend application, you can do that from any programming language that can connect to postgres, rather than only javascript
That reminds me, I need to figure out how to get my employer to sponsor the project...
I reckon for this extension, the business logic uses Stored Procedure and Access Control uses PG's user role? Many apps I know simply have 1 user "myappuser" (or even default user) to access its DB.
Sure; either those apps don't need to differentiate access between their users, in which case one role is sufficient, or they reimplement their own auth system, in which case you'd use Postgres' own rather robust auth system instead. It comes down to the needs of the domain; you'll solve the problems differently depending on what approach you take, but you need to solve the same problems.
Yes -- I've found it very tiring, as you put it, to keep reimplementing the same boilerplate in every API server just to lift the operations my database can already support out to an HTTP frontend. Postgres' auth means I don't have to make or press into service a separate auth system, and there are multiple ways to handle business logic orthogonally.
Stored procedures and triggers work well, but are synchronous within the current transaction, and sometimes simply don't map well to the domain needs. You can also use the AWAIT and NOTIFY statements to set up asynchronous external workers. I find this has a positive effect on the data model, as you're forced to consider what states a system will pass through during an asynchronous flow.
This was a really interesting bit to me, could you give detail on how the memory usage gets split up across these? Thank you.
Postgres runs in a 1 GB VM all by itself with some optimizations to get the most that limited hardware
All other services are in a second 1 GB VM.
You can see a list of those services here https://github.com/supabase/supabase/blob/master/docker/dock...
The memory use per process can differs by use-case, so its important to leave a bit of headroom. For example, PostgREST's memory consumption can grow if the amount of data being returned from its queries is large.
By the time you include:
- supabase studio
- kong
- auth
- storage
- meta
if they each take only 100MB, its pretty snug on a 1 GB VM!
Do you have performance comparisons for the same datasets with Hasura / Graphile?
pg_graphql: https://supabase.github.io/pg_graphql/performance/
graphile: https://www.graphile.org/postgraphile/performance/
We'll certainly be keeping an eye on performance as it gets closer to GA
{supabase team}
But we do use a lot of their more advanced features like being able to use aggregates in sorts, aggregates in results, custom functions, etc. What are your plans there and will you have a public roadmap?
https://supabase.github.io/pg_graphql/roadmap/
Its early days, so the conversations around aggregates haven't happened yet, but I'm optimistic that they'll make an appearance in a future release
Thanks for the feedback on what you're finding valuable and sharing your dissatisfaction with the lack of communication around bugs and M1 support.
M1 support is very close to ready, we've been waiting on a couple of dependencies to support M1.
For what it's worth, I agree with you that our communication needs to improve and it is something we're working on.
Also, congratulations to Oliver and the Supabase team on pg_graphql! PostgreSQL extensions are slick and it'll help even more people embrace APIs from databases.
Even so, there has still been a lot of interest in GraphQL so users can leverage the growing ecosystem for things reflecting the data model/types for client usage, offline caching, etc
Does it work with TimescaleDB?
As long as pg_catalog schema is visible & the base postgres version is >= 13 it should be good-to-go
- custom directives that run custom code
- custom routes on the server that do custom things? Or perhaps proxying to another app to handle alternate/custom routes
- schema injection, custom resolver logic
Love what you are doing with Supabase!
Quick question, have you considered building any kind of local mirroring system for offline mobile app/PWA, say on top of SQLite? Something like Realm for mongodb or PouchDB/Couchbase Lite for CouchDB/Couchbase.
It would obviously need devs to add some extra columns to tables for tracking, and a way to define the merge/overture characteristics for each column, but it would be awesome to have something like that!
It’s something I have thought about building for a while but never found the time. (I want to combine it with Yjs for collaborative offline rich text editing)
On the graphql side, we'll be targeting making mutations offline capable but are still in the brainstorming phase. If you have any suggestions for how you think it could work please open an issue on supabase/pg_graphql and we can discuss over there
https://supabase.github.io/pg_graphql/reflection/#type-conve...
are cast as strings, but prioritizing a JSON conversion if one is available is a great idea that we'll look into
https://www.postgresql.org/docs/current/sql-createcast.html
There are some weird permissions issues to work out IIRC, but this allows the user to specify how the json should be marshalled, and then can be implicitly or explicitly used by any sql statements. This is how PostgREST solves this problem, and I currently have a few custom casts created for range and multirange datatypes through my PostgREST server.
The columns and tables that are visible are also controlled by the role.
One cool thing about that approach is you could run e.g. an admin API and a user facing API all from that same endpoint by executing as different postgres roles!
https://supabase.com/docs/guides/auth
> 1. A user signs up. Supabase creates a new user in the auth.users table.
> 2. Supabase returns a new JWT, which contains the user's UUID.
> 3. Every request to your database also sends the JWT.
> 4. Postgres inspects the JWT to determine the user making the request.
> 5. The user's UID can be used in policies to restrict access to rows.
> Supabase provides a special function in Postgres, auth.uid(), which extracts the user's UID from the JWT. This is especially useful when creating policies.
For instance, say you have a `todos` table and want to make it so users can only read their own todos - you could have an RLS policy `todos.user_id = auth.uid()`. Afterward, `SELECT * FROM todos` will only return the authenticated user's todos. (Equivalent to manually issuing `SELECT * FROM todos WHERE todos.user_id = auth.uid()`.)
There's also `auth.role()` so you can easily restrict access by role: `auth.role() = "admin"`
Is everyone really this okay with their API always being 1-to-1 with their DB models?
In my experience, that kind of setup is only viable for the smaller, simpler projects and otherwise you always run into something where you'd really prefer to have a layer between you and your database.
I am currently using graphql at work and this is a very hard requirement for us. Our database schema is not translated literally into graphql(or the other way around) and this is very intentional. The whole idea of the API layer is to be able to make changes to your internals without breaking all your consumers.
This was a problem with amplify(though the least of the problems we had) and it seems to me this is also a problem here, as it is with Hasura and postgrest.
You can do so by not exposing tables and instead use views and stored functions/procedures.
See https://postgrest.org/en/v9.0/schema_structure.html#schema-i...
I am the person who wrote it. It was a proof-of-concept, written in PL/pgSQL. This made it fairly easy to set up and test but made maintenance and contributions very difficult.
Nice work, looks like an awesome project.
We were pretty happy with Hasura but had to switch to Postgraphile due to poor multi-database support, bummer.
(Postgraphile is not as polished as Hasura in some ways, but since it can used as a library, it's easy to dynamically create N instances of it with different configurations at runtime. Hasura required our ops team to define a new instance of the service in the docker-compose.yml file for each database)
https://github.com/hasura/graphql-engine/issues/6648
Basically, if two entities in the databases have the same names, Hasura fails unless you manually define a unique `custom_name` for each such entity.
Given that the most common multiple database scenario involves different databases with either the exact same schema (one-db-per-tenant) or similar schemas (staging vs. production database), it forces you to painstakingly set a custom_name for basically every single entity in your db.
Thankfully there is an API so in theory you could set this programmatically, but it still means that your client code needs to be manually kept in-sync with whatever custom name generator rule you used.
Our solution comes with a feature called Namespacing [1], which means, every API has its own namespace so there are 0 collisions between the different types and fields. It even goes so far that we also namespace directives so you can have a combined schema of multiple GraphQL APIs and can still use the namespaced directives on fields from that particular upstream.
Disclaimer, I'm the founder of WunderGraph.
[0]: https://wundergraph.com/docs/overview/datasources/overview#o...
[1]: https://wundergraph.com/docs/overview/features/api_namespaci...
Regarding your solution, it seems to be the same that Hasura is working towards. It's a perfectly fine solution if you have a few types that happen to clash in their basic names (we have that too, e.g. for "products.suppliers" vs "services.suppliers").
For multi-tenant solutions, i.e. where all data sources are identical but they refer to different customers' data, separated by user/schema/database/instance, namespacing works but it's not _great_.
It means that the client code needs to get the tenant's namespace from somewhere (probably a claim in the authn system), and then manually interpolate it in all the graphql queries. It's not a security flaw (if you screw up and query spacex_users from a different tenant, you'll just get a 404 - I hope!), but it's going to play awkwardly with most developer tooling, having to always work with interpolated strings.
More importantly, if you use namespace for tenants, now you can't also use them to solve simple name overlaps, unless you split each tenant over multiple APIs.
If you want to improve your multitenant story, here's what we did with Postgraphile (which is a straight adaptation of what our .NET backend does): when the backend starts up, it initializes one identical GraphQL source per tenant. Then looks up each tenant's authorization URL. When a request comes in, the backend validates the auth token, and it uses the signed authorization URL to determine which GraphQL source should run the query.
In this way the clients don't need to worry about tenancy or namespaces at all, there might be a single tenant for all they know. The same request that a developer tests with his login under TestCompany will also run for every other customer, but the auth token and the auth token alone determines which data source it gets run against.
As you can see, this approach isn't a replacement for ad-hoc namespacing. It's meant specifically for the scenario where the same identical schema exists in multiple data sources.
I’d like to learn more about the security of the functionality. I’m not talking about how to apply pg roles/privs/RLS, but rather my perceived risk of how hard it is to write stuff safely in C. That is, who is handling inbound http requests, pg or the extension? What confidence do I have that my db mem is safe from imperfect extension logic?
If the GraphQL server is a separate component, then a GraphQL query needs to do its own query planning, then the resolvers turn that into SQL queries to the database, then the database does its own query planning and execution for each query.
Since one GraphQL query will often turn into multiple SQL queries, there's likely to be duplicated work on the database side across those queries since they relate to the same data.
By integrating the GraphQL server into the DBMS, it can do query planning once for the whole GraphQL query, which means that it can reuse parts of the plan and prevent duplicated work and N+1 queries.
Either way it's going to have to query the database to get all the data, so the essential work is the same. But this way you have opportunities to reduce wasted work to process the whole GraphQL query.
If you want to scale horizontally you can run multiple read replicas with something which distributes connections evenly to the replicas.
This doesn't have aggregates yet but when that conversation happens hopefully it's considered.
Event triggers are the mechanism pg_graphql uses to keep the GraphQL schema up-to-date with the SQL schema
Interesting - many extensions are coming up allowing different interfaces to PostgreSQL! Other "Protocol Convertors" launched recently
MS SQL - https://babelfishpg.org/ MongoDB - https://www.ferretdb.io/